STM32 MikroC Custom Bootloader Development

This article is a continuation of the Series on STM32 MikroC Bootloader Development and carries the discussion on Bootloader design and implementation. The aim of this series is to provide easy and practical examples that anyone can understand. This post is Simple STM32 MikroC Custom Bootloader Development – Bootloader Tutorial Part 2. In this post, we are going to modify the bootloader that we have gotten from the MikroC Pro for ARM.

Prerequisites

In this simple STM32 MikroC bootloader example, we will be using the concepts which have been explained already in the below-given tutorials. So I would request you to go through those tutorials first if you are not familiar with those topics.

If you want to develop the bootloader for STM32 with STM32CubeIDE, please refer to these tutorials.

Hardware Required

In this demo, we have used the below components and software.

  • STM32F411 Microcontroller (You can use any other stm32 controller. But need to set the proper flash address)
  • MikroC Pro for ARM IDE

STM32 MikroC Custom Bootloader Development

We are assuming that you knew about the bootloader. If we are developing the bootloader using STM32CubeIDE, then we can easily develop it. Because we can change the interrupt vector table. So, the bootloader will be having different vector table, and the application or firmware will be having different bootloader. Once we jump from the bootloader to the application, we can change the vector table offset to the application or firmware vector table.

But in the MikroC Pro for ARM, we can’t change the address of the vector table. So, it will be always placed in the 0x08000000. Therefore, developing the bootloader is different in the MikroC Pro for ARM.

STM32 MikroC Pro for ARM Bootloader

In this post, we are going to take the bootloader source code from the MikroC. Once we flash the bootloader, we will have to use the MikroBootloader PC application to update the firmware or application.

MikroElekronika Bootloader MikroC Bootloader
MikroElekronika Bootloader

Original MikroC Bootloader

MikroE has provided the example bootloader for the STM32F051R8 microcontroller. The code is given below.

USB_UART_BOOTLOADER.c

/*
 * Project name:
     USB_UART_Bootloader
 * Copyright:
     (c) MikroElektronika, 2018.
 * Revision History:
     - initial release;
 * Description:
     MCU flash. It is used, instead of programming tools, to
     load real program code into the MCU. Real program code can
     be passed from PC to bootloader by specific PC application
     (Bootloader Tool) over numerous communication interfaces.

     This bootloader communicates with PC over UART inteface
     by using mikroE bootloader protocol. It is designed to
     work in conjuction with mikroE's 'mikroBootloader'
     PC application.

 * Test configuration:
     MCU:             STM32F051R8
                      https://www.st.com/resource/en/datasheet/stm32f051r8.pdf
     Dev.Board:       MINI-M0 for STM32
                      https://www.mikroe.com/mini-stm32f0
     Oscillator:      48.000000
     Ext. Modules:    None
     SW:              mikroC PRO for ARM v6.0.0
                      https://www.mikroe.com/mikroc-arm
 * NOTES:
     - It's recommended not to alter the start address of the main().
       Otherwise, less space in ROM will be available for the
       application being bootloaded.
*/
////////////////////////////////////////////////////////////////////////////////
#include <built_in.h>
////////////////////////////////////////////////////////////////////////////////
#pragma orgall 0xEC00
#define BOOTLOADER_START_ADDR   0xEC00
#define START_PROGRAM_ADDR      0xFC00
////////////////////////////////////////////////////////////////////////////////
static char block[1024];
////////////////////////////////////////////////////////////////////////////////
void Start_Program() org START_PROGRAM_ADDR{

}
////////////////////////////////////////////////////////////////////////////////
unsigned short UART_Write_Loop(char send, char receive){
  unsigned int rslt = 0;

  while(1){
    Delay_5ms();
    UART_Write(send);
    Delay_5ms();

    rslt++;
    if (rslt == 0x0200)
      return 0;
    if(UART_Data_Ready()) {
      if(UART_Read() == receive)
        return 1;
    }
  }
}
////////////////////////////////////////////////////////////////////////////////
void FLASH_EraseWritePage(unsigned long address) {
  unsigned int i = 0;
  unsigned int dataToWrite;

  FLASH_ErasePage(address);

  for (i = 0; i < 512; i++)
  {
    dataToWrite = block[i * 2] | (block[i * 2 + 1] << 8);
    FLASH_Write_HalfWord(address + i*2, dataToWrite);
  }
}
////////////////////////////////////////////////////////////////////////////////
void Write_Begin(){
  unsigned int i;
  unsigned long* ptr;
  unsigned char appResetVector[16];
  unsigned long arm_m0_inst;
  unsigned int dataToWrite;

  //LDR R0, PC+X
  arm_m0_inst = 0x4800 + 1;

  appResetVector[0] = arm_m0_inst;
  appResetVector[1] = arm_m0_inst >> 8;

  //MOV SP, R0
  arm_m0_inst = 0x4685;

  appResetVector[2] = arm_m0_inst;
  appResetVector[3] = arm_m0_inst >> 8;

  //LDR R0, PC+Y
  arm_m0_inst = 0x4800 + 1;

  appResetVector[4] = arm_m0_inst;
  appResetVector[5] = arm_m0_inst >> 8;

  //BX R0
  arm_m0_inst = 0x4700;
  appResetVector[6] = arm_m0_inst;
  appResetVector[7] = arm_m0_inst >> 8;

  //SP
  appResetVector[8] = block[0];
  appResetVector[9] = block[1];
  appResetVector[10] = block[2];
  appResetVector[11] = block[3];

  //PC
  appResetVector[12] = block[4];
  appResetVector[13] = block[5];
  appResetVector[14] = block[6];
  appResetVector[15] = block[7];

  FLASH_ErasePage(START_PROGRAM_ADDR);

  for (i = 0; i < 8; i++)
  {
    dataToWrite = appResetVector[i * 2] | (appResetVector[i * 2 + 1] << 8);
    FLASH_Write_HalfWord(START_PROGRAM_ADDR + i*2, dataToWrite);
  }

  ptr = (unsigned long*)0x00000000;
  block[0] = LoWord(*ptr);
  block[1] = LoWord(*ptr) >> 8;
  block[2] = HiWord(*ptr);
  block[3] = HiWord(*ptr) >> 8;

  ptr++;

  block[4] = LoWord(*ptr);
  block[5] = LoWord(*ptr) >> 8;
  block[6] = HiWord(*ptr);
  block[7] = HiWord(*ptr) >> 8;
}
////////////////////////////////////////////////////////////////////////////////
void Start_Bootload(){
  unsigned int i = 0;
  char xx, yy;
  long j = 0;

  while (1) {
    if (i == 1024) {
      //--- If 256 words (1024 bytes) recieved then write to flash
      if (!j)
        Write_Begin();
      if (j < BOOTLOADER_START_ADDR) {
        FLASH_EraseWritePage(j);
      }

      i = 0;
      j += 0x400;
    }
    //--- Ask for yy
    UART_Write('y');
    while (!UART_Data_Ready()) ;
    //--- Read yy
    yy = UART_Read();
    //--- Ask for xx
    UART_Write('x');
    while (!UART_Data_Ready()) ;
    //--- Read xx
    xx = UART_Read();
    //--- Save xxyy in block[i]
    block[i++] = yy;
    block[i++] = xx;
  }
}
////////////////////////////////////////////////////////////////////////////////
void main() org BOOTLOADER_START_ADDR{
// Main program
  UART1_Init(115200);
  Delay_100ms();

  if (UART_Write_Loop('g','r')) {     // Send 'g' for ~5 sec, if 'r'
    Start_Bootload();                 //   received start bootload
  }
  else {
    Start_Program();                  //   else start program
  }
}
////////////////////////////////////////////////////////////////////////////////

Custom MikroC Bootloader Development

The above code won’t work for STM32F411. Because that STM32F0 has a different flash address. And also we don’t want to use their PC application. Instead, we are going to use our customized PC application. So, we need to modify the above bootloader code.

In the code, we are going to make two major changes.

  1. Flash address and the corresponding flash access APIs.
  2. Sending the Firmware or application size to the bootloader during the firmware update.

STM32 MikroC Bootloader Design

 Please check the design and flow chart of the bootloader.

The bootloader process flow steps description is the following.

Block 1

The bootloader starts when the microcontroller power-up or resets.

Block 2

Transmit the character “g” through the USART to the host. In this example, we have used UART 1 for this data transfer.

Block 3

Check that we have received the character “r” from the host.

Block 4

Check that we have reached the maximum attempt. In this example, we set that count as 100.

  • If it reached the maximum attempt, then go to Block 16.
  • If it is not reached the maximum attempt, then go to Block 2.

Block 5

Till now we have got a response from the Host. Now we will receive the application size from the host. Now transmit the character ‘y’ to the host. Then go to Block 6.

Block 6

Receive the 1-byte data from the host. Then go to Block 7.

Block 7

Now transmit the character “x” to the host. Then go to Block 8.

Block 8

Receive the 1-byte data from the host. Now we can combine the bytes that we have received from Block 6 and Block 8 to get the application size. Then go to Block 9.

Block 9

Now we can receive the application data from the host. Transmit the character ‘y’ to the host. Then go to Block 10.

Block 10

Receive the 1-byte data from the host. Then go to Block 11.

Block 11

Now transmit the character “x” to the host. Then go to Block 8.

Block 12

Receive the 1-byte data from the host. Go to Block 13.

Block 13

Check that you have received all the data or the entire block of the data. In this example, we have set the block size as 1024 bytes.

  • If yes, then go to Block 14.
  • If no, then go to Block 9 and receive the remaining data.

Block 14

Write the data into the flash memory. Then go to Block 15.

Block 15

Check that you have received the entire application data.

  • If yes, then go to Block 16.
  • If No, then go to Block 9 and receive the remaining data.

Block 16

Now we have received the full data and written it into the flash memory or we did not receive any data from the host. Now, we can run our new or old application.

That’s it. Now we will implement the above concept in the code.

Connection

  • LED – PC13 (Onboard LED)
  • UART 1 (PA9, PA10) – For data transfer (115200Bps) (Use USB to Serial converter)
  • UART 2 (PA2, PA3) – For Debug Prints (115200Bps) (Use USB to Serial converter)

Source code – STM32 MikroC Custom Bootloader

Bootloader Source Code

STM32F411 has 512KB flash memory. In that, we are going to give 256KB for the application and 256KB for the bootloader. So, the bootloader will start from 0x08040000. In the project’s Library manager, please enable FLASH and UART. please refer to the below image. Because we are going to use those.

Please check the below-modified bootloader source code. You can also find this code on GitHub.

#include "built_in.h"

///////////////////////////////////////////////////////////////////////////////
#pragma orgall 0x40000
#define BOOTLOADER_START_ADDR   0x40000
#define START_PROGRAM_ADDR      0x60000
#define FLASH_DEFAULT_ADDR      0x08000000

#define MAX_BLOCK_SIZE          ( 1024 )                  //1KB
#define MAX_APP_SIZE            ( 262144 )                //256KB
#define FIRMWARE_FILE_NAME      "App.bin"

#define UART_FIRMWARE_UPDATE

////////////////////////////////////////////////////////////////////////////////
void Write_Begin();
void FLASH_EraseWrite(unsigned long address);

////////////////////////////////////////////////////////////////////////////////
static char block[MAX_BLOCK_SIZE];

//LED Pin PC13
sbit LED at ODR13_GPIOC_ODR_bit;

////////////////////////////////////////////////////////////////////////////////
void Start_Program() org START_PROGRAM_ADDR
{

}

////////////////////////////////////////////////////////////////////////////////
unsigned short UART_Write_Loop(char send, char receive)
{
  unsigned int rslt = 0;

  while(1)
  {
    LED = 1;       // ON PC13
    Delay_ms(20);
    UART_Write(send);
    LED = 0;       // OFF PC13
    Delay_ms(20);

    rslt++;
    if (rslt == 0x64)           // 100 times
      return 0;
    if(UART_Data_Ready()) {
      if(UART_Read() == receive)
        return 1;
    }
  }
}
////////////////////////////////////////////////////////////////////////////////
void FLASH_EraseWrite(unsigned long address)
{
  unsigned int i = 0;
  unsigned int dataToWrite;

  FLASH_Unlock();

  for (i = 0; i < 512; i++)
  {
    dataToWrite = block[i * 2] | (block[i * 2 + 1] << 8);
    FLASH_Write_HalfWord( ( address + i*2 + + FLASH_DEFAULT_ADDR ), dataToWrite);
  }

  FLASH_Lock();
}
////////////////////////////////////////////////////////////////////////////////
void Write_Begin()
{
  unsigned int i;
  unsigned long* ptr;
  unsigned char appResetVector[16];
  unsigned long arm_m0_inst;
  unsigned int dataToWrite;

  //LDR R0, PC+X
  arm_m0_inst = 0x4800 + 1;

  appResetVector[0] = arm_m0_inst;
  appResetVector[1] = arm_m0_inst >> 8;

  //MOV SP, R0
  arm_m0_inst = 0x4685;

  appResetVector[2] = arm_m0_inst;
  appResetVector[3] = arm_m0_inst >> 8;

  //LDR R0, PC+Y
  arm_m0_inst = 0x4800 + 1;

  appResetVector[4] = arm_m0_inst;
  appResetVector[5] = arm_m0_inst >> 8;

  //BX R0
  arm_m0_inst = 0x4700;
  appResetVector[6] = arm_m0_inst;
  appResetVector[7] = arm_m0_inst >> 8;

  //SP
  appResetVector[8] = block[0];
  appResetVector[9] = block[1];
  appResetVector[10] = block[2];
  appResetVector[11] = block[3];

  //PC
  appResetVector[12] = block[4];
  appResetVector[13] = block[5];
  appResetVector[14] = block[6];
  appResetVector[15] = block[7];

  FLASH_Unlock();

  //Clear the 7th Sector (App Start Point)
  FLASH_EraseSector(_FLASH_SECTOR_7);

  for (i = 0; i < 8; i++)
  {
    dataToWrite = appResetVector[i * 2] | (appResetVector[i * 2 + 1] << 8);
    FLASH_Write_HalfWord( ( START_PROGRAM_ADDR + FLASH_DEFAULT_ADDR + i*2 ), dataToWrite);
  }

  FLASH_Lock();

  ptr = (unsigned long*)0x00000000;
  block[0] = LoWord(*ptr);
  block[1] = LoWord(*ptr) >> 8;
  block[2] = HiWord(*ptr);
  block[3] = HiWord(*ptr) >> 8;

  ptr++;

  block[4] = LoWord(*ptr);
  block[5] = LoWord(*ptr) >> 8;
  block[6] = HiWord(*ptr);
  block[7] = HiWord(*ptr) >> 8;

  //Erase Application area
  FLASH_EraseSector(_FLASH_SECTOR_0);
  FLASH_EraseSector(_FLASH_SECTOR_1);
  FLASH_EraseSector(_FLASH_SECTOR_2);
  FLASH_EraseSector(_FLASH_SECTOR_3);
  FLASH_EraseSector(_FLASH_SECTOR_4);
  FLASH_EraseSector(_FLASH_SECTOR_5);
}
////////////////////////////////////////////////////////////////////////////////
void Start_Bootload()
{
  unsigned int i = 0;
  char xx, yy;
  long j = 0x0;
  int k =0;
  unsigned int fw_size = 0;
  unsigned int curr_fw_size = 0;

  //Get firmware Size
  //--- Ask for yy
  UART_Write('y');
  while (!UART_Data_Ready()) ;
  //--- Read yy
  yy = UART_Read();
  //--- Ask for xx
  UART_Write('x');
  while (!UART_Data_Ready()) ;
  //--- Read xx
  xx = UART_Read();

  fw_size = yy | (xx << 8);

  while (1) {
    if( (i == MAX_BLOCK_SIZE) || ( curr_fw_size >= fw_size ) ) {
      //--- If 256 words (1024 bytes) recieved then write to flash
      if (!j)
        Write_Begin();
      if (j < BOOTLOADER_START_ADDR) {
        FLASH_EraseWrite(j);
      }

      i = 0;
      j += 0x400;

      for(k=0; k<MAX_BLOCK_SIZE; k++)
      {
        block[k] = 0;
      }
    }
    if( curr_fw_size >= fw_size )
    {
      LED = 1; // OFF PC13
      Delay_ms(2000);
      UART2_Write_Text("Jumping to Application!!!\r\n");
      Start_Program();
    }

    //--- Ask for yy
    UART_Write('y');
    while (!UART_Data_Ready()) ;
    //--- Read yy
    yy = UART_Read();
    //--- Ask for xx
    UART_Write('x');
    while (!UART_Data_Ready()) ;
    //--- Read xx
    xx = UART_Read();
    //--- Save xxyy in block[i]
    block[i++] = yy;
    block[i++] = xx;

    curr_fw_size += 2;
  }
}

void main()
{
  int i;

  GPIO_Digital_Output(&GPIOC_BASE,_GPIO_PINMASK_13);
  UART2_Init(115200);                                        //Debug Print UART

#ifdef UART_FIRMWARE_UPDATE
  UART2_Write_Text("!------Firmware Update using UART------!\r\n");
  UART1_Init(115200);
  if(UART_Write_Loop('g','r'))       // Send 'g' for ~5 sec, if 'r'
  {
    Start_Bootload();                //   received start bootload
  }
  else
#endif   //UART_FIRMWARE_UPDATE
  {
    UART2_Write_Text("Jumping to Application!!!\r\n");
    Start_Program();                  //   else start program
  }

  UART2_Write_Text("No Application Available...HALT!!!\r\n");
  while(1)
  {
  }
}

PC Tool Code

We have created the PC application source code using the C language. This tool is used to send the firmware or application from the PC to the STM32. You can also get the complete source code from GitHub.

/**************************************************

file: etx_ota_update_main.c
purpose: 

compile with the command: gcc etx_ota_update_main.c RS232\rs232.c -IRS232 -Wall -Wextra -o2 -o etx_ota_app

**************************************************/
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

#ifdef _WIN32
#include <Windows.h>
#else
#include <unistd.h>
#endif

#include "rs232.h"

#define ETX_OTA_MAX_BLOCK_SIZE ( 1024 )
#define ETX_OTA_MAX_FW_SIZE    ( ETX_OTA_MAX_BLOCK_SIZE * 384 )

uint8_t APP_BIN[ETX_OTA_MAX_FW_SIZE];

void delay(uint32_t us)
{
#ifdef _WIN32
    //Sleep(ms);
    __int64 time1 = 0, time2 = 0, freq = 0;

    QueryPerformanceCounter((LARGE_INTEGER *) &time1);
    QueryPerformanceFrequency((LARGE_INTEGER *)&freq);

    do {
        QueryPerformanceCounter((LARGE_INTEGER *) &time2);
    } while((time2-time1) < us);
#else
    usleep(us);
#endif
}

int main(int argc, char *argv[])
{
  int comport;
  int bdrate   = 115200;       /* 115200 baud */
  char mode[]={'8','N','1',0}; /* *-bits, No parity, 1 stop bit */
  char bin_name[1024];
  int ex = 0;
  FILE *Fptr = NULL;

  do
  {
    if( argc <= 2 )
    {
      printf("Please feed the COM PORT number and the Application Image....!!!\n");
      printf("Example: .\\etx_ota_app.exe 8 ..\\..\\Application\\Debug\\Blinky.bin");
      ex = -1;
      break;
    }

    //get the COM port Number
    comport = atoi(argv[1]) -1;
    strcpy(bin_name, argv[2]);

    printf("Opening COM%d...\n", comport+1 );

    if( RS232_OpenComport(comport, bdrate, mode, 0) )
    {
      printf("Can not open comport\n");
      ex = -1;
      break;
    }

    printf("Opening Binary file : %s\n", bin_name);

    Fptr = fopen(bin_name,"rb");

    if( Fptr == NULL )
    {
      printf("Can not open %s\n", bin_name);
      ex = -1;
      break;
    }

    fseek(Fptr, 0L, SEEK_END);
    uint32_t app_size = ftell(Fptr);
    fseek(Fptr, 0L, SEEK_SET);

    printf("File size = %d\n", app_size);

    if( app_size > ETX_OTA_MAX_FW_SIZE )
    {
      printf("Application Size is more than the Maximum Size (%dKb)\n", ETX_OTA_MAX_FW_SIZE/ETX_OTA_MAX_BLOCK_SIZE);
      ex = -1;
      break;
    }

    //read the full image
    if( fread( APP_BIN, 1, app_size, Fptr ) != app_size )
    {
      printf("App/FW read Error\n");
      ex = -1;
      break;
    }

    unsigned char rec;

    printf("Waiting for OTA Start\n");
    do
    {
      RS232_PollComport( comport, &rec, 1);
    } while( rec != 'g' );

    printf("Sending r...\r\n");
    RS232_SendByte(comport, 'r');

    printf("Sending FW Size...\n");
    do
    {
      RS232_PollComport( comport, &rec, 1);
    } while( rec != 'y' );
    
    if( RS232_SendByte(comport, (uint8_t)app_size))
    {
      //some data missed.
      printf("OTA DATA : Send Err\n");
      ex = -1;
      break;
    }

    do
    {
      RS232_PollComport( comport, &rec, 1);
    } while( rec != 'x' );
    
    if( RS232_SendByte(comport, (uint8_t)(app_size >> 8)) )
    {
      //some data missed.
      printf("OTA DATA : Send Err\n");
      ex = -1;
      break;
    }

    printf("Sending Data...\n");

    for( uint32_t i = 0; i < app_size;)
    {
      do
      {
        RS232_PollComport( comport, &rec, 1);
      } while( rec != 'y' );
      
      if( RS232_SendByte(comport, APP_BIN[i++]) )
      {
        //some data missed.
        printf("OTA DATA : Send Err\n");
        ex = -1;
        break;
      }

      do
      {
        RS232_PollComport( comport, &rec, 1);
      } while( rec != 'x' );
      
      if( RS232_SendByte(comport, APP_BIN[i++]) )
      {
        //some data missed.
        printf("OTA DATA : Send Err\n");
        ex = -1;
        break;
      }
      if( ( i % ETX_OTA_MAX_BLOCK_SIZE ) == 0 )
      {
        printf("Block %d Transmitted...\r\n", i/ETX_OTA_MAX_BLOCK_SIZE);
      }
      
    }

    if( ex < 0 )
    {
      break;
    }

  } while (false);

  if(Fptr)
  {
    fclose(Fptr);
  }

  if( ex < 0 )
  {
    printf("OTA ERROR\n");
  }
  return(ex);
}

Application Source Code

We have created a simple application that toggles the PC13 LED every 1 second for testing. You can get the complete source code from GitHub.

#include <built_in.h>

#define LED_PIN (1<<13)

void main()
{
  GPIO_Digital_Output(&GPIOC_BASE, _GPIO_PINMASK_13); // Set PC13 as digital output

  while(1) {
    GPIOC_ODR |= LED_PIN; // ON PC13
    Delay_ms(1000);
    GPIOC_ODR &= (~LED_PIN); // OFF PC13
    Delay_ms(1000);
  }
}

That’s it. We have created the project. You can get the entire project from GitHub.

Testing the STM32 MikroC Custom Bootloader

  • Flash the Bootloader to the STM32.
  • You should see the LED blinking fastly for 100 times. Then you should see the below prints.
!------Firmware Update using UART------!
Jumping to Application!!!
No Application Available…HALT!!!
  • Yes, we don’t have the application. That’s why we are seeing this print.
  • Now we will send the application using the PC Tool that we have created.
  • Open the command prompt in the host application directory and build the host application using the below command.

gcc etx_ota_update_main.c RS232\rs232.c -IRS232 -Wall -Wextra -o2 -o etx_ota_app

  • Then run the application with two arguments. (The first argument is COMPORT number, and the second argument is the Application binary path). See the example below.

.\etx_ota_app.exe 8 ..\Application\Application_STM32.bin

  • Now the PC will be waiting for the Start command from the STM32. You should see the below prints in the command prompt.
D:\EmbeTronicX\Git_Repo\STM32-MikroE-Bootloader\PcTool>.\etx_ota_app.exe 8 ..\Application\Application_STM32.bin
Opening COM8...
Opening Binary file : ..\Application\Application_STM32.bin
File size = 2496
Waiting for OTA Start
  • Now, reset the STM32 Board. It should update the new application and the LED should be blinking every 1 second. You should get the below prints.

PcTool Prints

D:\EmbeTronicX\Git_Repo\STM32-MikroE-Bootloader\PcTool>.\etx_ota_app.exe 8 ..\Application\Application_STM32.bin
Opening COM8…
Opening Binary file : ..\Application\Application_STM32.bin
File size = 2496
Waiting for OTA Start
Sending r…
Sending FW Size…
Sending Data…
Block 1 Transmitted…
Block 2 Transmitted…

UART Prints

!------Firmware Update using UART------!
Jumping to Application!!!
No Application Available…HALT!!!
€!------Firmware Update using UART------!
Jumping to Application!!!

We hope you have learned something new today. In our next post, we will see how to update the application from the SD card in the MikroC Pro for ARM.

You can also read the below tutorials.

Linux Device Driver TutorialsC Programming Tutorials
FreeRTOS TutorialsNuttX RTOS Tutorials
RTX RTOS TutorialsInterrupts Basics
I2C Protocol – Part 1 (Basics)I2C Protocol – Part 2 (Advanced Topics)
STM32 TutorialsLPC2148 (ARM7) Tutorials
PIC16F877A Tutorials8051 Tutorials
Unit Testing in C TutorialsESP32-IDF Tutorials
Raspberry Pi TutorialsEmbedded Interview Topics
Reset Sequence in ARM Cortex-M4BLE Basics
VIC and NVIC in ARMSPI – Serial Peripheral Interface Protocol
STM32F7 Bootloader TutorialsRaspberry PI Pico Tutorials
STM32F103 Bootloader TutorialsRT-Thread RTOS Tutorials
Zephyr RTOS Tutorials – STM32Zephyr RTOS Tutorials – ESP32
AUTOSAR TutorialsUDS Protocol Tutorials
Product ReviewsSTM32 MikroC Bootloader Tutorial
VHDL Tutorials
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Table of Contents