STM32F103C8T6 SSCOM发送HEX文件升级

前言

本文的目的,使用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.pdf3.3.3小节 Table 5. Flash module organization (medium-density devices),如下图所示
在这里插入图片描述

由此可知STM32F103C8T6的*整个64K FLASH分为 Page0 - Page 63,每个PageSize1KByte

程序中宏定义如下

/* 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.硬件准备

  1. 一块STM32F103C8T6的开发板,需要一个SWD(或JTAG)烧录接口 和 一个UART,一个按键输入
    我这里UART用的是USART1(PA9 和 PA10),按键是PC13
  2. 一个ST-LINK,或者其它任意能给STM32烧写程序的烧录器
  3. 一个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;

8.未完待续。。。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值