自己开发I2C Bootloader -下位机开发篇

开发前言

   下位机开发就裸机编程而言其实就是基于MCU做固件或者说驱动开发,那目前作者接触到的主流的MCU型号国外的就是STM32,国内的就是GD32,至于其它家一般都有其特定的应用领域或者细分市场战略。就本项目的i2c bootloader开发而言,这里的下位机开发要实现的功能主要就包括两点,一是要实现串口转I2C驱动,二是要实现MCU内部Flash读写,具体来讲就是基于STM32F103来实现USB串口转I2C通信,基于GD32E230来实现能够擦写Flash的bootloader,bootloader代码主要的功能就是接收i2c master的指令信息,然后再将指令信息擦写到GD32E230的Main Flash位置区域。这里的通信链路说明一下:笔记本电脑 -> USB转I2C master设备 -> I2C slave设备,就本i2c bootloader项目开发而言,slave设备是GD32E232或者GD32E230并无区别。
自己开发I2C Bootloader -下位机开发篇_第1张图片

USB转I2C驱动开发

   传统的STM32开发方式,这里用到STM32CubeMX自动生成代码工程,需要配置的外设是USB和I2C,当然了你想直观调试查看,也可以配置USART串口打印,到时候用USB转USART设备直接连接到电脑看打印结果。需要注意这里的I2C外设要有自己的地址,默认是0,那作者这里手动设置hi2c1.Init.OwnAddress1 = 113;当然其它值也是没问题的。usbd_cdc_if.c是USB串口驱动代码,这段代码能够保证USB接收超过64字节的数据,实际上就是采用了分包处理的策略。main.c是USB转I2C驱动代码,这段代码会将USB串口接收到的上位机指令/数据转由I2C进行传输。另外需要说明的是,因为本工程采用arm-none-eabi-gcc进行编译,所以如果要实现串口重定向打印输出,就要重新实现 *int _write(int fd, char *ptr, int len) *。 V0.1版本的源码作者会放置到CSDN附件供参考使用。

  • usbd_cdc_if.c
/* USER CODE BEGIN PRIVATE_VARIABLES */
// uint8_t rxBuff[256+16];
#define Max_Pack_Size   (64)
#define rxBuff_Len      (256)
extern uint8_t  rxBuff[rxBuff_Len];
uint8_t Num_Packet;
uint32_t SinglePackLength;
uint32_t Num_Rx_Data;
uint32_t Wait_RX_Dly;
uint32_t Num_Out_Pack;
/* USER CODE END PRIVATE_VARIABLES */

/**
  * @brief  Data received over USB OUT endpoint are sent over CDC interface
  *         through this function.
  *
  *         @note
  *         This function will issue a NAK packet on any OUT packet received on
  *         USB endpoint until exiting this function. If you exit this function
  *         before transfer is complete on CDC interface (ie. using DMA controller)
  *         it will result in receiving more data while previous ones are still
  *         not sent.
  *
  * @param  Buf: Buffer of data to be received
  * @param  Len: Number of data received (in bytes)
  * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
  /* USER CODE BEGIN 6 */
  USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
  USBD_CDC_ReceivePacket(&hUsbDeviceFS);

  if (Num_Packet == 0)
  {
    memset(rxBuff, '\0', rxBuff_Len);
  }
  SinglePackLength = (uint32_t)*Len;
  if (SinglePackLength == Max_Pack_Size) {
    memcpy(((uint8_t *)&rxBuff[0]+(Max_Pack_Size*Num_Packet)), Buf, Max_Pack_Size);
    Num_Rx_Data = (Max_Pack_Size * Num_Packet) + SinglePackLength;
    Num_Out_Pack = ++Num_Packet;
    Wait_RX_Dly = 5;
  }
  else
  {
    memcpy(((uint8_t *)&rxBuff[0]+(Max_Pack_Size*Num_Packet)), Buf, SinglePackLength);
    Num_Rx_Data = (Max_Pack_Size * Num_Packet) + SinglePackLength;
    Num_Out_Pack = ++Num_Packet;
    Wait_RX_Dly = 0x00;
    Num_Packet = 0;
    memset(Buf, '\0', Max_Pack_Size);
  }
  return (USBD_OK);
  /* USER CODE END 6 */
}
  • main.c
/* USER CODE BEGIN 0 */
// int ret;
uint8_t *p_rev;
uint8_t pData[command_length] = {0};
uint8_t pData1[command_length] = {0};
#ifdef __GNUC__
int _write(int fd, char *ptr, int len)  
{  
  HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 0xFFFF);
  return len;
}
int _read(int fd, char *ptr, int len)
{
  HAL_UART_Receive(&huart1, (uint8_t*)ptr, len, 0xFFFF);
  return *ptr;
}
#else
int fputc(int ch, FILE *f)
{
	HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1,0xFFFF);
	return ch;
}
int fgetc(FILE *f)
{
	uint8_t ch = 0;
	HAL_UART_Receive(&huart1, &ch, 1, 0xFFFF);
	return ch;
}
#endif

extern uint32_t Num_Rx_Data;
uint8_t txBuff[256*8+5]; //1024 -> 256
// extern uint8_t rxBuff[256];
uint8_t rxBuff[256];
int txlen=0;
enum CMDenum{
	CMD_FAIL=0 ,    	//0     F Bytes	
	CMD_OK ,        	//1     O Bytes
	CMD_I2C_Write,  	//2     W Byte I2CAddr ...
	CMD_I2C_Read ,  	//3     R 1 Byte.
	CMD_WriteRead,  	//4     r bytes I2C address R 1 bytes. 
	CMD_WriteNoStop,	//5     w byte I2C address... without Stop.
  	CMD_I2C_Read_Muti=31, //31 return Los
  
  	CMD_TEST = 250            //(0xFA)
};
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  HAL_StatusTypeDef i = HAL_OK;
  uint8_t *p;
  int ret=0;
  int rxBuffLen;
  // int miti_lens = 0;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();
  MX_USB_DEVICE_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_TIM1_Init();
  MX_SPI2_Init();
  MX_I2C1_Init();

  /* Initialize interrupts */
  MX_NVIC_Init();
  /* USER CODE BEGIN 2 */
  rxBuffLen=0; 
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    printf("Master!\n");
    i = HAL_I2C_Slave_Receive(&hi2c1, pData1, command_length, 3000);
    if (i == HAL_OK) {
      p_rev = pData1 + 1;
    }
    ret=HAL_I2C_Master_Transmit(&hi2c1, Add, pData, sizeof(pData)/sizeof(pData[0]), 1000);
	HAL_Delay(100);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    txlen=1; 
    rxBuffLen = Num_Rx_Data;	
    if(rxBuffLen) 
    {
      p=rxBuff+1; rxBuffLen--;
      while(rxBuffLen>=2 && p[1]<=rxBuffLen-2) //p[1]=len
      {
        switch(*p) //p[0]=rxBuff[1]
        {
          case CMD_I2C_Write:             //0x02, W Byte I2CAddr ...
            //send 
            for(int j = 1; j <= command_length; j++) {
                pData[j-1] = p[j];
              }
            //receive
            if (p_rev[0]) {
              ret = HAL_OK;
              txBuff[txlen++]= (ret==HAL_OK)?CMD_OK:CMD_FAIL; 
              txBuff[txlen++]=0;
            }
            else {
              ret = HAL_ERROR;
              txBuff[txlen++]= (ret==HAL_ERROR)?CMD_FAIL:CMD_OK; 
              txBuff[txlen++]=0;
            }
            break;
  
          case CMD_I2C_Read:              //0x03, R 2 I2CAddr Bytes...
            for(int j = 1; j <= command_length; j++) {
              pData[j-1] = p[j];
              // txBuff[txlen++] = p_rev[j-1];
              txBuff[txlen++] = pData1[j-1];
            }
            break;

          default:
            // txBuff[txlen++]= CMD_FAIL;
            // txBuff[txlen++]=0;
            rxBuffLen=2;
            p[1]=0;
            break;
        }
        rxBuffLen-=p[1]+2; 
        if(rxBuffLen<2) rxBuffLen=0;
        p+=p[1]+2; //update the p[0]
      }
      // USB_Reset_RXSize();
      if(txlen>1)
      {
        txBuff[0]=txlen-1;
        while(CDC_Transmit_FS(txBuff, txlen) != USBD_OK)
          Delay_us(100);
      }
    }
  }
  /* USER CODE END 3 */
}

BootLoader源码设计开发

   须知bootloader源码和APP源码一样都是一个独立的固件。所以在设计bootloader时候就像写普通固件代码一样就可以了。但在写bootloader之前要查看一下自己所用的MCU的flash空间大小,有的MCU的flash过小,而待烧录的APP代码又过于复杂,导致能够被bootloader利用的空间被极端压缩,这时候就要自己权衡bootloader存在的必要性了。这里作者基于GD32提供的标准库来开发。也是定义了STM32F103C8T6的I2C设备地址。 #define MB_ADDRESS 0x71。对于V0.1版本的bootloader源码来讲目前主要实现了以下几个必要的功能。
1.FMC擦除;
2.FMC编程;
3.FMC任意指定FLASH内容读取;
4.跳转到APP;
这里需要强调一点,要想成功对FMC进行编程,FMC擦除操作一定要先执行,这是因为FLASH在编程时只允许由1到0,而不允许由0到1。 V0.1版本的源码作者会放置到CSDN附件供参考使用。

  • cflash.c
#include "cflash.h"

uint32_t *ptrd=NULL;
uint32_t address = 0x00000000;
uint32_t data0   = 0xb14bb510;

uint32_t page_num = (FMC_WRITE_END_ADDR - FMC_WRITE_START_ADDR) / FMC_PAGE_SIZE;
uint32_t word_num = ((FMC_WRITE_END_ADDR - FMC_WRITE_START_ADDR) >> 2);

void fmc_erase_pages_check(void)
{
    uint32_t i;
    ptrd = (uint32_t *)FMC_WRITE_START_ADDR;
    for (i = 0; i < word_num; i++) {
        if (0xFFFFFFFF != (*ptrd)) {
            blue_led_off();
            yellow_led_on();
            break;
        }
        else {
            blue_led_on();
            yellow_led_off();
            ptrd++;
        }
    }
}

/**
 * @brief 
 * 
 * @param   page_Numebr     the data from host
 * @param   number_of_pages the data from host
 */
static void fmc_erase_pages_memory(uint8_t page_Numebr, uint8_t number_of_pages)
{
    uint32_t erase_counter;
    fmc_unlock();
    fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_PGERR | FMC_FLAG_WPERR);
    for (erase_counter = 0; erase_counter < number_of_pages; erase_counter++) 
    {
        fmc_page_erase(((page_Numebr * FMC_PAGE_SIZE + FMC_ERASE_ADDR) + (FMC_PAGE_SIZE * erase_counter)));
        fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_PGERR | FMC_FLAG_WPERR);
    }
    fmc_lock();
}


void fmc_program_check(void)
{
    uint32_t i;
    ptrd = (uint32_t *)FMC_WRITE_START_ADDR1;
    for (i = 0; i < word_num; i++) {
        if (*ptrd != data0) {
            blue_led_on();
            yellow_led_off();
            break;
        }
        else
        {
            blue_led_off();
            yellow_led_on();
            // ptrd++;
        }
    }
}

/**
 * @brief 
 * 
 * @param   host_payload    the data is from host
 * @param   start_addr      the data is from host
 * @param   payload_len     the data is from host
 */
static void fmc_program_memory(uint8_t *host_payload, uint32_t start_addr, uint8_t payload_len)
{
    fmc_unlock();
    uint8_t word_len = payload_len / sizeof(uint32_t);
    uint32_t data[32] = {0};
    for (uint8_t i = 0; i < word_len; i++) 
    {
        data[i] = (host_payload[3 + 4 * i] << 24)+
        (host_payload[2 + 4 * i] << 16)+
        (host_payload[1 + 4 * i] << 8)+
        (host_payload[0 + 4 * i]);
        fmc_word_program(start_addr + 4 * i, data[i]);
        fmc_flag_clear(FMC_FLAG_END | FMC_FLAG_PGERR | FMC_FLAG_WPERR);
    }
    fmc_lock();
}

void Bootloader_Erase_Flash(uint8_t *Host_Buffer)
{
    // Perform an erase from Flash memory 
    fmc_erase_pages_memory(Host_Buffer[2], Host_Buffer[3]);
    fmc_erase_pages_check();
}

void Bootloader_Memory_Write(uint8_t *Host_Buffer)
{
	uint8_t Payload_Len = 0;
	uint32_t Host_Addr = 0;

    Host_Addr = *((uint32_t *)&Host_Buffer[2]);
    Payload_Len = Host_Buffer[6];
    fmc_program_memory((uint8_t *)&Host_Buffer[7], Host_Addr, Payload_Len);
    fmc_program_check();
}

uint32_t Bootloader_Memory_Read(uint8_t *Host_Buffer)
{
	uint8_t mem_val[4] = {0};
	uint32_t addr;
	uint32_t Host_Addr = 0;
	addr =  (Host_Buffer[5] << 24) + 
			(Host_Buffer[4] << 16) + 
			(Host_Buffer[3] << 8 ) + 
			 Host_Buffer[2];
	Host_Addr = *((uint32_t *)addr); //read memory
    // Host_Addr = *((uint32_t *)&Host_Buffer[2]); //read memory
	mem_val[0] = (Host_Addr & 0x000000FF);
	mem_val[1] = (Host_Addr & 0x0000FF00) >> 8;
	mem_val[2] = (Host_Addr & 0x00FF0000) >> 16;
	mem_val[3] = (Host_Addr & 0xFF000000) >> 24;
    blue_led_on();
    return ((mem_val[3] << 24) + (mem_val[2] << 16) + (mem_val[1] << 8) + mem_val[0]);
}

typedef  void (*pFunction)(void);
void Bootloader_JumpToApplication(void)
{
    uint32_t JumpAddress = *(__IO uint32_t*)(APP_ADDRESS + 4);
    pFunction Jump       = (pFunction)JumpAddress;
    if (0x20000000 == ((*(__IO uint32_t*)APP_ADDRESS) & 0x2FFE0000)) {
        SysTick->CTRL = 0;
        SysTick->LOAD = 0;
        SysTick->VAL  = 0;
        timer_disable(LED_BLINK_TIMER);
        __disable_irq();
        __DSB();
        __ISB();
        nvic_vector_table_set(NVIC_VECTTAB_FLASH, 0xDC00);
        __set_MSP(*(__IO uint32_t*)APP_ADDRESS);
        __DSB();
        __ISB();
        Jump();
    }
    else {
        yellow_led_on();
        blue_led_on();
    }
}
  • main.c

#include "main.h"

#define command_length (16 + 128 + 4)
uint8_t i2c_receiver[command_length] = {0};
uint8_t i2c_transmitter[command_length] = {0};
uint8_t *p;
int ret;
uint32_t mem_value;

/*!
    \brief      main function
    \param[in]  none
    \param[out] none
    \retval     none
*/
int main(void)
{
    int i;

    /* configure systick */
    systick_config();
    /* configure USART */
    usart_config();
    
    /* configure RCU */
    rcu_config();
    /* configure GPIO */
    gpio_i2c0_config();
    /* configure I2C */
    i2c0_config();
    while(1) {
        // printf("Slave\n");
        i = 0;
        memset(i2c_transmitter, 0x00, sizeof(i2c_transmitter)/sizeof(i2c_transmitter[0]));
        /* wait until ADDSEND bit is set */
        while(!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND));
        /* clear ADDSEND bit */
        i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
        for(i = 0; i < command_length; i++) {
            /* wait until the RBNE bit is set */
            while(!i2c_flag_get(I2C0, I2C_FLAG_RBNE));
            /* read a data byte from I2C_DATA */
            i2c_receiver[i] = i2c_data_receive(I2C0);
        }
        /* wait until the STPDET bit is set */
        while(!i2c_flag_get(I2C0, I2C_FLAG_STPDET));
        /* clear the STPDET bit */
        i2c_enable(I2C0);
        
        // for(i = 0; i < 148; i++) {
        //     printf("0x%02X ", i2c_receiver[i]);
        // }
        if (i2c_receiver[0]) {
            p = i2c_receiver + 1;
            switch (*p) {
                case CMD_I2C_Write:
                    ret = qsfp28lr4_i2c1_write(p + 4, *(p + 1), (p[2] << 8) + p[3]);
                    i2c_transmitter[1] = (ret==SET)?CMD_OK:CMD_FAIL;
                    break;
                case CMD_I2C_Read:
                    qsfp28lr4_i2c1_read(p + 4, *(p + 1),  (p[2] << 8) + p[3]);
                    for (i=0; i<((p[2] << 8) + p[3]); i++) {
                        i2c_transmitter[i] = p[4+i];
                    }
                    break;
                case CMD_FMC_ERASE:
                    Bootloader_Erase_Flash(p+1);
                    i2c_transmitter[1] = CMD_OK;
                    break;
                case CMD_FMC_PROGRAM:
                    Bootloader_Memory_Write(p+1);
                    i2c_transmitter[1] = CMD_OK;
                    break;
                case CMD_FMC_READMEM:
                    mem_value = Bootloader_Memory_Read(p+1);
                    printf("%02X, %02X, %02X, %02X\n", *(p+3), *(p+4), *(p+5), *(p+6));
                    i2c_transmitter[0] = (mem_value & 0xFF);
                    i2c_transmitter[1] = (mem_value & 0xFF00) >> 8;
                    i2c_transmitter[2] = (mem_value & 0xFF0000) >> 16;
                    i2c_transmitter[3] = (mem_value & 0xFF000000) >> 24;
                    break;
                case CMD_FMC_JUMPTOAPP:
                    Bootloader_JumpToApplication();
                    break;
                case CMD_I2C_AT24C02:
                    ret = i2c_24c02_test();
                    i2c_transmitter[1] = (ret==SET)?CMD_OK:CMD_FAIL;
                    led_on();
                    break;
                default:
                    p[0] = 0;
                    // led_on();
                    break;
            }
            /* time delay */
            delay_ms(500);
            /* wait until I2C bus is idle */
            while(i2c_flag_get(I2C0, I2C_FLAG_I2CBSY));
            /* send a start condition to I2C bus */
            i2c_start_on_bus(I2C0);
            /* wait until SBSEND bit is set */
            while(!i2c_flag_get(I2C0, I2C_FLAG_SBSEND));
            /* send slave address to I2C bus*/
            i2c_master_addressing(I2C0, MB_ADDRESS, I2C_TRANSMITTER);
            /* wait until ADDSEND bit is set*/
            while(!i2c_flag_get(I2C0, I2C_FLAG_ADDSEND));
            /* clear ADDSEND bit */
            i2c_flag_clear(I2C0, I2C_FLAG_ADDSEND);
            /* wait until the transmission data register is empty */
            while(!i2c_flag_get(I2C0, I2C_FLAG_TBE));
            for(i = 0; i < command_length; i++) {
                /* send a data byte */
                i2c_data_transmit(I2C0, i2c_transmitter[i]);
                /* wait until the transmission data register is empty*/
                while(!i2c_flag_get(I2C0, I2C_FLAG_TBE));
            }
            /* send a stop condition to I2C bus*/
            i2c_stop_on_bus(I2C0);
            /* wait until stop condition generate */
            while(I2C_CTL0(I2C0) & I2C_CTL0_STOP);
        }
    }
}

APP固件开发

  我们开发i2c bootloader的目的就是为了能够通过IAP的方式烧写APP。所以,准备一份能够烧写的app代码是必须的。这里的app针对待烧录设备GD32E230;所以app要是基于GD32E230开发的。需要注意的是APP在FLASH中的地址分配。本项目中我们根据bootloader代码存储空间需求(addr: 0x08000000 - 0x0800DBFF)和实际 Main FLASH(64 pages, 1page=1K)大小,在编译app固件时,要求在该app固件中的.ld链接文件中将MEMORY中的FLASH按此处样式修改FLASH (rx) : ORIGIN = 0x0800DC00, LENGTH = 9K,重新编译固件,生成.bin文件(此处为gd32e23x.bin)。在下一篇上位机开发篇中,我们需要重点处理这个.bin二进制文件,采用脚本方式将.bin文件顺利烧写到GD32E230的FLASH地址为0x0800DC00- 0x0800FFFF位置区域。 V0.1版本的源码作者会放置到CSDN附件供参考使用

  • gd32e23x_gcc.ld
/* memory map */
MEMORY
{
  FLASH (rx)      : ORIGIN = 0x0800DC00, LENGTH = 9K
  RAM (xrw)       : ORIGIN = 0x20000000, LENGTH = 8K
}

ENTRY(Reset_Handler)

SECTIONS
{
  __stack_size = DEFINED(__stack_size) ? __stack_size : 2K;

/* ISR vectors */
  .vectors :
  {
    . = ALIGN(4);
    KEEP(*(.vectors))
    . = ALIGN(4);
    __Vectors_End = .;
    __Vectors_Size = __Vectors_End - __gVectors;
  } >FLASH

  .text :
  {
    . = ALIGN(4);
    *(.text)
    *(.text*)
    *(.glue_7)
    *(.glue_7t)
    *(.eh_frame)

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    /* the symbol '_etext' will be defined at the end of code section */
    _etext = .;
  } >FLASH

  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)
    *(.rodata*)
    . = ALIGN(4);
  } >FLASH

  .ARM.extab :
  {
     *(.ARM.extab* .gnu.linkonce.armextab.*)
  } >FLASH

  .ARM : {
    __exidx_start = .;
    *(.ARM.exidx*)
    __exidx_end = .;
  } >FLASH

  .ARM.attributes : { *(.ARM.attributes) } > FLASH

  .preinit_array :
  {
    PROVIDE_HIDDEN (__preinit_array_start = .);
    KEEP (*(.preinit_array*))
    PROVIDE_HIDDEN (__preinit_array_end = .);
  } >FLASH

  .init_array :
  {
    PROVIDE_HIDDEN (__init_array_start = .);
    KEEP (*(SORT(.init_array.*)))
    KEEP (*(.init_array*))
    PROVIDE_HIDDEN (__init_array_end = .);
  } >FLASH

  .fini_array :
  {
    PROVIDE_HIDDEN (__fini_array_start = .);
    KEEP (*(.fini_array*))
    KEEP (*(SORT(.fini_array.*)))
    PROVIDE_HIDDEN (__fini_array_end = .);
  } >FLASH

  /* provide some necessary symbols for startup file to initialize data */
  _sidata = LOADADDR(.data);
  .data :
  {
    . = ALIGN(4);
    /* the symbol '_sdata' will be defined at the data section end start */
    _sdata = .;
    *(.data)
    *(.data*)
    . = ALIGN(4);
    /* the symbol '_edata' will be defined at the data section end */
    _edata = .;
  } >RAM AT> FLASH

  . = ALIGN(4);
  .bss :
  {
    /* the symbol '_sbss' will be defined at the bss section start */
    _sbss = .;
    __bss_start__ = _sbss;
    *(.bss)
    *(.bss*)
    *(COMMON)
    . = ALIGN(4);
    /* the symbol '_ebss' will be defined at the bss section end */
    _ebss = .;
    __bss_end__ = _ebss;
  } >RAM

  . = ALIGN(8);
  PROVIDE ( end = _ebss );
  PROVIDE ( _end = _ebss );

  .stack ORIGIN(RAM) + LENGTH(RAM) - __stack_size :
  {
    PROVIDE( _heap_end = . );
    . = __stack_size;
    PROVIDE( _sp = . );
  } >RAM AT>RAM
}

 /* input sections */
GROUP(libgcc.a libc.a libm.a libnosys.a)

你可能感兴趣的:(嵌入式系统,c语言,visual,studio,code,stm32,单片机)