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, inusart.c
and only ever directly (by name) written to once as part of the call touart_setup
.Likewise,
i2c_instance
is defined inmain.c
(I haven't moved the I2C functions yet). Is static and declared identically touart_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
andpUartApi
) and they retain their correct values; onlyuart_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;
}