RTOS

no fast - just Time Deterministic - response time almost constant





Przykłady



Latency:







Priority:




Multitasking using sheduler or multicore.


Linki do instalacji
Driver+CubeMX+Flasher my Dysk
opis na blogu:
https://embedded2018.blogspot.com/2019/12/6-stm32.html

płytka to discovery STM32F411
https://www.st.com/en/microcontrollers-microprocessors/stm32f411.html
  • Mikrokontroler STM32F411VET6:
    • Rdzeń: Cortex M4F
    • Taktowanie 100 MHz 
    • 512 KB Flash
    • 128 kB RAM
    • Obudowa LQFP100
  • Debugger ST-Link/V2 umieszczony na płytce z możliwością pracy jako oddzielne urządzenie z wyjściem SWD
  • Układ zasilany z USB lub z zewnętrznego źródła: 5V/3,3V
  • Na płytce znajdują się także:
    • Akcelerometr 3 osiowy LSM303DLHC (dokumentacja)
    • Żyroskop 3 osiowy: L3GD20 (dokumentacja)
    • Mikrofon MP45DT02 (dokumentacja)
    • Przetwornik DAC audio CS43L22 z sterownikiem klasy D (dokumentacja)
    • Osiem diod LED 
      (cztery do dyspozycji użytkownika)
    • Dwa przyciski (użytkownika oraz reset)
    • Złącze micro-AB USB
    • Wyprowadzenia goldpin dla portów I/O
user manual:
https://www.st.com/resource/en/user_manual/dm00148985-discovery-kit-with-stm32f411ve-mcu-stmicroelectronics.pdf


ST Link to wbudowany debuger, są dwa rodzaje ST-LINK/V2 (starszy) i ST-LINKV2-A(nowszy)

uwaga wymagane uzycie dodatkowego USART-USB do wgrania bootloadera (po podłączonym USB  ST-LINK nie zadziała)

płytka discovery STM32F411
- ustaw BOOT0 i BOOT1
- podłącz do serial2 (RX - PD6 , TX- PD5  ewentualnie  RX - PA3 , TX- PA2)
- ustaw poprawną prędkość com na managerze urządzen windowsa i w programie oraz Parity = Even































Repozytorium
https://github.com/niekiran/MasteringRTOS


vector/AUTOSAR

The AUTOSAR Classic Platform architecture distinguishes on the highest abstraction level between three software layers that run on a microcontroller: Application, Runtime environment (RTE) and Basic software (BSW). The application software layer is mostly hardware independent. Communication between software components and access to BSW happens via RTE, which represents the full interface for applications.
Standardization of interfacess between the application and basic software enables abstraction of the application from the hardware and smooth integration of functional modules.(abstraction make development more flexible)




Layers:
-Application Layer: application software components (SWC) that interact with the runtime environment and is an atomic software code fragment (independent, cannot be subdivided) and cam be mappe to any ECU. (composition - logical encapsulation of software components )
-Runtime environment (RTE): Middleware which abstracts from the network topology for the inter- and intra-ECU information exchange between the application software components and between the Basic Software and the applications. A configurable "middleware is needed for abstraction of the basic software. The RTE is scalable and is generated statically for each ECU.
-Basic Software(BSW): standardized software modules (mostly) without any functional job itself that offers services necessary to run the functional part of the upper software layer.
The BSW is divided in three major layers and complex drivers:
-Services,
-ECU (Electronic Control Unit) abstraction and
-Microcontroller abstraction.

det:



VFB
One essential concept of the Classic Platform is the virtual functional bus (VFB). This virtual bus is an abstract set of RTEs that are not yet deployed to specific ECUs and decouples the applications from the infrastructure. It communicates via dedicated ports, which means that the communication interfaces of the application software must be mapped to these ports. The VFB handles communication within the individual ECU and between ECUs. From an application point of view, no detailed knowledge of lower-level technologies or dependencies is required
Virtual Function Bus 
From a general perspective, the virtual function bus can be described as a system modeling and communication concept. It is logical entity that facilitates the
concept of relocatability within the AUTOSAR software architecture by providing a virtual infrastructure that is independent from any actual underlying
infrastructure and provides all services required for a virtual interaction between
AUTOSAR components.
The virtual function bus is a component interconnection concept that strictly separates the domain of application development and modeling from the infrastructure.
It provides generic communication services that can be consumed by any existing AUTOSAR software component. Although any of these services are virtual, they will then in a later development phase be mapped to actual implemented methods, that are specific for the underlying hardware infrastructure.


Runtime Environment 
In contrast to the purely virtual specification of the communication topology and interaction between components which is done via the virtual function bus, the runtime environment provides an actual implementation for these artifacts. It could also be said that the runtime environment provides an actual representation of the virtual concepts of the VFB for one specific ECU.
Each ECU has its own customized RTE implementation which is generated during the ECU Configuration process of the AUTOSAR methodology. The ECU mapping, i.e. the information about which component will deployed on which ECU, is part of the input of this configuration process



AUTOSAR Runtime Environment and Virtual Function Bus LINK




Application Layer issues

Type of Ports (joined by connectors)

SWC has Ports:
-Interfaces to other components for communication
-Contents:   -data elements assigned to network signals (S/R  -  Sender/Receiver)
                    -operations with arguments (C/S     -   Client/Server)




Sender/Receiver - asynchronous data transmission (no expects response, does not know the number of receivers (between ECUs). simple data types (int,float) and complex (array,record). Port may contain multiple data elements. Data element is "mapped" to a signal if its goes over the bus.
Communication 1:n or n:1





Client/Server - server offers services which are requested by the client. Swc may be as client, as server as both. Serving of operations with arguments. Synch or Asynch. Inter and Intra Ecu communication. Communication 1:1 or  n:1(n clients : 1 serwer )

Synchronous vs Asynchronous:
synch - client is blocked throughout the duration of the call and retains the result in the returning call.
asynch - client does not block during the call and can execute other operations, while the server executes the requested operation in parallel


Inter ECU communications: RPC remote procedure calls / RMI remote method invication


Runnables
Runnable entity is the executable unit of a software component (corresponds to a C function)
SWC contain one or more runnables
Runnabes might be executed when RTE Events occur(data are received or  periodic activation is due)







RTE Layer issues


RTE is the implementation of the VFB and acts as a switchboard between the application, basic software and hardware. 
As middleware integrates individual applications with the basic software. Its organizes the communication and data exchange between them and handles execution of software functions (runnables). The applications no longer needs to know layers below, they using interfaces of rte.
Communication involves sending only certain parameters by passing them to the defined interfaces (described in the model by ports).

RTE offers abstractions of:  OperatingSystem, communication services, hardware interfaces.
RTE is a runtime invironment for runnables (enables applications, triggering runnables via RTE events)
RTE must be regenerated for each ECU after SWC have been mapped to this ECU.


syntax (trigered event expet C/S):
void   <RunnableName>( Rte_Instance  instance)

syntax (trigered by C/S):
void/Std_ReturnType  <RunableName>( Rte_Instance  instance, {paramlist})

example:
void Lightserver(Rte_Instance self, UInt8 keyValue,Boolean active, ResultTypeRef result )


RTE events (triggering of runnables or wakeup from waitpoints(eg. client waits for server response)
-init
-timingevent
-datareceivedevent (S/R)
-datareceivederrorevent (S/R)
-datasendcompletedevent (S/R)
-operationInvokedEvent(C/S)
-asynchronousServerCallReturnsEvent(C/S)
-modeSwitchevent
-modeswitchackevent
Autosar 4.
-externalTriggerOccuredEvent
-InternalTriggerOccuredEvent
-BackgroundEvent


RTE as communication mechanism among SWC and BSW
-rte acts as implementation of the VFB
-S/R
-C/S
-Intra ECU and Inter ECU (via COM)
-RTE implements callbacks of AR-COM



Sender/Receiver    Direct vs Buffered & Queued

Direct - Data is written immediately during execution of the runnable:



Buffered - rte generates copy


Queued:



Client/Server 
-n:1
-client calls server operations
-operation is implemented as runnable of the Server-SWC
-synchronous vs asynchronous calls
-Server runnables run in the:
    - Task context (task switch is made when the runnable is assigned to antoher task,rte saving of data)
    - context of the Client (direct function call - not assigned to any task)

syntax:
Std_ReturnType   RteCall_ ( IN |  IN/OUT   |  OUT  <param_1> , ... )


Client/Server communication makes a new type of protocol necessary, the mapping of the operation is to the protocol ( in S/R mapping is made of a ports data elements to a network signal)



C/S synchronous vs asynchronous


synchronous:
-wait for response of Server (client is blocked)
-result of server via "OUT" parameter of  Rte_Call_<p>_<o>

Server runnable:
Std_ReturnType    GetTime   ( arg...)

RTE Client API:
Std_ReturnType     Rte_Call<Port>_GetTime   (arg... )


asynchronous
-client is not blocked
-client gets Server result by call of Rte_Result (polling or waiting, timeout handling )

RTE Client API:
Std_ReturnType   Rte_Result_<p>_<o> ( [IN/OUT | OUT <param_1>], ... [IN/OUT | OUT <param_2>],... )



Intra SWC communication

Problem: communication between runnables within one SWC, which runs potentialy on different tasks.
(there are no problem in Intra and Inter communication of different SWC (handled by RTE)

atomic acces on task level

Solution:
-Exclusive Areas (EAs)- allows to specify atomic sections for your implementation

Rte_Enter <name> ();
/* protected statements */
Rte_Exit <name> ();

-Inter-runnable variables (IRVs) - variable that allows the communication between two runnables

Rte_IrvWritte_<re>_<name> 
Rte_IrvRead_<re>_<name> 




Interfaces



Interfaces types are described in diagram above:
- Autosar interface

- Standarized interface
- Standarized Autosar interface


Basic software components:
- Services (diagnostics,nvram),flash
- Comunication (CAN,LIN)
- Operating system (based od OSEK OS)
- Micronoctroler abstraction (Digital I/O, AC, EEPROM

ECU specific basic software
- ECU abstraction
- Complex device driver






BSW Layer issues 

Basic Software(BSW): standardized software modules (mostly) without any functional job itself that offers services necessary to run the functional part of the upper software layer.







The BSW is divided in three major layers and complex drivers:
-Services,
-ECU (Electronic Control Unit) abstraction and
-Microcontroller abstraction
-Complex Device Drivers

Tasks:
Services: Services for aplication (diagnost, NVram management, Comunication, Schedule Manager)

ECU: Make higher levels independent of ECU hardware (driver for devices, interfaces for periphery)

Microcontroller: Make layers independent of microcontroller (drivers direct acces to periphery)

Complex Device Drivers: Offer functionality for complex sensors and actuators (Direc access to resources for critical aplications, ex: injection control, baterry management )









Communication:






Memory Services



Hardware I/O




Interfaces








Methodology (75)



in Practice (92) 




Links:

AUTOSAR Runtime Environment and Virtual Function Bus LINK



FOTA: https://www.embitel.com/blog/embedded-blog/understanding-fota-in-the-times-of-connected-cars


Plastic: https://www.plasticscm.com/


Vector:
https://www.vector.com/int/en/products/products-a-z/software/davinci-configurator-pro/

https://www.vector.com/pl/en/products/products-a-z/embedded-components/microsar/

https://assets.vector.com/cms/content/products/microsar/Docs/MICROSAR_ProductInformation_EN.pdf


Debugowanie lauterbach:
https://www.lauterbach.com/frames.html?home.html

CPU w APtv
https://pl.mouser.com/new/infineon/infineon-aurix-tricore-mcu/

6 STM32Fx Microcontroller Custom Bootloader Development "U"

Vector Table dla STM32  bazujących na  Cortex M3/4/7

Info LINK

When you apply power to board (or reset)  the PC of the processor is loaded with the value 0x0000_0000.
step 1 )Then the processor reads the value from this adress. (in Cortex value = 0x0800_0000 ) in to MSP
( MSP - Main Stack Pointer register )
So,  MSP is the main stack pointer ARM Cortex  micro-controller. And that means, the processor first actually initializes the stack pointer register Initial SP Value

step 2) So, after that, processor reads the value of @ memory location 0x0000_0004 into PC.
( PC - points to the (next) instruction to be fetched, not the current instruction being executed ! )
That value is actually the address of the reset handler.(Reset, adres is 0x0800_0004 )
So, now then PC jumps to the reset handler.

 (zamiana 0c0000_0000 na 0x0800_0000 to memory aliasing robiona przez MCU
Pamięć jest zmapowana na flash memory. tak jest tylko w produktach ST w Texas intrument (TI) flash jest od 0x0000_0000 więc nie ma potrzeby mapowania)

Now the reset handler is just a C or assembly function written by you to carry out some of the initialization required for your application.
And from the reset handler you call your main() function of the application. So, control comes to the main()




Boot Configuration of STM32

piny odczytywane podczas resetu.

BOOT0 configuration:

BOOT0 is at level “0” through a pull-down R28. If you want to set BOOT0 at level “1”, it can
be configured by setting a jumper between P2.21 (BOOT0) and P2.22 (VDD).

Note:If you need to set BOOT0 at level "1" continuously, then open SB19 solder bridge to avoid a consumption of 6 mA, while connecting pin P2.21 and P2.22 with a jumper or with a wire.

BOOT1 configuration:
BOOT1 ((PB2) jump withGND


STM32 enter system memory boot mode if the boot pins as follow:

(Boot0 = 1, Boot1= 0)




embedded SRAM adres is 0x2000_0000, czyli w trzeciej opcji adres 0x0000_0000 będzie zmapowany na podany adres SRAM a nie  na flash (0x0800_0000)



Drivery (film rozdzial 4)

ST-Link Driver  (film rozdzial 4)
Driver do link/debugera pasuje do obu płytek
https://www.st.com/en/development-tools/stsw-link009.html

ST-Link firmware Upgrade
https://www.st.com/en/development-tools/stsw-link007.html

LINK z Driver+CubeMX+Flasher my Dysk

ST-Link Utility



 KEIL MDK 5 software instalation  (film rozdział 5)
https://www2.keil.com/mdk5

LINK my dysk



film rozdział 6. Installing OpenSTM32  System-Workbench  
(jeśli nie windows i nie możesz KEIL )




film rozdział 7. STM32CubeMX 
(sysem generujący kod)

LINK z Driver+CubeMX+Flasher my Dysk




film rozdział 8. Exploring STM32 Native Bootloader
1-lokalizacja pinów do botowania i ich ustawienie
2-dokument an2606 LINK
3-uzycie dodatkowego USART-USB do wgrania bootloadera (po podłączonym USB nie pojdzie)
4-program do flashowania
   https://www.st.com/en/development-tools/flasher-stm32.html
LINK z Driver+CubeMX+Flasher my Dysk
   tworzenie pliku hex i wgrywanie wlasnego bootloadera

przykładowy link:
https://scienceprog.com/flashing-programs-to-stm32-embedded-bootloader/


płytka discovery STM32F407/446
- ustaw BOOT0 i BOOT1
- podłącz do serial3 (RX - PB11 , TX- PB10 )

płytka discovery STM32F411
- ustaw BOOT0 i BOOT1
- podłącz do serial2 (RX - PD6 , TX- PD5  ewentualnie  RX - PA3 , TX- PA2)
- ustaw poprawną prędkość com na managerze urządzen windowsa i w programie oraz Parity = Even


mała płytka i jej podłączenie do flasher:
 STM32F103C8T6
- ustaw BOOT0 jako 1
- podłącz do serial1 (TX - pin A9 , RX - pin A10  + GND)
- ustaw poprawną prędkość com na managerze urządzen comX i w programie oraz Parity = Even


film rozdział 9. Custom Bootloader Communication with HOST

Nucleo64:
1-wyjasnienie: USART2(przez wbudowane usb stlinka  ) do wysyłania polecen i otrzymywania odpowiedzi z bootloadera - tu nie potrzebny dodatkowy usb-usart jak w rozdziale 8, 
USART3 (przez dodatkowy usb-uart do pin B10 i B11) do wyswietlania komunikatow debugowych bootloaderai i wgrania go.
(generalnie w sprzęcie ST bootloader wspiera protokoły komunikacji usb,SPI,I2c itp. )

DISCO:
nie ma dostępu do USARTA przez ust stlinka (jest przez mikro usb wpne na płytce)
STM32F411 nie ma USARTA3 ale USART2 umożliwia wgranie bootloadera przez usart


2-Code Placement in Flash

uzyjemy pierwszych 32 kilobajtów naszej pamięci flash żeby wgrać tam bootloader, kolejne sektory flash będą dla kodu naszego programu.



Wbudowany w rom bootloader ST jest pod adresem: System memory. Jeśli wgramy go do Sectora 0 fash to będzie pod adresem 0x800_0000 a kod programu zacznie się od sektora drugiego czyli 0x800_8000

3-Supportet Commands
Bootloader Commands.pdf + prezentacja
pliki LINK

4-Bootloader communication HOST(PeCet)- Bootloader(MCU)
host wysyła komende. bootloader sprawdza crc i odpowiada 2 bajtami (pierwszy ACK lub NACK zależy czy crc ok, i drugi długość nadchodzącej odpowiedzi, następnie wysyła odpowiedz o podanej długości.





film rozdział 10. Boot-Loader Project Creation

1- uzycie STM32CubeMX  do generowanie kodu dla uVision
Nowy Projekt/ w zakładce Board Selector wybraC Vendora STMelectronic, typo of Board

poprawka, zamiast board selector lepiej wybrać sam mikrokontroler np stm32F411VE a nie płytkę !
czysta konfiguracja bez potencjalnych konfliktów

w ProjecrSettings w zakładce Project:
ustaw project location w KeilWorkspace
Toolchain/IDE ustaw MDK-ARM V5
w zakładce Code Generator ustaw Copy only the necessary library files

po stworzeniu projektu w programie:
zakładka  Pinout (zakładki parz góra)

Nucleo64:
.wybrac USART2 (wbudowane usb(virtual com) - do wysyłania comand bootloadera) ustawic Asynchroniczny tryb
.wybrac USART3 (przez usb-usart dongle) ustawic asynchroniczne , klikając z Ctrl w zazielenione piny pokazuje piny zastępcze (można dać drag and drop na nie)

DISCO411:
aktywować dwa usarty przez wewnętrzne dongle uart ?
dostępne usarty to 1,2,6 ale do wyświetlania działać będzie 2 i 6  (przez 1 wgrywa stlink)



.wybrac CRC i dac activated
.wybrac RCC i sprawdzic czy HSE jest disable - external clock

zakładka ClockConfiguration
na wykresie SYSCLK - podaje zegar

menu Project
.wybrac Generate Code
wybrać OpenProject wtedy  otwiera uVision z gotowym projektem:
w uVision:
w folderze Drivers/CMSIS mamy pliki systemowe
w folderze Drivers/STM321xxHAL_Driver mamy drivery
w Application/MDK-ARM mamy plik startup
w folderze Application/User mamy plik main.c gdzie piszemy KOD bootloadera

w uVision
klikamy "Options for Target" (symbol magicznej rozdzki ) zakladka Debug , wybieramy USE: ST-Link Debugger, kliknąć obok "Settings"
otworzy okno i w Download Option (po prawej na dole) wybrać
"Verify Code Download", "Download to Flash" 
OK
OK

w uVision
klikamy "build" (ikonka 2 w kolejnosci od lewej F7)
następnie "Load" (ikonka 6 w kolejnosci , Download Code to Flash memory)

Jeśli OK, sprawdzamy, czy możemy debugować ikonka
Start/Stop debug sesion (Ctrl + F5)



2-analiza wygenerowanego kodu plik main (w uVision w folderze Application/User)
znajdują się w nim różne funkcje inicjalizujące
HAL_Init
MX_GPIO_Init
MX_USART2_UART_Init
MX_USART3_UART_Init
MX_CR_Init
SystemClock_config
(prawy klik/ Go to definition )





film rozdział 11. Boot-Loader UART Testing

1-przy otwartym projekcie w uVision, przełączyć zakładki pod projektem, na  "functions" po rozwinięciu "+"




po wybraniu funkcji  stm..._hal_uart.c szukamy metody HAL_UART_Transmit, klikamy, otwiera plik, nie zmieniamy go! ale możemy podejrzeć jakie argumenty przyjmuje funkcja
możemy użyć w main

HAL_UART_Transmit(&huart2,(uint8_t*)somedata, sizeof(somedata), HAL_MAX_DELAY);

gdzie somedata to wczesniej zadeklarowana tablica charow

char somedata[]="Hello \n"

huart2 to wczesniej zadeklarowany handler do UARTA;
UART_HandleTypeDef huart2;


piszemy pętle opozniajaca:
uint32_t=HAL_Gettick();
while(HAL_GetTick() < (current_tick+500) );

albo
HAL_Delay(1000);


teraz za pomocą putty sprawdzamy na COM na którym jest UART2 czy przychodzi wiadomosc.
(ustawic w putty poprawna wartosc predkosci 115200, w menagerze urzadzen windows tez tak ustawic prędkosc COM uarta)



2-wysyłka za pomocą USART3
ta sama funkcja jak powyżej tylko zamieniamy na uart3
przykład własnej funkcji do wysyłania message na console (4 minuta filmu) printmsg(...)





film rozdział 12. Boot-loader Jumping to User Code

1-Bootloader code flow chart



w zależności czy podczas resetu jest wybrany user button(making that GPIA to ground on nucleo) mamy 2 ścieżki

implementacja:


/* Lets check whether button is pressed or not, if not pressed jump to user application */
  if ( HAL_GPIO_ReadPin(B1_GPIO_Port,B1_Pin) == GPIO_PIN_RESET )
  {
   printmsg("BL_DEBUG_MSG:Button is pressed .. going to BL mode\n\r");

   //we should continue in bootloader mode
   bootloader_uart_read_data();

  }
  else
  {
   printmsg("BL_DEBUG_MSG:Button is not pressed .. executing user app\n");
   
  //jump to user application
  bootloader_jump_to_user_app();

  }


przykład dodania breakpointów i debugowania


2- kod powyzej uzytych funkcji:
void  bootloader_uart_read_data(void)
{
    uint8_t rcv_len=0;

 while(1)
 {
  memset(bl_rx_buffer,0,200);
  //here we will read and decode the commands coming from host
  //first read only one byte from the host , which is the "length" field of the command packet
    HAL_UART_Receive(C_UART,bl_rx_buffer,1,HAL_MAX_DELAY);
  rcv_len= bl_rx_buffer[0];
  HAL_UART_Receive(C_UART,&bl_rx_buffer[1],rcv_len,HAL_MAX_DELAY);
  switch(bl_rx_buffer[1])
  {
            case BL_GET_VER:
                bootloader_handle_getver_cmd(bl_rx_buffer);
                break;
            case BL_GET_HELP:
                bootloader_handle_gethelp_cmd(bl_rx_buffer);
                break;
            case BL_GET_CID:
                bootloader_handle_getcid_cmd(bl_rx_buffer);
                break;
            case BL_GET_RDP_STATUS:
                bootloader_handle_getrdp_cmd(bl_rx_buffer);
                break;
            case BL_GO_TO_ADDR:
                bootloader_handle_go_cmd(bl_rx_buffer);
                break;
            case BL_FLASH_ERASE:
                bootloader_handle_flash_erase_cmd(bl_rx_buffer);
                break;
            case BL_MEM_WRITE:
                bootloader_handle_mem_write_cmd(bl_rx_buffer);
                break;
            case BL_EN_RW_PROTECT:
                bootloader_handle_en_rw_protect(bl_rx_buffer);
                break;
            case BL_MEM_READ:
                bootloader_handle_mem_read(bl_rx_buffer);
                break;
            case BL_READ_SECTOR_P_STATUS:
                bootloader_handle_read_sector_protection_status(bl_rx_buffer);
                break;
            case BL_OTP_READ:
                bootloader_handle_read_otp(bl_rx_buffer);
                break;
      case BL_DIS_R_W_PROTECT:
                bootloader_handle_dis_rw_protect(bl_rx_buffer);
                break;
             default:
                printmsg("BL_DEBUG_MSG:Invalid command code received from host \n");
                break;


  }

 }

}


/*code to jump to user application
 *Here we are assuming FLASH_SECTOR2_BASE_ADDRESS
 *is where the user application is stored
 */
void bootloader_jump_to_user_app(void)
{

   //just a function pointer to hold the address of the reset handler of the user app.
    void (*app_reset_handler)(void);

    printmsg("BL_DEBUG_MSG:bootloader_jump_to_user_app\n");


    // 1. configure the MSP by reading the value from the base address of the sector 2
    uint32_t msp_value = *(volatile uint32_t *)FLASH_SECTOR2_BASE_ADDRESS;
    printmsg("BL_DEBUG_MSG:MSP value : %#x\n",msp_value);

    //This function comes from CMSIS.
    __set_MSP(msp_value);

    //SCB->VTOR = FLASH_SECTOR1_BASE_ADDRESS;

    /* 2. Now fetch the reset handler address of the user application
     * from the location FLASH_SECTOR2_BASE_ADDRESS+4
     */
    uint32_t resethandler_address = *(volatile uint32_t *) (FLASH_SECTOR2_BASE_ADDRESS + 4);

    app_reset_handler = (void*) resethandler_address;

    printmsg("BL_DEBUG_MSG: app reset handler addr : %#x\n",app_reset_handler);

    //3. jump to reset handler of the user application
    app_reset_handler();

}
/* prints formatted string to console over UART */
 void printmsg(char *format,...)
 {
#ifdef BL_DEBUG_MSG_EN
 char str[80];

 /*Extract the the argument list using VA apis */
 va_list args;
 va_start(args, format);
 vsprintf(str, format,args);
 HAL_UART_Transmit(D_UART,(uint8_t *)str, strlen(str),HAL_MAX_DELAY);
 va_end(args);
#endif
 }



VTOR - Vector Table Offset Register
The VTOR indicates the offset of the vector table base address from memory address 0x00000000.



na dole w obszarze BL adresy zaczynają się od 0x0800 0000 i tam na samym początku mamy Vector Table boot loadera.
Powyżej (od sektora 2) zaczyna się pamięć user aplication od adresu 0x0800 8000. żeby przeskoczyć z adresu BL na User-apl trzeba wpisać do VTOR wartość 0x0800 8000







rozdz 13





..........................................

Na podstawie kursu LINK
repo kursu

Plik z repo LINK

LINK uproszczona instrukacja STM32F411
LINK implementacja Bootloadera

KursSTM32F411 a Forbot

Płytki:
STM32 Nucleo and STM32 Discovery boards:

Of course they use the same processor, so it is a good question.
The Nucleo boards are designed to help engineers design and prototype ideas very quickly. You use the online mbed compiler and the wide range of APIs that are part of that community. They also have the standard Arduino connectors which allows you to use the wide range of modules that have been developed. I believe they are also aimed at hobbyist and the education market due to their simplicity and that the USB connection can be treated as a serial device for outputting to a terminal on the PC.
Discovery boards are now starting to support mbed so the line is blurring, but they are designed for Engineers looking to do a more detailed evaluation of the STM32 MCUs. To support this they include the necessary components, like MEMS, microphones, sensors, and LCD displays to demonstrate specific device features and an integrated debugger. They’re great for prototyping the MCU before you include it in your final product design.

The step above that is the full evaluation boards, which gives you access to each of the MCUs pins for a full characterisation.



Sklep:
STM32 Nucleo Botland LINK

STM32 Discovery Botland LINK

wersje STM32F Discovery Link


Dokumentacja STM32F411VE


Kurs
Forbot Kurs dla Nucleo

Forbot Kurs dla Discovery


Zestawy do kursu:
Discovery
Nucleo Podstawowy
Nucleo Rozszerzony


Inne kursy Udemy:
beg::
https://www.udemy.com/course/stm32f4-programming-course-for-beginners/
https://www.udemy.com/course/microcontroller-embedded-c-programming/
adv:
https://www.udemy.com/course/mastering-microcontroller-with-peripheral-driver-development/
https://www.udemy.com/course/embedded-system-programming-on-arm-cortex-m3m4/
https://www.udemy.com/course/microcontroller-programming-stm32-timers-pwm-can-bus-protocol/


GynvaelColdwind-jak działą bootloader Arduino link

5 Bootloadery


Schemat blokowy typowego SOCa:


Typowy układ SOC składa się z procesora i szeregu układów dodatkowych, połączonych z główną jednostką specjalną szyną danych.
Każdy „szanujący się” SOC posiada co najmniej:
- kontroler flash do zapisywania ustawień lub jako główny „dysk”
- kontroler pamięci operacyjnej systemu, czyli DDR
- port szeregowy do diagnostyki – UART
- pamięć wewnętrzną SRAM (ang. static ram) – czasami jej rolę pełni pamięć cache procesora

Na szczególną uwagę zasługuje fakt, że zarówno pamięć operacyjna (DDR), jak i pamięć nieulotna (FLASH) są również układami peryferyjnymi. Tzw. „kości” DDR oraz Flash są montowane osobno na płycie głównej, a w SOCu znajduje się jedynie kontroler. Taka architektura powoduje, że tuż po uruchomieniu procesor nie ma dostępu do żadnej zewnętrznej pamięci.
W istocie każdy z kontrolerów należy osobno zaprogramować. Z tego impasu wychodzi się za pomocą dodatkowej, wewnętrznej pamięci procesora (SRAM, ROM). W pamięci ROM producent umieszcza pierwszy program uruchomieniowy, który dostaje w asyście odrobinę pamięci operacyjnej (SRAM ma nie więcej niż kilkadziesiąt KB).

Uruchamianie takiego schematycznego SOCa będzie przebiegało następująco:
1. Uruchomienie programu Boot0 – pierwszego bootloadera – z pamięci ROM
2. Aktywacja szyny danych
3. Aktywacja pamięci nieulotnej FLASH lub innej, np. karty SD, w celu wczytania kolejnego bootloadera
4. Załadowanie bootloadera Boot1 do pamięci SRAM
5. Uruchomienie programu Boot1
6. Aktywacja peryferiów komunikacyjnych: portu szeregowego (UART), pinów zewnętrznych (świecenie diodą LED)
7. Aktywacja pamięci operacyjnej – kontrolera DDR
8. Załadowanie bootloadera właściwego z pamięci zewnętrznej (FLASH, SATA) do RAM
9. Uruchomienie trzeciego, zapewne już ostatniego bootloadera Boot2. W systemach wbudowanych będzie to U-Boot albo UEFI
10. Aktywacja podsystemów pamięci wirtualnej
11. Aktywacja pamięci podręcznej (cache)
12. Załadowanie jądra systemu operacyjnego z wybranego medium (sieć, dysk, FLASH)


Ograniczona ilość pamięci SRAM wymusza więc podział całego procesu na etapy i tak samo etapami ładuje się coraz więcej kodu. W procesie bootowania, aż do momentu uruchomienia pełnego systemu operacyjnego OS, bierze udział tylko jeden rdzeń procesora.


Pamięć dzieli się na kilka rodzajów:
- RAM/DRAM – pamięć operacyjna, zarówno dynamiczna, jak i statyczna
- ROM – pamięć „wbudowana” lub FLASH – z punktu widzenia procesora jest tylko do odczytu i zawiera głównie firmware bootujący oraz dane konfiguracyjne
- I/O – pamięć do komunikacji z urządzeniami zewnętrznymi, np. portem szeregowym lub kontrolerem dysku. Każdy SOC ma część pamięci „zaszytą” na stałe do tego celu. Taka pamięć ma specjalne właściwości, o których będzie mowa później
- Mapowane I/O – pamięć przeznaczona dla urządzeń dynamicznie konfigurowanych, np. dla kart PCI. Nie wiadomo, jakie karty PCI będą obecne i ile ich będzie, więc przydziałem tej pamięci zajmuje się procesor na etapie uruchamiania systemu (Boot2 lub OS)

Ważne jest, żeby nie mylić pamięci fizycznej z pamięcią używaną w programach działających pod kontrolą systemu operacyjnego. Linuks działający na ARMach z serii ARMv7-A korzysta z pamięci wirtualnej, która całkowicie separuje adresację fizyczną od adresacji używanej przez programy. Nawet sterowniki działające w jądrze Linuksa korzystają z pamięci wirtualnej, która nie ma żadnego związku z adresami fizycznymi. Inaczej rzecz się ma w bootloaderach, które działają na pamięci fizycznej, bo jednostka MMU procesora nie jest włączona tuż po starcie.



Allwinner A20

Popularność tych chinskich SOCów w projektach płytek dla hobbystów wynika z niedużej ceny, niezłej wydajności oraz masy peryferiów, w tym kodeka audio, akceleratora 3D, dekodera TV itd.



Rysunek 4 przedstawia mapę pamięci SOCa A20:
- Pierwsze 44KB to wewnętrzny SRAM służący do bootowania
- Do 1GB są zmapowane na stałe urządzenia SOCa: m. in. te umieszczone w bloczkach na Rysunku 3
- Od 1GB do 3GB jest miejsce na pamięć DDR
- Ostatnie 64KB (od 0xFFFF0000) tradycyjnie dla ARMów są okupowaneprzez Boot ROM, czyli pierwszy program rozruchowy Boot0.

Z Rysunku  nasuwa się wniosek, że system może zaadresować tylko 2GB pamięci DDR, a pierwszy i drugi program rozruchowy, zanim DDR zostanie włączony, będzie miał do dyspozycji maksymalnie 48KB pamięci.


Warto teraz spojrzeć na obszar I/O z bliska: w Tabeli 1 znajduje się wycinek z dokumentacji Allwinnera opisujący urządzenie portu szeregowego. Port szeregowy jest pierwszym urządzeniem, które warto uruchomić, bo w systemie wbudowanym jest to jedyny sposób komunikacji z programem, zanim wstanie pełny OS. UART może też służyć jako urządzenie do debugowania, szczegółowo rejestrując wszystkie kroki wykonywane przez program.



Warto teraz spojrzeć na obszar I/O z bliska: w Tabeli 1 znajduje się wycinek z dokumentacji Allwinnera opisujący urządzenie portu szeregowego. Port szeregowy jest pierwszym urządzeniem, które warto uruchomić, bo w systemie wbudowanym jest to jedyny sposób komunikacji z programem, zanim wstanie pełny OS. UART może też służyć jako urządzenie do debugowania, szczegółowo rejestrując wszystkie kroki wykonywane przez program.
UART Uniwersalny asynchroniczny nadajnik-odbiornik, (od ang. universal asynchronous receiver-transmitter) – układ scalony służący do asynchronicznego przekazywania i odbierania informacji poprzez port szeregowy. UART posiada bufor do tymczasowego gromadzenia danych w przypadku szybkiej transmisji.

Opis urządzenia składa się z adresu bazowego (Base Address) oraz z listy rejestrów z adresami względnymi (offset). W ten sposób kilka modułów tego samego typu można zaadresować jako:

rejestr = adres_bazowy + offset

a wnikliwy obserwator zauważy jeszcze jedną prawidłowość:

rejestr(port) = adres_bazowy + port * 0x400 + offset

Przykładowo: rejestr LCR portu szeregowego nr 1 (liczone od zera) ma adres
0x01C2840C.

Wszystkie porty UART od 0 do 7 (w tabeli pokazane tylko 3) są oddalone o siebie o 1KB, czyli 0x400 szesnastkowo. Każdy rejestr ma opis zawierający pola bitowe wewnątrz rejestru i ich znaczenie.
Bardzo ważne jest zapamiętanie, że większość rejestrów SOCa ma rozmiar całego słowa, czyli w przypadku ARMv7 – 32 bity. Oznacza to, że zapis i odczyt musi zawsze odbywać się w rozmiarze całego rejestru, nawet gdy modyfikacji ulega tylko część jego bitów.

Dostęp do rejestrów, jak widać, zasadniczo różni się od dostępu do pamięci, którą na ARMie można adresować także bajtowo. Ponadto zapis i odczyt może mieć dodatkowe efekty uboczne, co wyraźnie widać na przykładzie pary rejestrów THR i RBR (nie opisany w Tabeli 1). Oba zajmują ten sam adres (offset 0), ale jeden jest tylko do zapisu (TBR), a drugi tylko do odczytu (RBR), więc w zależności od typu transakcji na szynie danych (odczyt lub zapis) aktywuje się jeden lub drugi rejestr. Informacje o rejestrach UART przydadzą się do napisania programu „Hello World”.


C

Oprogramowanie niskopoziomowe typu firmware, bootloader itp. tradycyjnie pisze się w mieszaninie asemblera i C. Dzisiaj ilość asemblera jest w zasadzie znikoma, bo zawsze można posłużyć się wstawką asemblerową. Język C nadaje się idealnie do tego typu zadania, bo jest dość prosto przenoszony na kod maszynowy, łatwo przewidzieć postać tego kodu i użycie pamięci, a kod języka C nie potrzebuje żadnego dodatkowego otoczenia (runtime), tzn. nie musi korzystać z bibliotek systemowych i może być całkowicie samowystarczalny, co zostanie pokazane poniżej.

static int x[1];
static int y[1] = {0xbabababa};

void foo(const char *s,int *i, int *j) {}

void _start(void) 
{
  foo("Hello World", y, x);
}


Te kilka linijek wystarczy jednak, aby zademonstrować, jak programy w C funkcjonują w pamięci. Każdy program w C składa się z następujących części:

- kod maszynowy zdefiniowanych funkcji, umownie    .text
  obszar danych:
- stałych, read only data:                                                .rodata
- zainicjalizowanych:                                                      .data
- niezainicjalizowanych, czyli wyzerowanych:              .bss

- sterty (albo dostępnej pamięci)
- stosu



Te obszary programu nazywa się sekcjami lub segmentami (sekcjami, gdy są w pliku, segmentami – w pamięci). Tablica x na powyższym Listingu  to zmienna bez wartości początkowej, czyli będzie wypełniona zerami i trafi do sekcji .bss, tablica y ma wartość początkową, więc trafi do sekcji .data. Stała łańcuchowa „Hello World” trafi do .rodata, bo jest stałą.



Na Listingu 2 pokazana jest kompilacja i wydruk zrzutu obrazu programu.
Niektóre fragmenty zostały pominięte i wykropkowane (kolor szary), a istotne wartości zaznaczone na czerwono.
Obecne są wszystkie 4 sekcje oraz ich rozmiar i adres w pamięci. Ten ostatni jest bardzo ważny: programy w języku C posługują się adresami bezwzględnymi, a każda sekcja musi mieć określony adres i rozmiar. Listing zawiera też zawartość sekcji .rodata i .data.
W sekcji danych wyraźnie widać wartość (0xbabababa) zmiennej y, a stała łańcuchowa „Hello World” wypełnia .rodata.


Listining 2:

$ arm-linux-gnueabihf-gcc -O0 -c hello.c
$ arm-linux-gnueabihf-ld -o hello hello.o
$ arm-linux-gnueabihf-objdump -f -s -h hello
hello: file format elf32-littlearm
architecture: arm, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x000080b8

Sections:
Idx Name Size VMA LMA File off Algn

0 .text      0000004c       00008094  00008094  00000094   2**2
                CONTENTS, ALLOC,    LOAD,      READONLY, CODE
1 .rodata  0000000c       000080e0   000080e0  000000e0   2**2
                CONTENTS, ALLOC,    LOAD,      READONLY, DATA
2 .data      00000004      000100ec   000100ec   000000ec    2**2
                CONTENTS, ALLOC,    LOAD,       DATA
3 .bss       00000004       000100f0   000100f0   000000f0     2**2
                ALLOC
<...>
Contents of section .text:
<...>
Contents of section .rodata:
   80e0 48656c6c 6f20576f 726c6400     Hello World.
Contents of section .data:
   100ec babababa                                           ....
<...>


Na początku powyższego listingu pojawia się adres startowy programu:
0x000080b8.
Jest to adres funkcji _start(). Nigdzie nie ma natomiast wzmianki o stosie. Kompilator zakłada, że stos jest już przygotowany w chwili rozpoczęcia programu, gdyż zajmuje się tym jądro systemu operacyjnego, gdy ładuje plik wykonywalny.
Wynika stąd, że zanim uruchomi się program w C bez pomocy OS, należy „ręcznie” przygotować stos.
Dlaczego kod zaczyna się od _start(), a nie main()?
Funkcja main() wykonuje się dopiero po inicjalizacji biblioteki standardowej C (libc na Linuksie, msvcrt na Windows) i funkcja _start() jest jej częścią. Program hello.c został jednak skompilowany bez żadnej biblioteki, jest całkowicie samowystarczalny i niemal gotowy do uruchomienia w środowisku bare metal.
Uważny czytelnik zauważy, że program został skompilowany jako program w C pod Linuksa, więc jak to możliwe, że można go uruchomić bez systemu? Otóż na maszynie ARM kod maszynowy zawsze ma tę samą formę na wszystkich etapach uruchamiania systemu, co stanowi standard w nowoczesnych architekturach (ale nie w x86). Różnica polega jedynie na braku wywołań systemowych i bibliotek. Język C prawie zawsze gwarantuje (wyjątkiem jest m.in. thread local storage w C99), że żadne wywołanie systemowe lub użycie biblioteki zewnętrznej nie odbędzie się bez jawnego udziału programisty.
Skoro program jest samowystarczalny, to czego jeszcze brakuje?



Skrypt linkera

To, co wyróżnia niskopoziomowy firmware od „zwykłych” programów w C, to przede wszystkim kontrola nad położeniem programu w pamięci. Adresacja sekcji na Listingu 2 jest nieciągła i nie można jej zmienić. Tymczasem bootloader musi wystartować z pamięci FLASH, a potem przenieść się do SRAM, bo tylko ta pamięć jest dostępna na początku. Podczas bootowania używane są adresy fizyczne pamięci, które są ustalone przez SOC. W celu szczegółowego rozmieszczenia sekcji w pamięci fizycznej należy posłużyć się skryptem linkera:

Listining 3

ENTRY(_start)
SECTIONS
{
 . = 0x2000
 .text : 
 {
  hello.o (.text)
  *(.text)
 }
 .rodata : { *(.rodata) }
 .data : { *(.data) }
 .bss : { *(.bss COMMON) }
}

Listing 3 pokazuje schematyczny skrypt linkera, który umieszcza wszystkie sekcje programu po kolei w pamięci, począwszy od adresu 0x2000. Pamięć programu zaczyna się od sekcji kodu modułu hello.o. Skrypty linkera pozwalają na szczegółowe zaadresowanie wszystkich sekcji programu z rozróżnieniem na osobne jednostki kompilacji (różne pliki z kodem).



Anatomia bootowania: Hello World na Bare Metal

Mając wszystkie elementy układanki, trzeba przystąpić do realizacji konkretnego projektu. Będzie to program rozruchowy 2-go stopnia, który wypisuje na port szeregowy „Hello World”.
W układzie Allwinner A20 pierwszy program rozruchowy jest dostarczony przez producenta w pamięci ROM i nie podlega wymianie. Schemat działania programu Boot0 opisany jest poniżej.

Zadaniem Boot0 jest znalezienie kolejnego programu rozruchowego i przekazanie mu kontroli, lub też przejście w specjalny tryb diagnostyczny.
Po kolei sprawdzane są:

1. Stan PINu diagnostycznego: gdy włączony, włącza się tryb ładowania kodu przez USB, tzw. tryb FEL.*
2. Karta SD**
3. Pamięć NAND Flash
4. Karta SD nr 2
5. SPI Flash ***
6. Gdy wszystko zawiodło, tryb FEL


*** (SPI is a simple interface for interfacing to slow peripherals. It is somewhat like I2C, but has a chip select line rather than an addressing scheme. Which, plausibly, makes it simpler.
Flash memory is a kind of non-volatile memory much used for storing programs for simple microprocessors.
SPI Flash is simply the cheapest, simplest way of building off-chip non volatile memory at the moment. )


**Karta SD musi posiadać następujący format:
Obraz programu Boot1 jest pod adresem 8KB na karcie SD i zawiera specjalny nagłówek wraz z sumą kontrolną. W typowym zastosowaniu, tj. uruchomienie Linuksa, rolę programu Boot1 przejmuje tzw. U-Boot SPL (Secondary Program Loader), który ładuje U-Boota właściwego po inicjalizacji pamięci DDR.
Pamięć Flash musi być podzielona na partycje, a pierwsza partycja powinna się nazywać boot i zawierać obraz U-Boota.


* FEL is a low-level subroutine contained in the BootROM on Allwinner devices. It is used for initial programming and recovery of devices using USB. LINK



FEL - tryb diagnostyczny USB

Wszystkie te procedury są użyteczne dla ostatecznej, działającej wersji bootloadera, ale nie są wygodne przy eksperymentowaniu, a w szczególności w naprawianiu zepsutego, niedziałającego firmware. Dlatego najlepiej zacząć od trybu USB. Allwinner Technologies w swojej uprzejmości umieścił na Githubie kod źródłowy programatora diagnostycznego, który potrafi ożywić
SOCa z innego komputera przez port USB. Program nazywa się FEL i ma następujące możliwości:
- załadowanie pliku pod adres fizyczny pamięci
- zapisanie wycinka pamięci do pliku
- wykonanie programu z wybranego adresu fizycznego i (opcjonalnie) powrót do trybu diagnostycznego
- hexdump z wybranego adresu fizycznego, również rejestrów urządzeń!
- wypełnienie wybranej pamięci pewną wartością, np. zerami, ale także zapis do rejestru!

Narzędzie FEL pozwala więc na szeroko zakrojoną modyfikację stanu procesora, i to wielokrotnie, bez konieczności resetowania systemu, bo można powrócić do trybu FEL i załadować kolejny program. Taka możliwość „podglądania” i „próbowania” jest bardzo przydatna na początku przygody z SOCem i może – przy odrobinie sprytu – zastąpić sprzętowy debugger JTAG.



Plan działania

Program „Hello World” na środowisko bare metal będzie działał następująco:
1. Po włączeniu zasilania procesor skoczy do adresu 0xFFFF0000 i uruchomiony zostanie tryb diagnostyczny (płyta Cubieboard 2 ma do tego specjalny przycisk)
2. Obraz programu zostanie skopiowany do pamięci SRAM pod adres 0x2000 (8 KB)
3. Zostanie wykonane polecenie skoku do adresu początkowego programu 0x2000
4. Najpierw zostanie uruchomiona funkcja rozruchowa _start(), napisana w asemblerze, która przygotuje środowisko dla programu w C
5. Funkcja _start() wywoła funkcję main()
6. Funkcja main() wywoła procedurę uruchomienia portu szeregowego UART0
7. Po inicjalizacji UART0, zostanie wypisany napis „Hello World”
8. Nastąpi powrót do Boot ROMu



Na Rysunku  znajduje się mapa pamięci programu, tuż po załadowaniu przez Boot ROM do pamięci SRAM. Początek obszaru okupuje moduł start.o, napisany w asemblerze. Potem kolejność jest już dowolna, bo moduł start.o skonfiguruje środowisko uruchomieniowe dla C. Oprócz opisanych w poprzednim rozdziale segmentów, jest zarezerwowane miejsce na stos. To, czy stos zostanie jawnie wkomponowany w mapę pamięci czy ustalony „ręcznie” na jakiś arbitralnie wybrany obszar, zależy tylko od widzimisię programisty.
Segmenty zaznaczone na zielono to te, które są częścią binarnego obrazu programu.
Segmenty szare to te, które należy przygotować po załadowaniu obrazu (pliku) programu do pamięci. Normalnie tą częścią, czyli alokacją segmentu .bss i stosu, zajmuje się system operacyjny. Nic jednak nie stoi na przeszkodzie, żeby zrobić to w procedurze _start(), bo rozmiary obu są znane na etapie działania programu.


Szybki kurs asemblera ARM

W chwili wejścia do main() procesor znajduje się w stanie bliżej nieokreślonym, gdyż nie wiadomo, co znajduje się w rejestrach, na stosie itd.
Bootloadery często zakładają, że „nie wiedzą”, co robił poprzednik, i „czyszczą” cały stan procesora.
Na pierwszy ogień idzie rejestr specjalny CPSR – Current Program Status Register. Jest to pole bitowe, które przechowuje tryb procesora, maskę przerwań i inne informacje.
Kolejnym krokiem jest skonfigurowanie stosu i zapisanie na nim niektórych rejestrów. Będą one przywrócone na samym końcu, żeby program w Boot ROMie mógł poprawnie pracować.
Zmienna stack_top zdefiniowana jest w skrypcie linkera i wskazuje na najwyższy adres obszaru stosu. Zwyczajowo w systemach komputerowych stos zapełniany jest przez zmniejszanie wartości wskaźnika stosu, czyli rośnie „w dół” adresów. Taka organizacja sprzyja wykrywaniu błędów przepełnienia stosu (stack overflow).
więcej: (patrz artykuł Programista 35 str 31)



Debugowanie

Diagnoza problemów w programie rozruchowym może być wyzwaniem samym w sobie. Brak sprzętowego debuggera (i każdego innego też) powoduje, że pierwsze kroki bywają trudne, ale są to trudności do przeskoczenia.
Błąd na tym etapie zazwyczaj kończy się „zamrożeniem” systemu, tzn. brakiem komunikatów i brakiem odpowiedzi na bodźce. Z tego nasuwa się oczywisty wniosek, że najlepszą diagnozą jest gęste usianie kodu komunikatami diagnostycznymi, a ostatni pojawiający się komunikat będzie stanowił wskazówkę, gdzie wystąpił problem.

Sposoby debugowania systemów wbudowanych można podsumować regułą „im prościej, tym lepiej”, bo każda komplikacja może wprowadzić kolejny błąd.
Ze znanych metod można wymienić:
- UART – można powiedzieć, że to jest luksusowy debugger
- Diody (tak tak – debugowanie przez miganie diodami:)
- Inne PINy – jeśli jest pod ręką oscyloskop albo analizator logiczny

(więcej o debugowaniu patrz Programista  82 str 24) 



Obsługa portu szeregowego

#define readl(a) (*(volatile unsigned int *)(a))
#define writel(v,a) (*(volatile unsigned int *)(a) = (v))
#define UART_PORT 0
#define UART_BASE 0x01C28000
#define UART_THR (UART_BASE + 0x00)
#define UART_RBR (UART_BASE + 0x00)
#define UART_DLL (UART_BASE + 0x00)
#define UART_DLH (UART_BASE + 0x04)
#define UART_LCR (UART_BASE + 0x0c)
#define UART_LSR (UART_BASE + 0x14)
#define UART_LCR_DLAB (0x1 << 7)
#define UART_LSR_TEMT (0x1 << 6)
#define BAUD_115200 13 /*clock 24M / (16*115200) */
#define NO_PARITY (0)
#define ONE_STOP_BIT (0)
#define DAT_LEN_8_BITS (3)
#define LC_8_N_1 (NO_PARITY << 3 \
| ONE_STOP_BIT << 2 | DAT_LEN_8_BITS)
#include <stdint.h>

volatile uint32_t debug = 0xdeadbeef;

void delay(unsigned cycles)
{
 unsigned i;
  for (i = 0; i < cycles; i++)
    __asm volatile ("nop"::);
}

void uart_init(void)
{
 volatile uint32_t *uart_clock =
  (uint32_t *) 0x01C2006C;
 volatile uint32_t *PB2 =
  (uint32_t *) 0x01C2082C;
 volatile uint32_t *PB_PULL1 =
  (uint32_t *) 0x01C20844;

 /* Włączenie zegara dla urządzenia UART na szynie APB */
 *uart_clock &=
   ~(1 << (16 + UART_PORT));
 /* Trzeba odczekać parę cykli zegara */
 delay(100);
 *uart_clock |=
   (1 << (16 + UART_PORT));

 /* Ustaw piny 22 i 23 portu B 
  na UART0 RX & UART0_TX */
 *PB2 |= 2 << 28 | 2 << 24;

 /* Piny muszą być wysterowane jako pull-up
  (wartość 2) dla pinów 22 i 23 portu B */
 *PB_PULL1 |=
  (2 << ((22 - 16) * 2)) |
  (2 << ((23 - 16) * 2));

 /* Aktywuj konfigurację szybkości transmisji */
 writel(UART_LCR_DLAB, UART_LCR);

 /* Ustaw prędkość portu na 115200 baud */
 writel(0, UART_DLH);
 writel(BAUD_115200, UART_DLL);

 /* Wyłącz konf. trasmisji i ustaw:
  - brak parzystości
  - 1 bit stopu
  - długość słowa 8 bitów */
  writel(LC_8_N_1, UART_LCR);
 /* Czekaj 100 cykli */
  delay(100);
}

#define TX_READY (readl(UART_LSR) & UART_LSR_TEMT)

void uart_putc(char c)
{
 while (!TX_READY);
 writel(c, UART_THR);
}

void uart_puts(const char *s)
{
 while (*s)
  uart_putc(*s++);
}
v
oid main(void)
{
 debug = 0x44444444;
 uart_init();
 uart_puts("Hello world!\n\r");
 debug = 0x55555555;
}


Na Listingu  przedstawiona jest całość programu w C:
- definicje i makra dotyczące portu UART,
- prosta funkcja opóźniająca delay()
- funkcja inicjalizacji urządzenia UART0 uart_init()
- funkcja wypisująca pojedynczy znak: uart_putc()
- funkcja drukująca łańcuch znakowy
- funkcja main(), która posyła „Hello World” na port szeregowy

Kod obsługi UARTa wzorowany jest na module early_print.c z projektu U-Boot. Jest to popularny UART z rodziny 16550, więc na jego temat jest masę materiałów w sieci, łącznie z gotowym kodem. Definicje i makra nie wymagają dużego komentarza, poza tym, że wartości wzięte są z dokumentacji Allinnera A20. Wartość baud rate dla UARTa wynika z odszukania w dokumentacji domyślnego zegara dla portu szeregowego (24MHz) oraz wzoru na wartość dzielnika zegara.
Na uwagę zasługuje przygotowanie do uruchomienia portu. Na początku trzeba zacząć od włączenia taktowania na szynie do układu UART, bo przy starcie wszystkie zewnętrzne szyny do peryferiów są wyłączone. Potem trzeba skonfigurować odpowiednie piny, które domyślnie pełnią inną rolę. Jest to częsta bolączka, gdy urządzeń jest więcej niż wyprowadzeń („nóżek”) na SOCu i każdym pinem trzeba się dzielić.
Inicjalizacja samego układu UART przebiega standardowo, tj. trzeba ustawić prędkość (rejestry DLL, DLH) i format transmisji (rejestr LCR). Potem wystarczy tylko trochę poczekać za pomocą funkcji delay().
Port szeregowy działa tu w trybie pollingowym, tj. bez przerwań. Wysłanie pojedynczego znaku realizuje się jako zapis do rejestru THR. Żeby uniknąć nadpisania niewysłanego znaku, trzeba  poczekać, aż rejestr statusu (LSR) będziemiał włączoną flagę transmitter empty (TEMT).



Kompilacja

Skrypt linkera:

ENTRY(_start)
SECTIONS
{
  . = 0x2000; /* Zaczynaj od 0x2000 */
  .text :
  {
   start.o (.text)
   *(.text)
  }
 .rodata : { *(.rodata) }
 .data : { *(.data) }

 .stack : {
  . = ALIGN(8); /* Stos wyrównany do 8 */
  . = . + 0x400; /* 1KB */
  stack_top = .;
 }

 .bss ALIGN(4) : { /* .bss wyrównany do 4 */
  bss_start = .;
   *(.bss COMMON)
   bss_end = ALIGN(4)
  /* .bss kończy się wyrównaniem do 4,
  dzięki czemu rozmiar jest wielokrotnością 4 */; 
 }
}


Skrypt linkera, scalający cały program, znajduje się na Listingu 6. Rozmieszcza on poszczególne sekcje obok siebie ze szczególną dbałością o adresację pierwszej sekcji. Początkowy segment, jak ustalono w planie, musi zaczynać się od adresu 0x2000, a pierwszym modułem w sekcji kodu ma
być start.o. To gwarantuje, że symbol początkowy _start, wyznaczony za pomocą funkcji ENTRY na Listingu 6, będzie miał adres 0x2000.
Segmenty stosu i danych niezainicjalizowanych znajdują się w skrypcie, choć nie są częścią binarnego obrazu programu. Segment stosu może na wejściu zawierać losowe wartości, a .bss ma być wyzerowany, w związku z tym oba nie zawierają żadnych istotnych wartości, oprócz swojego rozmiaru.
Na Listingu  został użyty symbol stack_top, który jest zdefiniowany w skrypcie. W ten sposób na etapie linkowania programu wartość stack_top jest prawidłowo ustalana. Tak samo można ustalić granice segmentu .bss, żeby potem go wyzerować. Funkcja ALIGN służy wyrównaniu adresacji do podanej wartości. Standard ARM EABI określa wyrównanie stosu na 4 lub 8 bajtów, więc najlepiej wyrównać do 8. Wyrównanie sekcji danych nie jest określone, ale 4 bajty dla .bss pozwala na wygodne wyzerowanie całości. Kod zerujący został pominięty na listingach, bo sekcja .bss i tak jest
pusta, a sam kod to zwykłe zerowanie tablicy.



Kompilacja i uruchomienie projektu

$ arm-linux-gnueabihf-gcc -g -marm -c -o main.o main.c
$ arm-linux-gnueabihf-ld -T image-sram.lds main.o start.o -o image.elf
$ arm-linux-gnueabihf-objcopy -O binary image.elf image.bin
$ fel write 0x2000 image.bin
$ fel exe 0x2000

Listing  pokazuje sesję kompilacji i uruchomienia programu. Kompilacja na Linuksie odbywa się do postaci ELF, więc plik trzeba przekonwertować na postać „binarną”, to jest pozbawioną wszelkich metadanych i informacji dodatkowych, z których korzystają systemy operacyjne. Listing 2 programu
objdump bazuje właśnie na metadanych pliku ELF, ale ten format jest bezużyteczny
na etapie bootowania.
Pozyskany plik binarny image.bin ładuje się do pamięci SOCa za pomocą narzędzia FEL, o którym była wcześniej mowa. Po wykonaniu komendy “fel exe” na terminalu portu szeregowego widać upragniony napis.

artykuł z Programista 35 str 28






Linki:
Youtube: Lecture 13: Booting Process
Udemykurs

Artykuły:
Bootloader:                               
Bootloader!                               Pr35  str28
własny RTOS                            Pr42  str14
Linux na nowej plafrotmie       Pr33  str20
botloader1                                 Pr82 str 16
wlasny Linux PI                        Pr84 str 38

LinuxJądro                   - LinuxMagazyn 187 str 32

GDB and OpenOCD
http://openocd.org/doc/html/GDB-and-OpenOCD.html

Debugowanie za pomocą openocd+gdb+stlinkv2
https://phabricator.hskrk.pl/w/poradniki/stm32/debugging/

JTAG,OpenOCD,BDM and GDB
http://rts.lab.asu.edu/web_438/project_final/Talk%205%20JTAG,OpenOCD,BDM%20and%20GDB.pdf

...
The Black Magic Probe is a modern, in-application debugging tool for embedded microprocessors. ... It is able to control and examine the state of the target microprocessor using a JTAG or Serial Wire Debugging (SWD) port and on-chip debug logic provided by the microprocessor.
...