2024-11-08 10:51 AM - last edited on 2024-11-08 10:55 AM by SofLit
I'm starting to learn about low-level embedded development. My goal is to learn how I can write my own linker scripts and startup code. I also want to learn how to use raw register calls to do something useful. I want to use the NUCLEO-L011K4 board and tried reading through the reference manual to find register addresses and the expected values.
For some reason, my minimal blinky (LED connected to PB13) app doesn't work. It flashes just fine, but the board does nothing after that.
I am pretty sure (keep in mind: Still a newbie), that the registers are the right ones. However, I'm not so sure about the linker script.
Here's my linker script:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 2K
}
ENTRY(main)
__reset_stack_pointer = ORIGIN(RAM) + LENGTH(RAM);
SECTIONS {
.text : {
LONG(__reset_stack_pointer);
LONG(main | 1);
/* The whole interrupt table is 332 bytes long. Advance to that position. */
. += 332;
*(.text)
*(.rodata*)
. = ALIGN(4);
} > FLASH
}
and here's my main.c:
void main(void)
{
volatile int *RCC_IOPENR = (int *)0X4002102C;
volatile int *GPIOB_MODER = (int *)0X50000400;
volatile int *GPIOB_ODR = (int *)0X50000410;
*RCC_IOPENR |= 0b10; // Enable GPIO Port B
*GPIOB_MODER |= 0b10 << 26; // Set Pin 13 as output
while (1)
{
*GPIOB_ODR ^= 0x0020; // Toggle output Pin
for (int i = 0; i < 1000000; i++)
{
__asm__("nop");
}
}
}
I compile this code with
arm-none-eabi-gcc main.c -T LinkerScript.ld -o blink.elf -mcpu=cortex-m0plus -mthumb -nostdlib -Os
and I flash it to the target using the integrated STLink and this OpenOCD command:
openocd -f interface/stlink.cfg -f target/stm32l0.cfg -c "program blink.elf verify reset exit"
The commands are adapted from this blogpost, which is for the blue pill board and Zig. I adapted the code to C and it worked as expected.
I'm not sure if there are other things required in the linker script or if the size of the NVIC table is a different one for the L011K4. I have not found its size in any documentation.
Please tell me if this is not the right place to ask such questions. This is my first post to the forum and I'm still trying to find my way around.
Thanks!
2024-11-08 11:50 AM - edited 2024-11-08 12:04 PM
1. Use ST headers with register/bit definitions to avoid extra work and extra mistakes.
2. 0b10 MODER field means AF, 0b01 is OUT.
3. xor with 0x20 toggles bit 7, not 13.
4. Don't write to ODR, use BSRR and BRR to change output state.
5. Your "delay loop" may or may not delay at all.
6. Vector table size is 48 words for every Cortex-M0(+).
2024-11-08 11:56 AM
@gbm wrote:
5. Your "delay loop" may or may not delay at all.
Need to declare i as volatile.
2024-11-08 01:30 PM - edited 2024-11-08 01:32 PM
>My goal is to learn how I can write my own linker scripts and startup code. I also want to learn how to use raw register calls to do something useful.
Quite strange goal. These days, folks learn edge AI, vision, motor control, communication. All for drones and robotics, all for the victory. Linker scripts... Chatgpt will write you a dozen, and explain.
2024-11-08 01:34 PM
Thanks for taking the time to respond! That's already really helpful. I'll gladly switch to the ST headers, once I've learned to properly read a reference manual and use the register addresses.
@gbm wrote:4. Don't write to ODR, use BSRR and BRR to change output state.
why do you recommend this? I mainly used ODR because that was used in the blogpost I used to get the blue pill working.
Apart from that, I made the following changes to the code:
Here's the code:
// LinkerScript.ld
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 2K
}
ENTRY(main)
__reset_stack_pointer = ORIGIN(RAM) + LENGTH(RAM);
SECTIONS {
.text : {
. = ALIGN(4);
LONG(__reset_stack_pointer);
LONG(main | 1);
/* The whole interrupt table is 96 bytes long. Advance to that position. */
. += 96;
*(.text)
*(.rodata*)
. = ALIGN(4);
_etext = .;
} > FLASH
.data : {
. = ALIGN(4);
_sdata = .;
*(.data)
. = ALIGN(4);
_edata = .;
} >RAM AT >FLASH
.bss : {
. = ALIGN(4);
_sbss = .;
*(.bss)
. = ALIGN(4);
_ebss = .;
} >RAM
}
// main.c
extern unsigned int _etext, _sdata, _edata, _sbss, _ebss;
void init_memory(void)
{
unsigned int data_size = (unsigned int)&_edata - (unsigned int)&_sdata;
unsigned char *flash_data = (unsigned char *)&_etext;
volatile unsigned char *sram_data = (unsigned char *)&_sdata;
for (unsigned int i = 0; i < data_size; i++)
{
sram_data[i] = flash_data[i];
}
unsigned int bss_size = (unsigned int)&_ebss - (unsigned int)&_sbss;
volatile unsigned char *bss = (unsigned char *)&_sbss;
for (unsigned int i = 0; i < bss_size; i++)
{
bss[i] = 0;
}
}
void main(void)
{
init_memory();
volatile int *RCC_IOPENR = (int *)0X4002102C;
volatile int *GPIOB_MODER = (int *)0X50000400;
volatile int *GPIOx_BSRR = (int *)0X50000418;
*RCC_IOPENR |= 0b10; // Enable GPIO Port B
*GPIOB_MODER |= 0b01 << 26; // Set Pin 13 as output
while (1)
{
*GPIOx_BSRR ^= 0x2000; // Switch pin on
for (volatile int i = 0; i < 1000000; i++)
;
*GPIOx_BSRR ^= 0x2000 << 16; // Switch pin off
for (volatile int i = 0; i < 1000000; i++)
;
}
}
The compile and flash commands haven't changed.
2024-11-08 01:35 PM
Thanks, that's a good suggestion. I know, that I should better be using timers for software delays. I'll add that, once I get at least something working :D