13.5-“制作一款私有IAP串口下载小工具”之STM32的Bootloader代码编写(包含源码)

一、开发环境与工具

STM32CubeMX、TrueSTUDIO、J-Flash V6.32f
芯片:STM32L072KBUx

二、使用CubeMx新建工程与配置说明

使用CubeMX通过芯片型号新建工程。需要配置如下内容:

  • 配置系统时钟
  • 下载程序的SWD口
  • 一个定时器(开启中断)
  • 一个串口(开启接收中断)

配置过程及详细参数如以下截图所示:
注意:只要能实现同样功能即可,不强制必须使用串口1或定时器7。
13.5-“制作一款私有IAP串口下载小工具”之STM32的Bootloader代码编写(包含源码)_第1张图片
13.5-“制作一款私有IAP串口下载小工具”之STM32的Bootloader代码编写(包含源码)_第2张图片
13.5-“制作一款私有IAP串口下载小工具”之STM32的Bootloader代码编写(包含源码)_第3张图片
13.5-“制作一款私有IAP串口下载小工具”之STM32的Bootloader代码编写(包含源码)_第4张图片
13.5-“制作一款私有IAP串口下载小工具”之STM32的Bootloader代码编写(包含源码)_第5张图片13.5-“制作一款私有IAP串口下载小工具”之STM32的Bootloader代码编写(包含源码)_第6张图片

三、通信协议构架代码

在单片中需要接收来自上位机的数据,并安装协议规定格式反馈数据。比上位机端简单一些。
把串口接收到的每1byte数据,依次传递给以下这个“协议识别函数”来进行“封包”识别:

void APP_UartProcess(uint8_t uart_data) {

	if(IsStartRecvCommand == true) {
		UartRxCache[UartRxCacheIndex++] = uart_data;

		uint16_t TempCacheLen = UartRxCacheIndex;
		if(TempCacheLen > 4 &&
				UartRxCache[TempCacheLen - 4] == TAIL[0] &&
				UartRxCache[TempCacheLen - 3] == TAIL[1] &&
				UartRxCache[TempCacheLen - 2] == TAIL[2] &&
				UartRxCache[TempCacheLen - 1] == TAIL[3] &&
				UartRxCache[sizeof (HEAD)] + sizeof (HEAD) + sizeof (TAIL)  == TempCacheLen //check pkg len

		) { //检测到一包接收完成
			ProcessAFinishedPackage();
			UartRxCacheIndex = 0;
			IsStartRecvCommand = false;
		}

	} else if(uart_data == HEAD[0] && IsStartRecvCommand == false) { //only receiving command when start with a head
		IsStartRecvCommand = true;
		UartRxCache[UartRxCacheIndex++] = uart_data;
	}
}

当单片机需要给上位机反馈命令的时候,使用以下函数计算出“反馈封包”:

/**
 * @brief	获取命令反馈封包,返回数据包长度
 * @param	resPkg 参数详情
 * @retval	反馈封包最终长度, -1 缓存数组长度不够
 */
int APP_GetProtocolResponsePKG(ResponsePkg *resPkg) {

	//CRC校验码 = [命令类型码 + 命令码 + 命令参数]
	//计算方括号内的数据
	uint16_t res_data_len = resPkg->param_len + 2;
	uint8_t res_data[res_data_len];
	res_data[0] = resPkg->command_type;
	res_data[1] = resPkg->command;
	for (int a = 0; a < resPkg->param_len; a++) {
		res_data[2 + a]  = resPkg->param_buf[a];
	}

	//计算缓存数据是否够用
	uint16_t buffer_need_len = res_data_len + 8;
	if(buffer_need_len > resPkg->res_buffer_len) return -1;

	//CRC校验码
	uint16_t crc;
	GetCRC(res_data, res_data_len, &crc);
	uint8_t crc_high_byte = (crc >> 8) & 0xff;
	uint8_t crc_low_byte = crc & 0xff;

	//组装封包
	uint8_t* res_buf = resPkg->res_buffer;
	int res_buf_index = 0;

	res_buf[res_buf_index++] = HEAD[0];
	res_buf[res_buf_index++] = 0;//temp pkg len

	for (int a = 0; a < res_data_len; a++) {
		res_buf[res_buf_index++]  = res_data[a];
	}
	res_buf[res_buf_index++] = crc_high_byte;
	res_buf[res_buf_index++] = crc_low_byte;

	uint8_t tail_buf_len = sizeof(TAIL);
	for (int a = 0; a < tail_buf_len; a++) {
		res_buf[res_buf_index++]  = TAIL[a];
	}

	//计算包长
	uint8_t head_len = sizeof(HEAD);
	uint8_t tail_len = sizeof(TAIL);
	uint8_t temp_pkg_len = res_buf_index;

	uint8_t pkg_len = temp_pkg_len - head_len - tail_len;
	res_buf[1] = pkg_len;

	return temp_pkg_len;
}

以上为协议相关的2个关键函数,其他函数请参考第八小节的项目源码。

四、介绍一下实现一个秒级延时定时器的方法

这里主要讲一下本代码在实现秒级延时的设计思想。
需求中,我们不仅要求达到指定的时间延时,还需要再延时等待的过程中,接收来自串口的数据,如果串口接收到一个“更新App的请求”。则必须中断延时,来执行“更新App的请求”。
因此,我们不能使用Delay函数来实现延时。所以要使用定时器达到延时。我在一开始写实现这个定时器的时候犯了一个错误。一共需要延时5s左右。我把这5s的时间值直接写到了定时器的周期寄存器中。测试发现没有达到5s的延时。经过调试才明白。定时器的周期寄存器最大为0xffff即65535,也就是说寄存器是放不下5s对应的周期寄存器值。因此重新设计为,定时器500ms中断一次,通过统计中断的次数来,达到一个5s的延时(即,秒级的延时)。为了把定时器实现这个延时的逻辑整理到一个代码文件中,设计了一个“回调函数”,因此,可以通过定时器模块的接口函数设定“所需的定时时间”,当定时时间到达后,定时模块会调用配置好的“回调函数”。把延时完成后需要调用的代码,放在“回调函数”即可。

五、作为被Bootloader启动的app需要做哪些编码操作呢?

注意:编程软件默认生成的是hex文件,因此需要配置生成bin文件。
使用bootloader之后,App就不能放置在Flash的默认启动地址了,bootloader会把app放到一个设计好的flash地址所指定的地方。对应的App自己也需要自己被放置到flash的哪个地址,才能正确的使用app的堆栈和中断向量表。否则app无法运行。那么设置app自己的地址方法根据不同开发平台设置方式不同。

用keil的设置方式:
13.5-“制作一款私有IAP串口下载小工具”之STM32的Bootloader代码编写(包含源码)_第7张图片

使用TrueSTUDIO的设置方式:
//注意还需要 在STM32L072KB_FLASH.ld 文件中 修改flash的地址如下:
13.5-“制作一款私有IAP串口下载小工具”之STM32的Bootloader代码编写(包含源码)_第8张图片

由于bootloader在跳转到App之前,把总中断关闭了,因此在app运行后的第一句代码就应该“打开总中断”。在实际中还需要其他的一些配置。总结起来写成一个函数,如下代码所示:

#define USE_IAP
//#define USE_FREERTOS
void BSP_BootloaderInit(void) {
//重新设置中断向量表
#ifdef USE_IAP
	SCB->VTOR = ((uint32_t)FLASH_BASE) | ((0x8000) & (uint32_t)0x1fffff80);

//开启总中断
#ifdef USE_FREERTOS
	//FreeRTOS		:不需要下面的这行打开中断调用,调用了后App反而无法执行
#else
	//无任何单片机操作系统	:需要下面这句话,来打开总中断
	__set_PRIMASK(0);
#endif
	
#endif
}

六、调试代码经过记录

在调试bootloader的过程中,最难调试的部分是Flash的写入和擦除。 必须要把目标bin文件准确完整的写入到Flash的指定地址中。另一个地方是要准确的擦除flash(不能擦除不该擦除的地方)。说这几部分难,是因为擦除和写入的的结果不容易直观的通过调试看到。因此我们需要借助一个专业的Flash操作工具来辅助调试。这个工具就是与“J-Link”配套的软件“J-Flash”。

  1. J-Flash功能列表(需要连接J-Link使用):
  • 加密芯片
  • 解密芯片
  • 擦除整个芯片
  • 擦除指定扇区
  • 烧写
  • 烧写并校验
  • 校验
  • 从芯片flash读取程序(整个程序或指定扇区)
  • 启动应用程序
  1. 本Bootloader调试过程中,使用到J-Flash的地方
  • “校验”功能:
    使用自己的bootloader写bin文件到flash中,如果想要确认一下是否正确写入。可以使用J-Flash的“校验”功能校验。“出错”校验的结果截图如下:
    13.5-“制作一款私有IAP串口下载小工具”之STM32的Bootloader代码编写(包含源码)_第9张图片

  • “从芯片flash读取程序”的功能:
    把bootloader写入到芯片中的bin文件使用j-Flash读取出来,同时使用J-Flash打开bin文件。可以通过人工对比发现两者的差异。从而找出bootloder烧写bin的错误。比如在开发本bootloader的时候,发现bootloader写入到flash的程序无法运行,经过读取出来与原bin文件对比发现大小端的不同。bin文件正确在flash储存的时候,应该是低位在前。但是之前bootloader是把高位写在来前面。因此导致写入的应用程序无法运行。发现大小端不同的截图如下:
    13.5-“制作一款私有IAP串口下载小工具”之STM32的Bootloader代码编写(包含源码)_第10张图片

八、获取源码与源码目录说明

源码包含内容:

  • CubeMx工程
  • TrueStudio工程

点此获取源码

你可能感兴趣的:(哈喽,上位机(上位机开发指南),IAP,下载小工具,bootloader,stm32,上位机)