前言
本文的目的,使用SSCOM串口调试助手,通过USB转TTL,向单片机发送KEIL生成的 .hex文件,实现单片机程序的升级。本教程适用于不适合接烧录器、但是可以使用其它通信接口的场合,比如做车辆相关的,单片机的板子称为vcu、ecu等等,这些板子会装在一个盒子里面,一般不会预留SWD 或 JTAG烧录接口。本教程只讲解原理和代码,若工程应用中出现一些问题,本人概不负责!相关文档、程序均可在文章末尾白嫖下载。文中提到的文档、程序都可在文章结尾下载
1.关于STM32F103C8T6
1.1 STM32F103C8T6 Flash大小
Flash大小可以在CD00161566.pdf(STM32F103C8T6的datasheet)第108页(共117页)的第七章”7. Ordering information scheme“中查看,如下图所示,由此可知STM32F103C8T6 的Flash大小为64Kbytes
1.2 STM32F103C8T6属于STM32F10X_MD的原因
由上一节知道,STM32F103C8T6 的Flash大小为64Kbytes。在 stm32f10x.h头文件中,可以看到 STM32F103C8T6 属于STM32F10X_MD(Medium-density)产品线。虽然STM32F10X_MD_VL(Medium-density value line) 也包含64Kbytes Flash,但是它只能是 STM32F100xx相关名称的控制器,见下图
1.3 STM32F103C8T6 的 Flash空间
参考STM32F103C8T6参考手册文件CD00171190.pdf,3.3.3小节 Table 5. Flash module organization (medium-density devices),如下图所示
由此可知STM32F103C8T6的*整个64K FLASH分为 Page0 - Page 63,每个Page的Size是1KByte。
程序中宏定义如下
/* Base address of the Flash pages */
#define ADDR_PAGE_0 0x08000000
#define ADDR_PAGE_1 0x08000400
#define ADDR_PAGE_2 0x08000800
#define ADDR_PAGE_3 0x08000C00
#define ADDR_PAGE_4 0x08001000
#define ADDR_PAGE_5 0x08001400
#define ADDR_PAGE_6 0x08001800
#define ADDR_PAGE_7 0x08001C00
#define ADDR_PAGE_8 0x08002000
#define ADDR_PAGE_9 0x08002400
#define ADDR_PAGE_10 0x08002800
#define ADDR_PAGE_11 0x08002C00
#define ADDR_PAGE_12 0x08003000
#define ADDR_PAGE_13 0x08003400
#define ADDR_PAGE_14 0x08003800
#define ADDR_PAGE_15 0x08003C00
#define ADDR_PAGE_16 0x08004000
#define ADDR_PAGE_17 0x08004400
#define ADDR_PAGE_18 0x08004800
#define ADDR_PAGE_19 0x08004C00
#define ADDR_PAGE_20 0x08005000
#define ADDR_PAGE_21 0x08005400
#define ADDR_PAGE_22 0x08005800
#define ADDR_PAGE_23 0x08005C00
#define ADDR_PAGE_24 0x08006000
#define ADDR_PAGE_25 0x08006400
#define ADDR_PAGE_26 0x08006800
#define ADDR_PAGE_27 0x08006C00
#define ADDR_PAGE_28 0x08007000
#define ADDR_PAGE_29 0x08007400
#define ADDR_PAGE_30 0x08007800
#define ADDR_PAGE_31 0x08007C00
#define ADDR_PAGE_32 0x08008000
#define ADDR_PAGE_33 0x08008400
#define ADDR_PAGE_34 0x08008800
#define ADDR_PAGE_35 0x08008C00
#define ADDR_PAGE_36 0x08009000
#define ADDR_PAGE_37 0x08009400
#define ADDR_PAGE_38 0x08009800
#define ADDR_PAGE_39 0x08009C00
#define ADDR_PAGE_40 0x0800A000
#define ADDR_PAGE_41 0x0800A400
#define ADDR_PAGE_42 0x0800A800
#define ADDR_PAGE_43 0x0800AC00
#define ADDR_PAGE_44 0x0800B000
#define ADDR_PAGE_45 0x0800B400
#define ADDR_PAGE_46 0x0800B800
#define ADDR_PAGE_47 0x0800BC00
#define ADDR_PAGE_48 0x0800C000
#define ADDR_PAGE_49 0x0800C400
#define ADDR_PAGE_50 0x0800C800
#define ADDR_PAGE_51 0x0800CC00
#define ADDR_PAGE_52 0x0800D000
#define ADDR_PAGE_53 0x0800D400
#define ADDR_PAGE_54 0x0800D800
#define ADDR_PAGE_55 0x0800DC00
#define ADDR_PAGE_56 0x0800E000
#define ADDR_PAGE_57 0x0800E400
#define ADDR_PAGE_58 0x0800E800
#define ADDR_PAGE_59 0x0800EC00
#define ADDR_PAGE_60 0x0800F000
#define ADDR_PAGE_61 0x0800F400
#define ADDR_PAGE_62 0x0800F800
#define ADDR_PAGE_63 0x0800FC00
/**
0x0800 FC00 + 0x400 = 0x0801 0000
十六进制 0x1 0000 就是 十进制 65536, 65536 / 1024 = 64K
可以用以下代码打印上述定义
for(i = 0;i < 64;i++)
{
printf_with_DMA("#define ADDR_PAGE_%d 0x0800%04X\r\n", i, i * 0x400);
}
**/
2.hex文件简介
扫盲文章请参考 link,强烈建议拜读,本文只做简单讲解
打开一个hex文件,删除中间部分,保留部分头和尾如下
整个hex文件一般有很多行,一般可以分为 4类。
第一类,表示此hex文件存放flash地址的高16位,即上图第1行
第二类,包含此hex文件存放flash地址的低16位,以及要存放的数据,即上图2 - 6行
第三类,不做解释,上图第7行
第四类,hex文件结束字符串 “:00000001FF”,上图第8行
第1行,02表示当前行数据有效数据Byte个数为0x02;接着 0000 04不做解释,请看上述扫盲文章;0800(当前行的有效数据)表示当前hex文件编译出来的32位flash基地址高16位为0x0800;行尾F2表示当前行的校验码,每行皆如此。
第2行,10表示当前行数据有效数据Byte数量为0x10(十进制就是16);0000表示当前行16个Byte数据 900600203D0200088108000879080008 存放 首地址 的低16位为 **0x0000 **,说人话就是当前行16Byte的数据存放地址为 0x0800 0000 - 0x0800 000F(长度16)
第3行 - 第5行同第2行
第6行,08表示当前行数据有效数据Byte数量为0x08(十进制就是8);1310表示当前行8个Byte数据0102030406070809 存放首地址 的低16位为 **0x1310 **,即当前行8Byte的数据 存放地址为 0x0800 1310- 0x0800 1317(长度8)
第7行,不做解释
第8行,表示文件结束
上面说了一堆,简答来说,在keil烧录程序过程中,相当于在 0x0800 0000 – 0x0800 1317地址存放第2行的数据“90060020…” 至倒数第3行的“ 0102030406070809”。单片机重启后,会从0x0800 0000处执行程序。
注意 : 上图中,第1行是从 : 开始发送,F2发完后,会接着发送 \r\n
同理,第2行也是从 : 开始发送,D9发完后,会接着发送 \r\n,
sscom每次会累计发送256字节,插入设置的延时后,继续下一次256字节的发送,直至整个hex文件发送完毕
当最后一帧小于等于256字节时,有多少发多少
3.硬件准备
- 一块STM32F103C8T6的开发板,需要一个SWD(或JTAG)烧录接口 和 一个UART,一个按键输入
我这里UART用的是USART1(PA9 和 PA10),按键是PC13 - 一个ST-LINK,或者其它任意能给STM32烧写程序的烧录器
- 一个USB转TTL
4.IAP APP FLASH空间分配
STM32F103C8T6的FLASH大小总共是64Kbytes,前32K空间作为IAP程序,后32K空间作为app程序
IAP程序功能:
程序上电后,如果按键按下,则擦除后32K空间的app程序,然后进入while(1)循环接收、处理sscom发过来的hex文件;如果按键没有按下,则跳转到后32K空间执行app程序
5. sscom 发送hex文件设置
app程序功能:
while(1)循环内,每0.5秒打印一次printf一次
sscom在发送hex文件时,需要如下图所示设置,我测试的是除了第一个“每发送256字节延时1ms”,剩下三个都可行
6. 循环数组简介
iap程序在接收hex文件时,需要定义一个循环数组 和 一个无符号16位变量
uint8_t uc_iap_loop_buf[8];//实际定义长度1024,这里写8是便于讲解原理
uint16_t us_uart_iap_rx_next_index = 0;
以下步骤说明上述两个变量的变化
6.1 发送字符串 “123” 长度3
index 0 1 2 3 4 5 6 7
uc_iap_loop_buf '1' '2' '3' 0 0 0 0 0
us_uart_iap_rx_next_index = 0 + 3,下次存放的数组下标是3
6.2 发送字符串 “456” 长度3
index 0 1 2 3 4 5 6 7
uc_iap_loop_buf '1' '2' '3' '4' '5' '6' 0 0
us_uart_iap_rx_next_index = 3 + 3,下次存放的数组下标是6
可以看到 us_uart_iap_rx_next_index 的值是 uc_iap_loop_buf 下次将要存放数据的数组下标
6.3 发送字符串 “ABC” 长度3
index 0 1 2 3 4 5 6 7
uc_iap_loop_buf 'C' '2' '3' '1' '2' '3' 'A' 'B'
易见,
'A' 'B' 要放在下标6 7, 'C'要放在下标0
下一帧要放的下标us_uart_iap_rx_next_index 值就成了1,
因为6 + 3 大于等于 8,所以计算方法: us_uart_iap_rx_next_index = 6 + 3 - 8 = 1
6.4 发送字符"1234567" 长度7
index 0 1 2 3 4 5 6 7
uc_iap_loop_buf 'C' '1' '2' '3' '4' '5' '6' '7'
易见,'1’ ‘2’ ‘3’ ‘4’ ‘5’ ‘6’ ‘7 要放在下标1 2 3 4 5 6 7
因为1 + 7 大于等于 8,所以计算方法: us_uart_iap_rx_next_index = 1 + 7 - 8 = 0
下次再接收,就是从 uc_iap_loop_buf 的首地址存放了
6.5 发送字符"12345678" 长度8
index 0 1 2 3 4 5 6 7
uc_iap_loop_buf '1' '2' '3' '4' '5' '6' '7' '8'
易见,'1' '2' '3' '4' '5' '6' '7' '8' 要放在下标0 1 2 3 4 5 6 7
因为0 + 8 大于等于 8,所以计算方法: us_uart_iap_rx_next_index = 0 + 8 - 8 = 0
下次再接收,依旧是从 uc_iap_loop_buf 的首地址存放了
以上5步展示了循环数组的数据存放,对后续程序的解读很有帮助。
在IAP程序中,uc_iap_loop_buf 数组定义的长度为1024,其实比256大就行,因为sscom可选的是 “每发送256字节延时 x ms”。
8. iap程序main.c内的函数
#include "stm32f10x.h"
#include "board_config.h"
#include "stdio.h"
#include "math.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
typedef void(*pFunction)(void);
pFunction pFun_Jump_To_App;
/*
IAP程序中使用,使用之前,尽量不要开中断,否则可能会跳转失败
传入的参数就是app的首地址,
查看 “IAP APP FLASH空间分配”和“STM32F103C8T6 的 Flash空间” 章节,
本文使用的app首地址是 ADDR_PAGE_32 0x08008000
*/
void jump_to_user_app(uint32_t ul_addr)
{
uint32_t ul_jumpAddress;
if(((*(__IO uint32_t*)ul_addr) & 0x2FFE0000) == 0x20000000)// check app Flash exist APP program or not
{
ul_jumpAddress = *(__IO uint32_t*)(ul_addr + 4);
pFun_Jump_To_App = (pFunction)ul_jumpAddress;
__set_MSP(*(__IO uint32_t*)ul_addr);
pFun_Jump_To_App();
}
else
{
while(1)
{
//do something here
}
}
}
/*
USART1配置DMA发送、接收,且USART1只开了一个空闲中断
当空闲中断发生,uc_uart_iap_rx_ok_flag置位
while(1)内,查询到uc_uart_iap_rx_ok_flag为真后,
把uc_uart_iap_rx_buf的内容存放到循环数组中,并更新全局变量 p_rx_tail 的值
然后,for循环执行10次以下函数,其实执行6次就足够了
因为:10000000900600203D0200088108000879080008D9\r\n 的长度是 45
256 / 45 = 5.688888
*/
void process_sscom_dat(void)
{
uint8_t* p1 = NULL;
uint8_t* p2 = NULL;
uint8_t* p3 = NULL;
uint8_t* p_tmp_rx_tail = p_rx_tail;
uint32_t ul_tmp_len = 0;
uint8_t uc_format_type = 0;
uint8_t uc_tmp_check_sum_value = 0;
if(p_tmp_rx_tail != p_rx_head)
{
sys_memset(uc_cur_process_str, 0, UART_IAP_RX_BUFF_SIZE);//uc_cur_process_str 定义的 size 是 1024
if(p_tmp_rx_tail > p_rx_head)//尾在头的后面,这总情况比较简单;但是copy时,要注意包含头尾
{
/*
从头到尾,包含头尾,copy 到 uc_cur_process_str
012345678
| |
| |tail
|
|head
*/
sys_memcpy(uc_cur_process_str, p_rx_head, p_tmp_rx_tail - p_rx_head + 1);
uc_format_type = 1;//type 赋值1 ,表示 尾在头的后面
}
else// if(p_rx_tail < p_rx_head)//尾在头的前面,这种情况要 copy 两次
{
/*
012345678
| |
| |head
|
|tail
第一次copy,可以理解成,先把数组下标 6 7 8复制到uc_cur_process_str;
第二次copy,把下标 0 1的内容拼接到 uc_cur_process_str后面,注意包含头尾
p_rx_head - uc_iap_loop_buf 表示的是 全局指针 p_rx_head 到 循环数组 uc_iap_loop_buf 首地址的长度
类看上述注释 6 - 0,所以 p_rx_head - uc_iap_loop_buf 就是 6
UART_IAP_RX_BUFF_SIZE 减去上一行,就是 循环数组 uc_iap_loop_buf 尾到 p_rx_head 的长度
类看上述注释 9 - 6,所以 UART_IAP_RX_BUFF_SIZE - (p_rx_head - uc_iap_loop_buf) 就是 3
也就是第一次copy,把 下标 6 7 8三个内容,从uc_cur_process_str首地址开始存放
*/
sys_memcpy(uc_cur_process_str, p_rx_head, UART_IAP_RX_BUFF_SIZE - (p_rx_head - uc_iap_loop_buf));
/*
第二次copy
第一次copy, 已经把循环数组 uc_iap_loop_buf 后部分复制到了uc_cur_process_str ,
这一次是要把 uc_iap_loop_buf 首地址 到 全局指针 p_rx_tail的内容,拼接到 uc_cur_process_str 后面
上一行sys_memcpy第三个参数 “UART_IAP_RX_BUFF_SIZE - (p_rx_head - uc_iap_loop_buf)” 表示长度,
下一行sys_memcpy第一个参数中的 “UART_IAP_RX_BUFF_SIZE - (p_rx_head - uc_iap_loop_buf)” 表示 数组下标
所以,下一行第一个参数不需要 +1;
第二个参数当然就是 uc_iap_loop_buf 的首地址了;
第三个参数,要把p_rx_tail所指向内容也 copy进去,所以要 +1
012345678
| |
| |head
|
|tail
参照上面注释,下一个 copy
第一个参数指向的是 uc_cur_process_str[3],从第4个元素开始存放
第三个参数,就是1 - 0 + 1 = 2,也就是将下标0 1,两个元素,拼接到uc_cur_process_str后面
*/
sys_memcpy(uc_cur_process_str + UART_IAP_RX_BUFF_SIZE - (p_rx_head - uc_iap_loop_buf), uc_iap_loop_buf, p_tmp_rx_tail - uc_iap_loop_buf + 1);
ul_tmp_len = UART_IAP_RX_BUFF_SIZE - (p_rx_head - uc_iap_loop_buf);// 0a --- end num
/*
p1 p2 p3都是指向 uc_cur_process_str 的局部指针变量
p1 用于查找 uc_cur_process_str 中的 ':'
p2 用于查找 uc_cur_process_str 中的 "\r\n"
p3 指向 uc_cur_process_str 的 上述注释的 下标 1,后面配合p2用于计算更新 p_rx_head
*/
p3 = &uc_cur_process_str[ul_tmp_len - 1];
uc_format_type = 2;//type 赋值2 ,表示 尾在头的前面
}
p1 = my_strstr_1_byte(uc_cur_process_str, ':');//uc_cur_process_str首地址查找 ':'
if(p1 != NULL)
{
//从p1开始查找 "\r\n", '\n'的十进制是10,而十进制10不会以数据格式出现在一帧数据中,它只能是sscom发送的换行
p2 = my_strstr_1_byte(p1, '\n');
if(p2 != NULL)
{
/*
uc_ori_str_fream_table定义的长度是64
hex文件中,':'开始, '\n'结尾的字符串长度,最长不超过45,参考 “hex文件简介” 章节
10 20
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
:10519000000000000000000000000000000000000F\r\n
可以看到,一帧 uc_ori_str_fream_table 最多有21个字节的数据
注意包含头尾,所以下一行第三个参数要+1
*/
sys_memcpy(uc_ori_str_fream_table, p1, p2 - p1 + 1);//:10519000000000000000000000000000000000000F\r\n
/*
uc_hex_table 定义的长度是21,str_to_hex 函数的第一个参数+1,是因为 uc_ori_str_fream_table 的首元素是 ':'
*/
str_to_hex(uc_ori_str_fream_table + 1, uc_hex_table);
//计算校验和,参考hex文件简介 中的 “扫盲文”章
uc_tmp_check_sum_value = hex_check_sum(uc_hex_table, uc_hex_table[0] + 4);
if(uc_tmp_check_sum_value == uc_hex_table[uc_hex_table[0] + 4])
{
if(uc_hex_table[3] == 0x00)// 0x00 表示解析出来的此 uc_hex_table 是要保存在flash中的数据
{
if(uc_flash_build_start_addr_flag)
{
uc_flash_build_start_addr_flag = 0;//此变量除了定义和声明,只在此行和上一行出现
ul_tmp_len = uc_hex_table[1]; ul_tmp_len <<= 8;
ul_tmp_len |= uc_hex_table[2];
//ul_tmp_len : keil编译出来的hex文件,要保存的Flash的首地址的低16位
ul_flash_build_start_addr |= ul_tmp_len;
}
//要写入flash的数据,以uint32_t 格式,存放在 ul_flash_buff 中
sys_memcpy((uint8_t*)&ul_flash_buff[us_flash_buff_index], uc_hex_table + 4, uc_hex_table[0]);
us_flash_buff_index += uc_hex_table[0] / 4;
//本文宏定义的 NUM_OF_WRITE_FLASH 是 256, 256 * 4 = 1024(1KByte),表示存储256个uint32_t 类型数据后,写入flash
if(us_flash_buff_index >= NUM_OF_WRITE_FLASH)
{
//第一个参数,全局变量,当前要写入的flash的地址,初值为 app 程序的首地址,即 ADDR_PAGE_32
EE_app_flash_program(&ul_app_flash_program_addr,ul_flash_buff, us_flash_buff_index);
ul_program_num_of_uint32 += us_flash_buff_index;//等号左边,往flash内写入的 uint32_t 类型数据的个数
us_flash_buff_index = 0;
printf_with_DMA("ul_app_flash_program_addr = 0x%08X\r\n", ul_app_flash_program_addr);
}
}
/*
以下判断,判断hex文件的第一行,获取 keil编译出来的hex文件,要保存的Flash的首地址的高16位
*/
else if( (uc_hex_table[0] == 0x02) && \
(uc_hex_table[1] == 0x00) && \
(uc_hex_table[2] == 0x00) && \
(uc_hex_table[3] == 0x04))
{
ul_tmp_len = uc_hex_table[4]; ul_tmp_len <<= 8;
ul_tmp_len |= uc_hex_table[5]; ul_tmp_len <<= 16;
ul_flash_build_start_addr = ul_tmp_len;//获取 keil编译出来的hex文件,要保存的Flash的首地址的高16位
}
/*
此处为第二个 else if,
如果 uc_ori_str_fream_table
既不是 要保存在Flash的数据,
又不是 hex文件的第一行,
那就判断当前 uc_ori_str_fream_table 是否包含 hex文件字符串 ":00000001FF"
*/
else if(NULL != my_strstr_my(uc_ori_str_fream_table, uc_hex_end_str))
{
//已经是hex文件的最后结束字符了,只要 us_flash_buff_index 为真,就表示有数据要写入app FLASH
if(us_flash_buff_index)
{
EE_app_flash_program(&ul_app_flash_program_addr,ul_flash_buff, us_flash_buff_index);
ul_program_num_of_uint32 += us_flash_buff_index;
us_flash_buff_index = 0;
}
/* 打印一些解析出来的信息 */
printf_with_DMA("\r\nus_uart_iap_rec_fream_view_only_cnt = %d\r\n", us_uart_iap_rec_fream_view_only_cnt);
//整个iap过程中,接收到的桢数,或者iap串口 空闲中断的次数
//在while(1)内,判断uc_uart_iap_rx_ok_flag为真后,累加一次
printf_with_DMA("us_uart_iap_idle_view_only_irq_cnt = %d\r\n", us_uart_iap_idle_view_only_irq_cnt);
//整个iap过程中,iap串口 空闲中断的次数,在iap串口空闲中断中,累加一次
printf_with_DMA("ul_uart_iap_rxne_view_only_cnt = %d Byte\r\n", ul_uart_iap_rxne_view_only_cnt);
//整个iap过程中,iap串口 接收的字节数,sscom加载hex文件后,在sscom上会统计当前hex文件要发送的字节总数
//此变量 和 sscom上统计当前hex文件要发送的字节总数 相等
//在while(1)内,判断uc_uart_iap_rx_ok_flag为真后,ul_uart_iap_rxne_view_only_cnt += us_uart_iap_rx_len;
printf_with_DMA("ul_flash_build_start_addr = 0x%08X\r\n", ul_flash_build_start_addr);
/*
keil编译出来的hex文件,要存放的 flash 首地址
if(uc_flash_build_start_addr_flag) 和
else if( (uc_hex_table[0] == 0x02) && \
(uc_hex_table[1] == 0x00) && \
(uc_hex_table[2] == 0x00) && \
(uc_hex_table[3] == 0x04))
两处,分别获取低16位和高16位
*/
printf_with_DMA("ul_image_entry_point = 0x%08X\r\n", ul_image_entry_point);
//hex文件解析出来的 image entry point,紧接着下面一个else if 判断解析出来的,我也不知道什么意思
printf_with_DMA("ul_app_flash_program_addr = 0x%08X\r\n", ul_app_flash_program_addr);
//下一次要写入flash的地址
printf_with_DMA("ul_program_num_of_uint32 = %d\r\n", ul_program_num_of_uint32);
//整个iap过程中,往Flash中写入的 uint32_t 类型数据的总个数
printf_with_DMA("ul_check_ok_cnt = %d\r\n", ul_check_ok_cnt);
//整个iap过程中,if(uc_tmp_check_sum_value == uc_hex_table[uc_hex_table[0] + 4])判断成功的次数
printf_with_DMA("ul_check_err_cnt = %d\r\n", ul_check_err_cnt);
//整个iap过程中,if(uc_tmp_check_sum_value == uc_hex_table[uc_hex_table[0] + 4])判断不成功的次数
printf_with_DMA("us_flash_buff_index = %d\r\n", us_flash_buff_index);
//这里应该是0,因为 if(us_flash_buff_index) 中,此变量已经清零了
printf_with_DMA("\r\n\r\n\r\n\r\n############# write last flash data!!! RESET #############\r\n\r\n\r\n\r\n\r\n\r\n");
__set_PRIMASK(1); // disable all interrupt
NVIC_SystemReset(); // reset
}
else if( (uc_hex_table[0] == 0x04) && \
(uc_hex_table[1] == 0x00) && \
(uc_hex_table[2] == 0x00) && \
(uc_hex_table[3] == 0x05))
{
ul_tmp_len = uc_hex_table[4]; ul_tmp_len <<= 8;
ul_tmp_len |= uc_hex_table[5]; ul_tmp_len <<= 8;
ul_tmp_len |= uc_hex_table[6]; ul_tmp_len <<= 8;
ul_tmp_len |= uc_hex_table[7];
//获取 image entry point
ul_image_entry_point = ul_tmp_len;
}
if(uc_format_type == 1)//尾在头的后面,且尾和头的差小于 1024
{
/*
之前
uc_iap_loop_buf 01234567890123456789
| |tail
|head
p2 指向的是 '\n'
*/
p_rx_head += p2 - uc_cur_process_str;
}
else
{
if(p2 <= p3)
{
p_rx_head += p2 - uc_cur_process_str;
}
else
{
p_rx_head = uc_iap_loop_buf;
p_rx_head += p2 - p3 - 1;
}
}
ul_check_ok_cnt++;
// printf_with_DMA("\r\n**********************check ok******************\r\n");// do not printf
}
else
{
ul_check_err_cnt++;
printf_with_DMA("\r\n**********************check err!!! ul_check_ok_cnt = %d******************\r\n", ul_check_ok_cnt);
}
}
else// error : p2 no \n
{
}
}
else// error : p1 no ':'
{
}
}
}
int main(void)
{
uint8_t i = 0;
uint8_t uc_is_iap_flag = 0;
sys_rcc_config_8MHz_with_source_HSI_64MHz();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
sys_gpio_config();//GPIOC 13 配置为输入,接按键
SysTick_Delay_ms(50);
if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == Bit_RESET)// Bit_SET Bit_RESET
{
sys_uart1_config();//配置usart1, 配置dma 发送 接收,只开启 usart1 的空闲中断,整个程序也只有这一个中断
p_rx_head = uc_iap_loop_buf;
p_rx_tail = uc_iap_loop_buf;
printf_with_DMA("\r\n\r\n\r\nenter iap, flash_erase_app\r\n\r\n\r\n");
i = flash_erase_app();//擦除 app 区域的 flash,返回值是擦除过程中,擦除错误 page 时的次数
if(i > 0)
{
printf_with_DMA("\r\n\r\n\r\nerase app flash err!!! restart!!!\r\n\r\n\r\n");
__set_PRIMASK(1); // disable all interrupt
NVIC_SystemReset(); // reset
}
else
{
printf_with_DMA("\r\n\r\n\r\nerase app flash complete!!!\r\n\r\n\r\n");
}
uc_is_iap_flag = 1;
ul_app_flash_program_addr = ul_app_addr;
printf_with_DMA("\r\n\r\nul_app_flash_program_addr = 0x%08X\r\n\r\nwaiting for hex file......\r\n", ul_app_flash_program_addr);
}
if(uc_is_iap_flag == 1)
{
while(1)
{
if(uc_uart_iap_rx_ok_flag)//空闲中断发生了
{
uc_uart_iap_rx_ok_flag = 0;
//整个iap过程中,接收到的桢数,只看变量,在 hex 文件接收完毕,打印这个变量的值
us_uart_iap_rec_fream_view_only_cnt++;
//整个iap过程中,iap串口 接收的字节数,只看变量,在 hex 文件接收完毕,打印这个变量的值
ul_uart_iap_rxne_view_only_cnt += us_uart_iap_rx_len;
//下次写入的下标,加上 当前 接收桢的长度,小于 UART_IAP_RX_BUFF_SIZE,直接拼接在循环数组后面
if((us_uart_iap_rx_next_index + us_uart_iap_rx_len) < UART_IAP_RX_BUFF_SIZE)
{
sys_memcpy(&uc_iap_loop_buf[us_uart_iap_rx_next_index], uc_uart_iap_rx_buf, us_uart_iap_rx_len);
us_uart_iap_rx_next_index += us_uart_iap_rx_len;
}
//下次写入的下标,加上 当前 接收桢的长度,大于 UART_IAP_RX_BUFF_SIZE,要分两次拼接
else if((us_uart_iap_rx_next_index + us_uart_iap_rx_len) > UART_IAP_RX_BUFF_SIZE)
{
//第一次,从下标 us_uart_iap_rx_next_index 开始,拼接到 uc_iap_loop_buf 的尾
sys_memcpy(&uc_iap_loop_buf[us_uart_iap_rx_next_index], \
uc_uart_iap_rx_buf, \
UART_IAP_RX_BUFF_SIZE - us_uart_iap_rx_next_index);
//第二次,从 uc_iap_loop_buf 开始,拼接到 uc_iap_loop_buf 的尾
sys_memcpy(&uc_iap_loop_buf[0], \
&uc_uart_iap_rx_buf[UART_IAP_RX_BUFF_SIZE - us_uart_iap_rx_next_index], \
us_uart_iap_rx_len - (UART_IAP_RX_BUFF_SIZE - us_uart_iap_rx_next_index));
us_uart_iap_rx_next_index = us_uart_iap_rx_len - (UART_IAP_RX_BUFF_SIZE - us_uart_iap_rx_next_index);
}
else
{
sys_memcpy(&uc_iap_loop_buf[us_uart_iap_rx_next_index], uc_uart_iap_rx_buf, us_uart_iap_rx_len);
us_uart_iap_rx_next_index = 0;
}
if(us_uart_iap_rx_next_index == 0)//如果下次要存放的循环数组的下标是0,那么 p_rx_tail 就指向循环数组的尾元素
{
p_rx_tail = &uc_iap_loop_buf[UART_IAP_RX_BUFF_SIZE - 1];
}
/*
如果下次要存放的循环数组的下标不是0,
假如是1,那么那么 p_rx_tail 就指向uc_iap_loop_buf[0]
假如是2,那么那么 p_rx_tail 就指向uc_iap_loop_buf[1]
......
假如是n,那么那么 p_rx_tail 就指向uc_iap_loop_buf[n - 1]
*/
else
{
p_rx_tail = &uc_iap_loop_buf[us_uart_iap_rx_next_index - 1];
}
/*
每接收一帧256字节,执行10次 process_sscom_dat() 函数,实际6次就够了
*/
for(i = 0; i < 10;i++)
{
process_sscom_dat();
}
}
}
}
jump_to_user_app(ul_app_addr);
}
7.循环数组首、尾指针
首指针:p_rx_head
尾指针: p_rx_tail
#define UART_IAP_RX_BUFF_SIZE 1024
uint8_t uc_iap_loop_buf[UART_IAP_RX_BUFF_SIZE];//循环数组
uint8_t uc_cur_process_str[UART_IAP_RX_BUFF_SIZE];//当前要处理的数组
uint8_t uc_ori_str_fream_table[64];
uint8_t* p_rx_head = NULL;
uint8_t* p_rx_tail = NULL;
//main 函数内初始化
p_rx_head = uc_iap_loop_buf;
p_rx_tail = uc_iap_loop_buf;
为便于讲解,这里把宏定义 UART_IAP_RX_BUFF_SIZE 看成是500,
程序初始化后,p_rx_head 和 p_rx_tail都指向循环数组的首地址。
假如 uc_iap_loop_buf 在 RAM 中的首地址是 0x2000 0000,尾地址是 0x2000 01F3,长度500(0x01F3 = 499)。
忽略高16位,只看低16位,那么 uc_iap_loop_buf 的地址范围就是 0 - 499,
接下来,sscom每次发送256字节,发5次,以此看 p_rx_head 和 p_rx_tail 所指向的地址
第一次发256字节
10 20 30 40
1234567890123456789012345678901234567890123 4 5
:10000000900600203D0200088108000879080008D9\r\n 的长度是 45
:10000000900600203D0200088108000879080008D9\r\n 的长度是 45
:10000000900600203D0200088108000879080008D9\r\n 的长度是 45
:10000000900600203D0200088108000879080008D9\r\n 的长度是 45
:10000000900600203D0200088108000879080008D9\r\n 的长度是 45
10 20 30 40
1234567890123456789012345678901234567890123 4 5
:10000000900600203D0200088108000879080008D9\r\n 此行只发31个字节
因为45 * 5 + 31 = 256
接收完以上256字节后,
p_rx_head 依然是 0
p_rx_tail 成了255
us_uart_iap_rx_next_index 成了 256
也就是当前循环数组内,有5桢数据需要解析。
程序把这256字节存放在 uc_cur_process_str 数组内。
然后程序查找 uc_cur_process_str 内的 ':',
若查找到了,则再往后查找 \n。两次都查到的话,
就把从':'开始 到 \n 结尾的字符串存放到 uc_ori_str_fream_table 数组中。
接着调用 uint8_t str_to_hex(uint8_t* uc_src, uint8_t* uc_dest); 函数,
因为uc_cur_process_str 第一个元素是':',所以传递的第一个参数是 uc_cur_process_str + 1,
就可以得到如下16进制数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
10 00 00 00 90 06 00 20 3D 02 00 08 81 08 00 08 79 08 00 08 D9
拿到这21个数据之后,按照 uint8_t hex_check_sum(uint8_t* pdat, uint8_t uc_len) 函数进行校验,
检验通过则继续,否则做相应出错处理
第5个到第20个,共16个字节,是要存放在FLASH区域内的,可以得到两个uint32_t 类型的数据。
p_rx_head += p2 - uc_cur_process_str;