# bootloader_stm32f103c8 **Repository Path**: lao-gaos-foam/bootloader_stm32f103c8 ## Basic Information - **Project Name**: bootloader_stm32f103c8 - **Description**: stm32f103c8芯片的bootloader实现, 具有串口升级, 串口通信和打印功能. 文件传输基于ymodem实现 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2025-07-19 - **Last Updated**: 2025-07-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # stm32f103c8芯片bootloader实现 核心功能: - SWD调试 - 串口升级 - 串口通信 - print映射到串口打印 - 基于HAL库实现 ---- - [1. 实现串口打印 2023-12-25更新](#1-实现串口打印-2023-12-25更新) - [1.1. 引脚定义](#11-引脚定义) - [1.2. 代码](#12-代码) - [2. 更新按钮读取的实现 2023-12-25](#2-更新按钮读取的实现-2023-12-25) - [3. 实现接收SecureCRT上位机发来的文件数据2023-12-26](#3-实现接收securecrt上位机发来的文件数据2023-12-26) - [3.1. ymodem头文件](#31-ymodem头文件) - [3.2. ymodem代码](#32-ymodem代码) - [4. 实现了校验算法 2023-12-27](#4-实现了校验算法-2023-12-27) - [5. 自定义RAM代码段](#5-自定义ram代码段) - [5.1. ld文件中的MEMORY段](#51-ld文件中的memory段) - [5.2. ld文件修改](#52-ld文件修改) - [5.3. 在代码中定义RAM](#53-在代码中定义ram) - [5.4. CubeIDE生成bin, hex文件配置](#54-cubeide生成bin-hex文件配置) - [5.4.1. CubeIDE配置](#541-cubeide配置) - [5.4.2. 无敌大的bin文件如果在.ld没有`(NOLOAD)`](#542-无敌大的bin文件如果在ld没有noload) - [6. 自定义FLASH段](#6-自定义flash段) - [6.1. ld文件修改](#61-ld文件修改) - [6.2. main文件修改](#62-main文件修改) - [6.3. 编译并查看内存分区情况](#63-编译并查看内存分区情况) - [6.4. Debug并查看实际内存](#64-debug并查看实际内存) - [6.4.1. Debug工程](#641-debug工程) - [6.4.2. 打开内存查看器](#642-打开内存查看器) - [6.4.3. 添加1个要查看的地址](#643-添加1个要查看的地址) - [7. 将APP映射到指定的内存空间](#7-将app映射到指定的内存空间) - [7.1. APP内存的申请](#71-app内存的申请) - [7.2. 修改section定义](#72-修改section定义) - [7.3. APP\_FLASH检查](#73-app_flash检查) - [7.4. APP代码的实现并指定内存起点](#74-app代码的实现并指定内存起点) - [7.5. app运行后的内存检查](#75-app运行后的内存检查) - [8. 内部flash的擦写](#8-内部flash的擦写) - [8.1. 解锁](#81-解锁) - [8.2. 擦除](#82-擦除) - [8.3. 调试检查擦除功能](#83-调试检查擦除功能) - [9. 内存写入](#9-内存写入) - [9.1. 注意事项:](#91-注意事项) - [9.2. 代码实现](#92-代码实现) - [9.3. 写入效果](#93-写入效果) - [9.3.1. 示例文件](#931-示例文件) - [9.3.2. 超级终端上位机](#932-超级终端上位机) - [9.3.3. SecurCRT上位机](#933-securcrt上位机) - [9.4. 小结](#94-小结) - [10. 提出ymodem无效数](#10-提出ymodem无效数) - [10.1. 更新代码](#101-更新代码) - [10.2. 更新结果](#102-更新结果) - [10.3. 小结](#103-小结) - [11. AP程序](#11-ap程序) - [11.1. 更改代码RAM的地址](#111-更改代码ram的地址) - [11.2. 中断向量表更改](#112-中断向量表更改) --- ## 1. 实现串口打印 2023-12-25更新 ### 1.1. 引脚定义 目前是使用USART1默认的定义 - PA10 USART1_RX - PA9 USART1_TX PC13接了一个LED指示灯 IOC文件如下 ![串口print实现的ioc](image/readme/串口print实现的ioc.png) ### 1.2. 代码 ```c // uart_print.h #ifndef _UART_PRINT_H #define _UART_PRINT_H #include "stm32f1xx_hal.h" #include "stm32f103xb.h" #include "stm32f1xx_hal_uart.h" #include #include #include /** * @brief 基于HAL库的初始化printf到串口输出的映射, 波特率会重置为115200 * @param USART_TypeDef *: uart_to_print, 要映射的串口, 比如USART1, USART3 * @return 0:初始化成功; * 其他:初始化失败 */ int uart_print_init(USART_TypeDef *uart_to_print); #endif ``` ```c // uart_print.c #include "uart_print.h" UART_HandleTypeDef uart_print; int uart_print_init(USART_TypeDef *pUart) { uart_print.Instance = pUart; /* 映射到哪个串口, 就填哪个 */ uart_print.Init.BaudRate = 115200; uart_print.Init.WordLength = UART_WORDLENGTH_8B; uart_print.Init.StopBits = UART_STOPBITS_1; uart_print.Init.Parity = UART_PARITY_NONE; uart_print.Init.Mode = UART_MODE_TX_RX; uart_print.Init.HwFlowCtl = UART_HWCONTROL_NONE; uart_print.Init.OverSampling = UART_OVERSAMPLING_16; if(HAL_UART_Init(&uart_print) == HAL_OK) return 0; return 1; } #ifdef __GNUC__ /* With GCC, small printf (option LD Linker->Libraries->Small printf set to 'Yes') calls __io_putchar() */ int __io_putchar(int ch) #else int fputc(int ch, FILE *f) #endif /* __GNUC__ */ { /* Place your implementation of fputc here */ /* e.g. write a character to the UART3 and Loop until the end of transmission */ HAL_UART_Transmit(&uart_print, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; } ``` ## 2. 更新按钮读取的实现 2023-12-25 使用板载的PB13当作更新按钮, 电路上按钮常开状态为高电平, 为方便测试, 采集到高电平连续三拍有2拍为高则判断进入更新模式. ![更新按钮读取ioc](image/readme/更新按钮读取ioc.png) ## 3. 实现接收SecureCRT上位机发来的文件数据2023-12-26 ![传送完成界面](image/readme/传送完成界面.png) ### 3.1. ymodem头文件 ```c /* * ymodem.h * * Created on: Dec 25, 2023 * Author: jetpo */ #ifndef INC_YMODEM_H_ #define INC_YMODEM_H_ #include "stm32f1xx_hal.h" #include "stm32f1xx_hal_uart.h" #include /* Exported constants --------------------------------------------------------*/ /* Packet structure defines */ #define PACKET_HEADER_SIZE ((uint32_t)3) #define PACKET_DATA_INDEX ((uint32_t)3) #define PACKET_START_INDEX ((uint32_t)0) #define PACKET_NUMBER_INDEX ((uint32_t)1) #define PACKET_CNUMBER_INDEX ((uint32_t)2) #define PACKET_TRAILER_SIZE ((uint32_t)2) #define PACKET_OVERHEAD_SIZE (PACKET_HEADER_SIZE + PACKET_TRAILER_SIZE) /* 帧头+帧尾的长度 */ #define PACKET_DATA_SIZE ((uint32_t)128) /* 纯128字节包的数据长度 */ #define PACKET_DATA_1K_SIZE ((uint32_t)1024) /* 纯1024字节包的数据长度 */ #define FILE_NAME_LENGTH ((uint32_t)64) /* 文件名最大只支持64位 */ #define FILE_SIZE_LENGTH ((uint32_t)16) /* 文件长度字最大只支持64位 */ /* /-------- Packet Struct ---------------------------------------------\ * | 0 | 1 | 2 | 3 | ... | n+3 | n+4 | n+5 | * |--------------------------------------------------------------------| * | start | number| !num | data[0] | ... | data[n] | crc0 | crc1 | * \--------------------------------------------------------------------/ * 其中的n长度为1024或者128 */ #define SOH ((uint8_t)0x01) /* start of 128-byte data packet */ #define STX ((uint8_t)0x02) /* start of 1024-byte data packet */ #define EOT ((uint8_t)0x04) /* end of transmission */ #define ACK ((uint8_t)0x06) /* acknowledge */ #define NAK ((uint8_t)0x15) /* negative acknowledge */ #define CA ((uint8_t)0x18) /* two of these in succession aborts transfer */ #define CRC16 ((uint8_t)0x43) /* 'C' == 0x43, request 16-bit CRC */ #define NEGATIVE_BYTE ((uint8_t)0xFF) #define PACKET_TIMEOUT (0x1000) #define MAX_ERROR (5) /** * @brief ymodem通信初始化 * * @param pUart 通信端口 * @return 0: 初始化正常; * 其它:错误代码. */ int ymodem_init(UART_HandleTypeDef *pUart); /** * @brief 处理1个完整的数据信息包, 不负责校验部分 * 文件信息包格式 * SOH 00 0xFF FILE_NAME '\0' FILE_LENGHT ' ' [用SecureCRT发的乱码未考证为什么, 按照协议应该是填0x00?] 0x00 .. 0x00 crc_hight crc_low * 其中 * - 文件长度使用字符串表示 * - 文件名结尾有1个符号'\0', 即0x00 * - 长度字符串结尾有1个' '(空格符), 再往后目前对我来看是乱码 * * @param p_buf 原始数据流 * @param pout_filename 返回值:解码出的文件名 * @param pout_filename_len 返回值:解码出的文件名实际长度, 包含最后的0x00 * @param pout_filelsize 返回值:解码出的文件长度 * @return 0:解码成功; * 其它:解码失败. */ int ymodem_parse_fileinfo(uint8_t *p_buf, uint8_t *pout_filename, uint16_t *pout_filename_len, uint32_t *pout_filesize); /** * @brief 接收一个数据包 * * @param pout_dat 返回值, 接收到的数据 * @param pout_len 0: 接收程序完成; * -1: 传送中止; * >1: 有效数据长度, 对于ymodem, 应为128/1024 * @return 0: ymodem发送结束 * -1: 传送中止 * 1: 正确接收1包 */ int ymodem_receive_packet(uint8_t *pout_dat, uint32_t *pout_len); int ymodem_send_C(); int ymodem_send_ACK(); int ymodem_send_NAK(); #endif /* INC_YMODEM_H_ */ ``` ### 3.2. ymodem代码 ```c /* * ymodem.c * * Created on: Dec 25, 2023 * Author: jetpo */ #include "ymodem.h" #include "common.h" UART_HandleTypeDef *huart_ymodem; #define UART_GETCHAR(rx) HAL_UART_Receive(huart_ymodem, (uint8_t *)&rx, 1, PACKET_TIMEOUT) // rx => char #define UART_SENDCHAR(tx) HAL_UART_Transmit(huart_ymodem, (uint8_t *)&tx, 1, 100) // tx => char /** * @brief crc16校验 * * @param p_buf 原始字节流 * @param offset 开始计算的字节的偏移 * @param len 计算的字节长度(不含尾部的crc) * @return 0: 校验通过; * 其它:错误代码. */ static int calc_crc16(uint8_t *p_buf, uint8_t offset, uint32_t len) { return 0; } /** * @brief 接收一个数据包 * * @param pout_dat 返回值, 接收到的数据 * @param pout_len 0: 接收程序完成; * -1: 传送中止; * >1: 有效数据长度, 对于ymodem, 应为128/1024 * @return 0: ymodem发送结束 * -1: 传送中止 * 1: 正确接收1包 */ int ymodem_receive_packet(uint8_t *pout_dat, uint32_t *pout_len) { uint16_t i, packet_size; HAL_StatusTypeDef ex; uint8_t rx; // 首字节处理 ex = UART_GETCHAR(rx); if (HAL_OK != ex) return -1; switch (rx) { case SOH: packet_size = PACKET_DATA_SIZE; break; case STX: packet_size = PACKET_DATA_1K_SIZE; break; case EOT: return 0; // 接收完成 case CA: // 发送方取消 ex = UART_GETCHAR(rx); if (CA == rx) { *pout_len = -1; return -1; } default: { *pout_len = -1; return -1; } } *pout_dat = rx; for (i = 1; i < packet_size + PACKET_OVERHEAD_SIZE; i++) { if (HAL_OK != UART_GETCHAR(rx)) { *pout_len = -1; return -1; } pout_dat[i] = rx; } // 长度字校验 if ((pout_dat[PACKET_NUMBER_INDEX] + pout_dat[PACKET_CNUMBER_INDEX]) != 255) { *pout_len = -1; return -1; } // TODO:CRC校验 *pout_len = packet_size; return 1; } /** * @brief 处理1个完整的数据信息包, 不负责校验部分 * 文件信息包格式 * SOH 00 0xFF FILE_NAME 0x00 FILE_LENGHT 0x00 .. 0x00 crc_hight crc_low * 其中, 文件长度使用字符串表示 * @param p_buf 原始数据流 * @param pout_filename 返回值:解码出的文件名 * @param pout_filename_len 返回值:解码出的文件名实际长度, 包含最后的0x00 * @param pout_filelsize 返回值:解码出的文件长度 * @return 0:解码成功; * 其它:解码失败. */ int ymodem_parse_fileinfo(uint8_t *p_buf, uint8_t *pout_filename, uint16_t *pout_filename_len, uint32_t *pout_filesize) { uint16_t i = 0; char * ptr; i = 0; ptr = p_buf + PACKET_HEADER_SIZE; while ((*ptr) != '\0' && i < FILE_NAME_LENGTH) // 名字的断位符为0 { pout_filename[i] = *(ptr++); i+=1; } pout_filename[i++] = '\0'; *pout_filename_len = i; ptr++; uint8_t filesize_str[FILE_SIZE_LENGTH]; i = 0; while ((*ptr) != ' ' && i < FILE_SIZE_LENGTH) // 长度的断位符为空格 { filesize_str[i] = *(ptr++); i+=1; } filesize_str[i++] = '\0'; str2int(filesize_str, pout_filesize); // 长度的字符串转为数值 return 0; } int ymodem_init(UART_HandleTypeDef *pUart) { huart_ymodem = pUart; } int ymodem_send_C() { static uint8_t tx = CRC16; UART_SENDCHAR(tx); } int ymodem_send_ACK() { static uint8_t tx = ACK; UART_SENDCHAR(tx); } int ymodem_send_NAK() { static uint8_t tx = NAK; UART_SENDCHAR(tx); } ``` ## 4. 实现了校验算法 2023-12-27 ```c uint16_t update_crc16(uint16_t crc_in, uint8_t byte) { uint32_t crc = crc_in; uint32_t in = byte | 0x100; do { crc <<= 1; in <<= 1; if (in & 0x100) ++crc; if (crc & 0x10000) crc ^= 0x1021; } while (!(in & 0x10000)); return crc & 0xffffu; } uint16_t cal_crc16(const uint8_t *p_dat, uint32_t size) { uint32_t crc = 0; const uint8_t *data_end = p_dat + size; while (p_dat < data_end) crc = update_crc16(crc, *p_dat++); crc = update_crc16(crc, 0); crc = update_crc16(crc, 0); return crc & 0xffffu; } /** * @brief 使用ymodem的crc16算法进行校验 * * @param p_dat 待校验的数据, 包含帧头和帧尾 * @param size 校验的数据长度 * @return 0: 校验通过; * 其它: 校验错误. */ int ymodem_crc_valid(uint8_t *p_dat, uint32_t size) { uint32_t crc; crc = p_dat[PACKET_DATA_INDEX + size] << 8; crc += p_dat[PACKET_DATA_INDEX + size + 1]; uint16_t calc_crc = cal_crc16(&p_dat[PACKET_DATA_INDEX], size); if ( calc_crc!= crc) { return -1; } return 0; } ``` ## 5. 自定义RAM代码段 在stm32工程中会有1个`.ld`文件, 其中定义了RAM和FLASH等的起始位置和大小定义. 对于stm32f103c8的工程, 这个文件名通常为`STM32F103C8TX_FLASH.ld` ### 5.1. ld文件中的MEMORY段 该段定义了RAM和FLASH的起始地址(`ORIGIN`)和长度(`LENGTH`), 这个不能乱改 ```c /* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 64K } ``` RAM的构成 ```c /** * Address Segment type * 0x20000000 Data * Bss Segment * ↓ heap * ↑ stack */ ``` ### 5.2. ld文件修改 注意新增加了一段`.myBufBlockRAM`以申请RAM空间, 申请的长度和起始位置要合理不能越过RAM的实际起点和大小. ```c /* Entry Point */ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */ _Min_Heap_Size = 0x200; /* required amount of heap */ _Min_Stack_Size = 0x400; /* required amount of stack */ /* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 64K } /*---------------------- by alp ----------------------*/ /** * Address Segment type * 0x20000000 Data * Bss Segment * ↓ heap * ↑ stack */ /*---------------------- by alp ----------------------*/ /* Sections */ SECTIONS { /* The startup code into "FLASH" Rom type memory */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); } >FLASH /*---------------------- by alp ----------------------*/ .myBufBlockRAM 0x20000100 (NOLOAD): /* 自定义并命名了1个RAM块, 只定义了起点 */ { KEEP(*(.myBufSectionRAM)) /* 即使未被引用也保留, 该段的长度在代码中通过 unsigned char __attribute__((section(".myBufSectionRAM"))) buf_ram[512]; 来定义和获取长度 */ } > RAM /*---------------------- by alp ----------------------*/ ``` ### 5.3. 在代码中定义RAM 在main.c中添加代码, 注意这个".myBufSectionRAM"要和.ld文件中的命名一致, buf_ram[128]中的128即为申请的大小B. 申请的长度和起始位置要合理不能越过RAM的实际起点和大小. ```c /* USER CODE BEGIN PV */ unsigned char __attribute__((section(".myBufSectionRAM"))) buf_ram[128]; /* ".myBufSectionRAM"要跟ld文件中KEEP(*(.myBufSectionRAM))一致 */ /* USER CODE END PV */ ``` ### 5.4. CubeIDE生成bin, hex文件配置 #### 5.4.1. CubeIDE配置 ![CubeIDE生成bin](image/readme/CubeIDE生成bin.png) #### 5.4.2. 无敌大的bin文件如果在.ld没有`(NOLOAD)` ![无敌大的bin文件](image/readme/无敌大的bin文件.png) ## 6. 自定义FLASH段 ### 6.1. ld文件修改 ```c /* Entry Point */ ENTRY(Reset_Handler) /* Highest address of the user mode stack */ _estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */ _Min_Heap_Size = 0x200; /* required amount of heap */ _Min_Stack_Size = 0x400; /* required amount of stack */ /* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K /* 申请RAM时要在这个ORIGIN地址基础上+偏移申请 */ FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 64K /* 申请FLASH时要在这个ORIGIN地址基础上+偏移申请 */ } /*---------------------- by alp ----------------------*/ /** * Address Segment type * 0x20000000 Data * Bss Segment * ↓ heap * ↑ stack */ /*---------------------- by alp ----------------------*/ /* Sections */ SECTIONS { /* The startup code into "FLASH" Rom type memory */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); } >FLASH /*---------------------- by alp ----------------------*/ .myBufBlockRAM 0x20000100 (NOLOAD): /* 自定义并命名了1个RAM块, 只定义了起点, 注意这个NOLOAD */ { KEEP(*(.myBufSectionRAM)) /* 即使未被引用也保留, 该段的长度在代码中通过 unsigned char __attribute__((section(".myBufSectionRAM"))) buf_ram[512]; 来定义和获取长度 */ } > RAM /*---------------------- by alp ----------------------*/ /*---------------------- by alp ----------------------*/ .myBufBlockFLASH 0x8001000 : /* 自定义并命名了1个FLASH块, 只定义了起点 */ { KEEP(*(.myBufSectionFLASH)) /* 即使未被引用也保留*/ } > FLASH /*---------------------- by alp ----------------------*/ /* The program code and other data into "FLASH" Rom type memory */ .text : { . = ALIGN(4); *(.text) /* .text sections (code) */ *(.text*) /* .text* sections (code) */ *(.glue_7) /* glue arm to thumb code */ *(.glue_7t) /* glue thumb to arm code */ *(.eh_frame) KEEP (*(.init)) KEEP (*(.fini)) . = ALIGN(4); _etext = .; /* define a global symbols at end of code */ } >FLASH ``` ### 6.2. main文件修改 ```c /* USER CODE BEGIN PV */ unsigned char __attribute__((section(".myBufSectionRAM"))) buf_ram[128]; /* ".myBufSectionRAM"要跟ld文件中KEEP(*(.myBufSectionRAM))一致 */ const unsigned char __attribute__((section(".myBufSectionFLASH"))) buf_flash[10] = { /* ".myBufSectionRAM"要跟ld文件中KEEP(*(.myBufSectionRAM))一致 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; /* USER CODE END PV */ ``` ### 6.3. 编译并查看内存分区情况 编译后可以在`Build Analyzer` > `Memory Detail`中查看到申请的FLASH和RAM. ![查看内存](image/readme/查看内存.png) ### 6.4. Debug并查看实际内存 #### 6.4.1. Debug工程 #### 6.4.2. 打开内存查看器 `Window` > `Show View` > `Other` > `Memory` ![打开内存查看器](image/readme/打开内存查看器.png) #### 6.4.3. 添加1个要查看的地址 ![查看的地址](image/readme/查看的地址.png) 在显示的列表框中可以看到以08001000起始的10个B内存中存储的是在main.c中定义的常量{0,1,2,3,4,5,6,7,8,9} ## 7. 将APP映射到指定的内存空间 ### 7.1. APP内存的申请 在`.ld`文件中修改`MEMORY DEFINITION`. 注意新增了一个`APP_FLASH`占用了原来FLASH的32K的空间, 因此原来FLASH的LENGTH从64K改为32K. ```c /* Memories definition */ MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K /* 申请RAM时要在这个ORIGIN地址基础上+偏移申请 */ FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 32K /* 原始值为64K, 分给下面APP32K,申请FLASH时要在这个ORIGIN地址基础上+偏移申请 */ APP_FLASH (rx) : ORIGIN = 0x8018000, LENGTH = 32K /* 新增用来存APP*/ } ``` ### 7.2. 修改section定义 `.ld`中关于Section定义的代码修改如下 ```c /* Sections */ SECTIONS { /* The startup code into "FLASH" Rom type memory */ .isr_vector : { . = ALIGN(4); KEEP(*(.isr_vector)) /* Startup code */ . = ALIGN(4); } >FLASH /*---------------------- by alp ----------------------*/ .myBufBlockRAM 0x20000100 (NOLOAD): /* 自定义并命名了1个RAM块, 只定义了起点, 注意这个NOLOAD */ { KEEP(*(.myBufSectionRAM)) /* 即使未被引用也保留, 该段的长度在代码中通过 unsigned char __attribute__((section(".myBufSectionRAM"))) buf_ram[512]; 来定义和获取长度 */ } > RAM /*---------------------- by alp ----------------------*/ /*---------------------- by alp ----------------------*/ .myBufBlockFLASH 0x8001000 : /* 自定义并命名了1个FLASH块, 只定义了起点 */ { KEEP(*(.myBufSectionFLASH)) /* 即使未被引用也保留*/ } > FLASH /*---------------------- by alp ----------------------*/ /*---------------------- by alp ----------------------*/ .appsection : /* app内存段 */ { . = ALIGN(4); __appsection_start__ = .; /* = 的两边注意留空格*/ *(.appsection*) __appsection_end__ = .; . = ALIGN(4); } > APP_FLASH /* 内存溢出检查 */ ASSERT(LENGTH(APP_FLASH) >= (__appsection_end__ - __appsection_start__), "APP FLASH memory overflowed!") /*---------------------- by alp ----------------------*/ /* * 后面的代码略 */ } ``` ### 7.3. APP_FLASH检查 重新编译后可以看到, 未编写APP代码前, 使用的内存为0. ![APP_FLASH检查](image/readme/APP_FLASH检查.png) ### 7.4. APP代码的实现并指定内存起点 在main.c中修改`USER CODE BEGIN PV`部分 ```c /* USER CODE BEGIN PV */ unsigned char __attribute__((section(".myBufSectionRAM"))) buf_ram[128]; /* ".myBufSectionRAM"要跟ld文件中KEEP(*(.myBufSectionRAM))一致 */ const unsigned char __attribute__((section(".myBufSectionFLASH"))) buf_flash[10] = { /* ".myBufSectionRAM"要跟ld文件中KEEP(*(.myBufSectionRAM))一致 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; #define LOCATE_FUNC __attribute__((section(".appsection"))) // 指定使用appsection作为后面函数的内存起点 /* USER CODE END PV */ ``` 在main.c中修改`USER CODE BEGIN 0`部分 ```c /* USER CODE BEGIN 0 */ void LOCATE_FUNC Blink(uint32_t dlyticks) { const char flag = '.'; HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); HAL_UART_Transmit(&huart1, (uint8_t *)&flag, 1, 10 ); HAL_Delay(dlyticks); } /* USER CODE END 0 */ ``` 在main.c的`main()`函数中修改主while循环的`USER CODE BEGIN WHILE`部分 ```c /* USER CODE BEGIN WHILE */ check_update(20, 5, &huart1); while (1) { Blink(500); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } ``` ### 7.5. app运行后的内存检查 新增加的代码编译成功后会发现APP_FLASH空间出现占用. ![app运行后的内存检查](image/readme/app运行后的内存检查.png) ## 8. 内部flash的擦写 ### 8.1. 解锁 代码写在bootloader判定应进入更新程序后. ```c // 解锁内存 HAL_StatusTypeDef update_status; update_status = HAL_FLASH_Unlock(); if(HAL_OK != update_status) { printf(">> flash unlock FAIL!!!\r\n"); printf(">> application update abortted...\r\n"); return -1; } else { printf(">> flash unlock SUCCESS!!!\r\n"); printf(">> ready for uploading...\r\n"); } ``` ### 8.2. 擦除 注意程序的写入地址(即擦除的起始地址)暂且写死为`0x08018000`. 代码写在bootloadet通过ymodem接收到文件信息包并校验成功之后. ```c // 通信建立且收信息包正常 // 擦除原有内存 // TODO: 内存中APP分2个区, A区运行, B区存储更新数据 // B区更新完成后置标志位为已下载新程序, 下一次上电将B区程序搬运到A区, 再置B区标志位为已更新 // FLASH擦除 // -------- // TODO: 擦除失败退出ymodem FLASH_EraseInitTypeDef eraseInitStruct; uint32_t sectorError; eraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; eraseInitStruct.PageAddress = 0x08018000; // 程序起点 eraseInitStruct.NbPages = 6; // 擦除页数 update_status = HAL_FLASHEx_Erase(&eraseInitStruct, §orError); if(HAL_OK != update_status) { return -1; } ``` ### 8.3. 调试检查擦除功能 - 刚上电时的内存 ![擦除前内存](image/readme/擦除前内存.png) - ymodem发送完信息包之后的内存 ![擦除成功后的内存](image/readme/擦除成功后的内存.png) ## 9. 内存写入 ### 9.1. 注意事项: - HAL_FLASH_Program()函数中第一个参数设置为`FLASH_TYPEPROGRAM_HALFWORD`后, 每次要写2个BYTE; - 对于ymodem写入的时候记得每1包前3个字节不是有效数据; - 对于ymodem最后一包不同的上位机有不同的实现, 有些是1024有些是128字节, 不要写入多余的0x1A了(TODO) ### 9.2. 代码实现 注意程序的写入地址暂且写死为`0x08018000`. 在`bootloader.c`的ymodem业务流程代码更新如下: ```c // 信息包和数据包处理 // ---------------- if (WAIT_UPLOADING == session) { ymodem_parse_fileinfo(data, file_name, &file_name_len, &file_size); // 通信建立且收信息包正常 // 擦除原有内存 // TODO: 内存中APP分2个区, A区运行, B区存储更新数据 // B区更新完成后置标志位为已下载新程序, 下一次上电将B区程序搬运到A区, 再置B区标志位为已更新 // FLASH擦除 // -------- // TODO: 擦除失败退出ymodem FLASH_EraseInitTypeDef eraseInitStruct; uint32_t sectorError; eraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES; eraseInitStruct.PageAddress = 0x08018000; // 程序起点 eraseInitStruct.NbPages = 16; // 擦除页数, 1页对应2K update_status = HAL_FLASHEx_Erase(&eraseInitStruct, §orError); if (HAL_OK != update_status) { return -1; } session = SEND_FILE_PACKET; // 信息包正常确认 ymodem_send_ACK(); ymodem_send_C(); continue; } else if (SEND_FILE_PACKET == session) { static uint32_t application_write_idx = 0x08018000; // 程序的起点 // TODO: 不写入ymodem最后一包带来的废数据 for (uint32_t i = 0; i < size / 2; i++) // CAUTION: 发送的文件必须2字节对齐 { uint32_t idx = PACKET_DATA_INDEX + i * 2; uint16_t halfword_data = data[idx] | (data[idx + 1] << 8); // 每次写2个B update_status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, application_write_idx, halfword_data); if (HAL_OK == update_status) { application_write_idx += 2; } else { // TODO: 内存错误, 退出更新 break; } } ymodem_send_ACK(); // 请求下一包 continue; } else // 发送结束包的情况 { ymodem_send_ACK(); // 结束确认 if (SEND_EOT <= session) { session = SEND_SUCCESS; break; } continue; } ``` ### 9.3. 写入效果 #### 9.3.1. 示例文件 ymodem.txt, 长度3300B, 内容为0~9循环, 示例如下 ``` 01234567890123456789012345678901234567890 ``` #### 9.3.2. 超级终端上位机 - 写入的起点`0x08018000`后面几个值为0x30, 0x31, 0x32...0x39即为'0', '1', '2' ... '9'的ASCII码, 与传入的文件一致 ![超级终端写入起点](image/readme/超级终端写入起点.png) - 写入的终点`0x08019000-1`前面的值为0x1A, 与ymodem最后一包补充的字符一致 ![超级终端写入的终点](image/readme/超级终端写入的终点.png) - 地址`0x08019000-1`到`0x08018000`差了`0x1000`个位置即10进制的4096个B, 则发送了4KB的数据, 与实际的3300B长度不一致, 后面的垃圾数据需要处理 #### 9.3.3. SecurCRT上位机 - 发送成功界面 ![SecurCRT上位机](image/readme/SecurCRT上位机.png) - 写入终点与超级终端不同, 地址为`0x08018D00-1`与起始位`0x0801000`差了`0xD00`个位置即10进制的3328个B(=1024×3+128+128), 即发送了3个1024长度的包加2个128字节长度的包 ![SecurCRT上位机写入终点](image/readme/SecurCRT上位机写入终点.png) ### 9.4. 小结 - 至此FLASH的擦除和写入成功 - 审慎使用未经确认的工具, 不然可能一直在坑里爬不出来 ## 10. 提出ymodem无效数 ### 10.1. 更新代码 修改ymodem业务流程文件数据包的处理代码, 增加下一个写入位置是否大于文件长度即可. ```c else if (SEND_FILE_PACKET == session) // 当前收到的是文件数据包, 开始写FLASH { static uint32_t application_start_address = 0x08018000; // 程序的起点 static uint32_t application_write_idx = 0; // 写入起点的偏移量 for (uint32_t i = 0; i < size / 2; i++) // CAUTION: 发送的文件必须2字节对齐 { uint32_t idx = PACKET_DATA_INDEX + i * 2; uint16_t halfword_data = data[idx] | (data[idx + 1] << 8); // 每次写2个B update_status = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, application_start_address + application_write_idx, halfword_data); if (HAL_OK == update_status) { application_write_idx += 2; } else { // TODO: 内存错误, 退出更新 break; } if (application_write_idx >= file_size) // 下一个写入的位置大于等于文件长度, 则表明上传完成 break; } ymodem_send_ACK(); // 请求下一包 continue; } ``` ### 10.2. 更新结果 再次编译调试, 并上传文件后, 新的终点地址为`0x0818CE3`, 与起点差了`0xCE3`即3299, 对应发了3300个BYTE. ![无效数据剔除](image/readme/无效数据剔除.png) ### 10.3. 小结 至此stm32f103c8t6内部flash通过ymodem更新的机制已经基本实现. 接下来要实现程序的跳转. ## 11. AP程序 app程序要做两个事情: - 更改代码RAM的地址 - 中断向量表更改 ### 11.1. 更改代码RAM的地址 在ld文件更改RAM定义: - 起始地址到对应bootloader中设置的app起始地址 - 长度与预留的APP代码长度一致 ![app程序ld文件](image/readme/app程序ld文件.png) ### 11.2. 中断向量表更改 在system_stm32f1xx.c中: - 取消`define USER_VECT_TAB_ADDRESS`的注释 - 中断向量表`VECT_TAB_OFFSET`更改偏移地址更改为0x18000U, 即app代码与bootload代码之间的距离 ![更改中断向量表](image/readme/更改中断向量表.png)