This issue has me vexed. I am doing some bare-metal programming in C for a custom sensor board based on the LPC15XX series microcontrollers. The platform amounts to a bunch of I2C sensors and a BlueTooth transmitter hooked up to one of the UARTs. This is NOT one of the LPCxpresso (or similar) boards; it is of a custom design.
Until today, everything was working as expected with all test code in one monolithic file. As the code grew, I decided to break the main functional pieces into separate source files to easier management. Unfortunately, as soon as I did this, the program began segfaulting.
The code being moved is a copy + paste of the original monolithic file. I have stepped through using GDB over a SWD link. It appears that one of my static variables used to hit the internal ROM drivers is being NULLified. Dereferencing this later causes the fault.
Setting a watch on the static variable, I can see the line of code blowing it up is in the I2C initialization routines. What is problematic is the fact that init is done using the provided in-ROM driver routines from NXP. Furthermore, this same code worked without issue when it was all in the same file.
Questions
What would nuke a file-level static variable like that? To my understanding, the variable shouldn't be on the stack so I don't see a buffer overflow causing this.
Why would moving the code between files expose this? Shouldn't it all be compiled and linked into a single namespace anyway?
Replies to Questions in Comments
uart_instance
is defined once, in usart.c
and only ever directly (by name) written to once as part of the call to uart_setup
.
Likewise, i2c_instance
is defined in main.c
(I haven't moved the I2C functions yet). Is static and declared identically to uart_instance
. I have updated the code below to include all static variables from both files.
The pointer is actually NULLed out in the call to pI2CApi->i2c_set_bitrate(hI2C, BASE_CLOCK, I2C_BAUD);
, as determined by setting a watch on the memory in question.
I cannot directly trace into any of the ROM driver routines. They are provided by the uC and are accessed through a double pointer provided at a fixed memory address. I have checked these pointer objects (hUart
and pUartApi
) and they retain their correct values; only uart_instance
gets clobbered.
I created and dumped the map file for this application; it did not contain a reference to uart_instance
(or any static variable, for that matter). Strange. I can post the full contents on request (is a bit long).
Code Vomit
A word of warning, this code is extremely prototypical and unoptimized. A good most of it was copied shamelessly from the datasheet.
Definition of static variables (usart.c)
static UARTD_API_T* pUartApi; // USART API function addr table
static UART_HANDLE_T uart_instance; // Raw storage for USART API
static UART_HANDLE_T* hUart; // Handle to USART API
Definition of static variables (main.c)
static I2CD_API_T* pI2CApi; // I2C API function addr table
static I2C_HANDLE_T i2c_instance; // Raw storage for I2C API
static I2C_HANDLE_T* hI2C; // Handle to I2C API
Init of static handle (usart.c)
int setupUSART0(int sys_clock, int baud) {
UART_CONFIG_T config;
uint32_t frg_val = 0;
uint32_t size_in_bytes;
// Enable USART0 clock
LPC_SYSCON->SYSAHBCLKCTRL1 |= ((1UL << 17));
// Configure USART clock divider
LPC_SYSCON->UARTCLKDIV = (uint8_t)USART_PERIPH_PRESCALE;
// Configure USART0 pins
LPC_SWM->PINASSIGN0 = 0;
LPC_SWM->PINASSIGN0 |= ((uint8_t)18) << 0; // PIO0_18, tx
LPC_SWM->PINASSIGN0 |= ((uint8_t)13) << 8; // PIO0_13, rx
LPC_SWM->PINASSIGN0 |= ((uint8_t)0xFF) << 16; // Not wired, rts
LPC_SWM->PINASSIGN0 |= ((uint8_t)0xFF) << 24; // Not wired, cts
// Get handle to USART API
pUartApi = getUartDriver();
// Initialize memory for UART API
size_in_bytes = pUartApi->uart_get_mem_size();
if (10 < (size_in_bytes / 4)) return -1;
hUart = pUartApi->uart_setup(LPC_USART0_BASE, (uint8_t*)&uart_instance); // <- uart_instance initialized here
// Initialize USART API
config.sys_clk_in_hz = sys_clock / USART_PERIPH_PRESCALE;
config.baudrate_in_hz = baud;
config.config = 1; // 8N1
config.sync_mod = 0;
config.error_en = 0;
frg_val = (pUartApi->uart_init(hUart, &config) << 8) | 0xFF;
// Configure USART fractional divider
if (!frg_val) return -1;
LPC_SYSCON->FRGCTRL = frg_val;
// Enable USART0 in NVIC
NVIC->ISER0 |= ((1UL << 21));
// Enable UART0 interrupts
LPC_USART0->INTENSET |= ((1UL << 0));
return 0;
}
I2C init code that breaks the pointer (main.c)
ErrorCode_t setupI2C() {
ErrorCode_t err;
// Enable I2C clock
LPC_SYSCON->SYSAHBCLKCTRL1 |= ((1UL << 13));
LPC_I2C0->DIV = 0x0078; // 120 decimal
LPC_I2C0->MSTTIME = 0x00; // SCL high / low = 2 clocks each
//DEBUG
LPC_SWM->PINENABLE1 = 0x00;
// Enable interrupts
NVIC->ISER0 |= ((1UL << 24)); // ISE_I2C0
LPC_I2C0->INTENSET |= ((1UL << 0)); // MSTPENDINGEN
LPC_I2C0->INTENSET |= ((1UL << 8)); // SLVPENDINGEN
// Get handle to I2C API
pI2CApi = getI2CDriver();
// Initialize memory for UART API
hI2C = pI2CApi->i2c_setup(LPC_I2C0_BASE, (uint32_t*)&i2c_instance);
// This NULLS uart_instance somehow
// Set bitrate
err = pI2CApi->i2c_set_bitrate(hI2C, BASE_CLOCK, I2C_BAUD);
// Set master mode
LPC_I2C0->CFG = ((1UL << 0)); // MSTEN
return err;
}
I see nothing wrong with the code above.
Generally, an error that can be accidentally introduced by splitting the code into several files is a duplication of static variables. For example, there could be a static UART_HANDLE_T uart_instance;
both in main.c and usart.c (or even weirdly in some .h included by both). Everything will compile and link OK, but functions from main.c and usart.c will use unrelated variables, and the program logic will change with potential for data corruption.
Now, this is a generic remark not directly related to your code. There is some chance it helps... Good luck hunting the bug!