.data
and .bss
. Remember that .data
stores global (static) variables with the value specified during compilation. This section must be copied from flash memory to operational. Global variables with zero value are stored in .bss
, it must be zeroed out.crt0.a
(Wikipedia suggests that this name means C RunTime 0, where 0 means the very beginning of the application’s life). Today we will write an analogue crt0 for our toy platforms.04-helloworld/platform/protoboard/layout.ld
. In general, there are no significant changes relative to the previous version: several constants, a description of the memory section. Let's take a look at the .data
section for an example: .data : ALIGN(4) { _data = .; *(SORT_BY_ALIGNMENT(.data*)) . = ALIGN(4); _edata = .; } > ram AT>rom = 0xff
.data
section is written to the output file with alignment of 4 bytes (i.e., if the cursor points to 0x00000101 before this section, then .data
will start from 0x00000104). The section is in RAM ( > ram
), but is loaded from flash memory ( AT>rom
).=0xff
sets the fill pattern. If unaddressed bytes are formed in the output section, their value will be set to the value of the filler byte. 0xff is selected for the reason that the erased flash memory is all units, i.e., the 0xff record (as opposed to 0x00, for example) is an empty operation._data
. Since the section is in RAM, _data
will indicate its very beginning, in this case: 0x10000000..data
from all input files are copied to the section, while sorting them by size. Sorting plays a very important role, consider it on an example: uint16_t static_int = 0xab; uint8_t static_int2 = 0xab; uint16_t static_int3 = 0xab; uint8_t static_int4 = 0xab;
.data
section are defined here. What gets into the final file? .data 0x0000000010000000 0xc load address 0x00000000000007b0 0x0000000010000000 _data = . *(.data*) .data.static_int2 0x0000000010000000 0x1 build/d0f0154f60ed1a9c2083183e7c731846451d2bdb_helloworld.o 0x0000000010000000 static_int2 *fill* 0x0000000010000001 0x3 ff .data.static_int3 0x0000000010000004 0x4 build/d0f0154f60ed1a9c2083183e7c731846451d2bdb_helloworld.o 0x0000000010000004 static_int3 .data.static_int4 0x0000000010000008 0x1 build/d0f0154f60ed1a9c2083183e7c731846451d2bdb_helloworld.o 0x0000000010000008 static_int4 *fill* 0x0000000010000009 0x1 ff .data.static_int 0x000000001000000a 0x2 build/d0f0154f60ed1a9c2083183e7c731846451d2bdb_helloworld.o 0x000000001000000a static_int 0x000000001000000c . = ALIGN (0x4) 0x000000001000000c _edata = .
*fill*
bytes that align the variables along the word boundary. Due to an unsuccessful order, we lost 4 bytes just like that. Repeat the operation, this time using SORT_BY_ALIGNMENT: .data 0x0000000010000000 0x8 load address 0x00000000000007b0 0x0000000010000000 _data = . *(SORT(.data*)) .data.static_int3 0x0000000010000000 0x4 build/d0f0154f60ed1a9c2083183e7c731846451d2bdb_helloworld.o 0x0000000010000000 static_int3 .data.static_int 0x0000000010000004 0x2 build/d0f0154f60ed1a9c2083183e7c731846451d2bdb_helloworld.o 0x0000000010000004 static_int .data.static_int2 0x0000000010000006 0x1 build/d0f0154f60ed1a9c2083183e7c731846451d2bdb_helloworld.o 0x0000000010000006 static_int2 .data.static_int4 0x0000000010000007 0x1 build/d0f0154f60ed1a9c2083183e7c731846451d2bdb_helloworld.o 0x0000000010000007 static_int4 0x0000000010000008 . = ALIGN (0x4) 0x0000000010000008 _edata = .
.data
. Construction . = ALIGN(4)
aligns the cursor (if there is not enough data in the input sections for full alignment) along the word boundary. The final value is recorded in _edata
._data_load = LOADADDR(.data)
. LOADADDR is a function that returns the section load address. In addition to it there are some more interesting functions: ADDR returns the “virtual” address, SIZEOF - section size in bytes..data
section, 04-hello-world/platform/common/platform.c
: uint32_t *load_addr = &_data_load; for (uint32_t *mem_addr = &_data; mem_addr < &_edata;) { *mem_addr++ = *load_addr++; }
load_addr
to mem_addr
.platform_init
, since this function does not depend on the data in .data
/ .bss
, and its execution will allow you to execute the subsequent code faster, which ultimately will give a performance boost. The downside was the emergence of a separate platform_init_post
, where the global variable is initialized by the system bus frequency value./DISCARD/
- is special, it's a kind of / dev / null linker. All incoming sections will simply be thrown away (as you remember, if a section is not explicitly indicated, it will be automatically added to the appropriate memory area). This section is described more for clarity, as the input sections in the case of ARMv6-M0 are guaranteed to be empty..text
, where two new ones fall: .isr_vector
and .isr_vector_nvic
. Both are wrapped in a KEEP instruction, which prevents the linker from “optimizing” them as superfluous. .isr_vector
contains a common interrupt table for the Cortex-M, which can be platform/common/isr.c
in the file platform/common/isr.c
: __attribute__ ((weak)) void isr_nmi(); __attribute__ ((weak)) void isr_hardfault(); __attribute__ ((weak)) void isr_svcall(); __attribute__ ((weak)) void isr_pendsv(); __attribute__ ((weak)) void isr_systick(); __attribute__ ((section(".isr_vector"))) void (* const isr_vector_table[])(void) = { &_stack_base, main, // Reset isr_nmi, // NMI isr_hardfault, // Hard Fault 0, // CM3 Memory Management Fault 0, // CM3 Bus Fault 0, // CM3 Usage Fault &_boot_checksum, // NXP Checksum code 0, // Reserved 0, // Reserved 0, // Reserved isr_svcall, // SVCall 0, // Reserved for debug 0, // Reserved isr_pendsv, // PendSV isr_systick, // SysTick };
hang
). All of these default handlers perform an infinite loop (although I shoved the debug LED a couple of times in isr_hardfault
I wrote examples for the article), but since they are declared with the weak
attribute, they can be redefined in any other file. For example, timer.c
has its own implementation of isr_systick
, which will fall into the final image.isr_vector_table_nvic
, since it already depends on the specific processor, but the essence remains the same. static volatile uint32_t systick_10ms_ticks = 0; void platform_delay(uint32_t msec) { uint32_t tenms = msec / 10; uint32_t dest_time = systick_10ms_ticks + tenms; while(systick_10ms_ticks < dest_time) { __WFI(); } } // override isr_systick from isr.c void isr_systick(void) { ++systick_10ms_ticks; }
isr_systick
increments the counter by 1. Note that systick_10ms_ticks
declared as volatile , this makes the compiler realize that the value of this variable can (and will) change outside the current context, and it should be re-read each time from RAM (where the interrupt handler will change it).__aeabi_uidiv
, which divides numbers programmatically. This function (and several similar ones) are implemented in the compiler support library: libgcc.a. Unfortunately, our linker knows nothing about it, and we stumble upon an unpleasant error: build/5a3e7023bbfde5552a4ea7cc57c4520e0e458a53_timer.o: In function `platform_delay': timer.c:(.text.platform_delay+0x4): undefined reference to `__aeabi_uidiv'
-nostartfiles
that we have our own initialization code, and through -ffreestanding
, that our application is independent and does not depend on any OS.platform/protoboard/uart.c
extern uint32_t platform_clock; void platform_uart_setup(uint32_t baud_rate) { NVIC_DisableIRQ(UART_IRQn);
First of all, we turn off the interrupt on NVIC in case it was turned on. LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16); LPC_IOCON->PIO1_6 &= ~0x07; LPC_IOCON->PIO1_6 |= 0x01; LPC_IOCON->PIO1_7 &= ~0x07; LPC_IOCON->PIO1_7 |= 0x01;
Next, we will turn on the microcontroller unit, which is responsible for pin setting, and configure them in TXD / RXD UART mode. This code spilled a lot of my blood when I tried to understand why the UART after a reboot does not work. Be careful, sometimes the obvious things are turned off by default! LPC_SYSCON->SYSAHBCLKCTRL |= (1<<12); LPC_SYSCON->UARTCLKDIV = 0x1;
Now you can turn on the UART itself, and at the same time set the input frequency divider. LPC_UART->LCR = 0x83; uint32_t Fdiv = platform_clock // / LPC_SYSCON->SYSAHBCLKDIV // / LPC_SYSCON->UARTCLKDIV // UART / 16 // 16, / baud_rate; // , , LPC_UART->DLM = Fdiv / 256; LPC_UART->DLL = Fdiv % 256; LPC_UART->FDR = 0x00 | (1 << 4) | 0; LPC_UART->LCR = 0x03;
In addition to the classic 8N1 mode, we open access to the output dividers, which set the bitrate. Calculate dividers and write them into registers. For the curious, the formula is in section 13.5.15 of the manual. In addition, it describes an additional divider for even more accurate bodrate. In my tests, the 9580 worked quite well :-) LPC_UART->FCR = 0x07; volatile uint32_t unused = LPC_UART->LSR; while(( LPC_UART->LSR & (0x20|0x40)) != (0x20|0x40) ) ; while( LPC_UART->LSR & 0x01 ) { unused = LPC_UART->RBR; }
We turn on the FIFO, reset it, make sure that there are no strange data in the registers. // NVIC_EnableIRQ(UART_IRQn); // LPC_UART->IER = 0b101;
Enable interrupts to receive (in fact, no). There is no interrupt handler in the example, so there is no need for an interrupt.Source: https://habr.com/ru/post/194816/
All Articles