本文摘要:本文章介绍如何使用NXP官方软件S32KDS实现CAN总线上的bootloader
开发平台:S32 Design Studio for ARM Version 2.2
SDK版本:S32_SDK_S32K1xx_RTM_3.0.0
使用芯片:S32K148
使用设备:图莫斯usb2can
源码免费下载方式在文末!!!
1. 先介绍一下用于升级的CAN协议(假定ID:0x555为设备的独有ID)
(1)开始升级时,上位机会持续发送表1-1的报文。在APP程序里会有一个相应的处理,即接收到表1-1的报文后会重启MCU,使得MCU能够进入BOOT程序;在BOOT程序里也会有一个相应的处理,即接收到表1-1的报文后会将BOOT延时跳转APP的标志置位,即不再进行APP的跳转,同时发送一则表1-2的报文用以通知上位机已进入BOOT程序。
ID | Byte0 | Byte1 | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 |
0x555 | 0x7F | N/A | N/A | N/A | N/A | N/A | N/A | N/A |
表1-1
ID | Byte0 | Byte1 | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 |
0x555 | 0x63 | N/A | N/A | N/A | N/A | 0x1 | N/A | N/A |
表1-2
(2)上位机收到表1-2的报文后,则发送表1-3的报文,其中后四个字节表示APP程序bin文件的大小,即总字节数(APPByteSize = Byte4<<24+Byte5<<16+Byte6<<8+Byte7)。BOOT程序收到表1-3的报文则会计算APP程序的大小,用在后续对照接收APP数据和判断APP数据接收是否完成,随后发送一则表1-4的报文用以通知上位机可以开始发送APP数据。
ID | Byte0 | Byte1 | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 |
0x555 | 0x60 | N/A | N/A | N/A | APPsize | APPsize | APPsize | APPsize |
表1-3
ID | Byte0 | Byte1 | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 |
0x555 | 0x63 | N/A | N/A | N/A | N/A | N/A | 0x1 | N/A |
表1-4
(3)上位机收到表1-4的报文后,则持续发送表1-5格式的APP数据报文,直到发送完。其中Byte1-Byte3表示当前报文携带的APP数据的索引号(逐帧累加),用于和BOOT程序自身的索引号对照,保证APP数据的正确性;其Byte4-Byte7表示APP的四字节数据,Byte4-Byte7——APPByte1-APPByte4,即CAN报文的低字节存放APP的低字节数据。
ID | Byte0 | Byte1 | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 |
0x555 | 0x61 | index | index | index | APPByte1 | APPByte2 | APPByte3 | APPByte4 |
表1-5
(4)BOOT程序持续接收表1-5的报文期间,每接收一帧都会对照自身索引号和报文携带索引号是否一致,以保证接收正确。如果出现不一致的情况则会返回给上位机一则表1-6的报文,其中Byte5-Byte7存放出错的索引号,上位机可以据此重新发送此索引号的APP数据。
ID | Byte0 | Byte1 | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 |
0x555 | 0x62 | N/A | N/A | N/A | N/A | index | index | index |
表1-6
(5)BOOT程序持续接收表1-5的报文期间,会依据此前计算出的APP数据总大小来判断APP数据是否接收完成。接收完成后,BOOT程序会发送一则表1-7的报文,用以通知上位机接收完成,自身则会跳转至APP执行APP程序。
ID | Byte0 | Byte1 | Byte2 | Byte3 | Byte4 | Byte5 | Byte6 | Byte7 |
0x555 | 0x63 | N/A | N/A | N/A | N/A | N/A | N/A | 0x1 |
表1-7
2. bootloader的实现逻辑和基本原理就不再赘述,主程序奉上
实现bootloader主要用到了以下组件,在往期文章中均有介绍:
flexcan组件:
(104条消息) S32K的flexcan组件使用(RxFIFO+中断)_阿衰0110的博客-CSDN博客
lptmr组件:
(104条消息) S32K的lptmr组件使用(系统定时器)_阿衰0110的博客-CSDN博客
flash组件:
(104条消息) S32K的flash组件使用(操作FLASH)_阿衰0110的博客-CSDN博客
wdog组件:
(104条消息) S32K的wdog组件使用(看门狗)_阿衰0110的博客-CSDN博客
/*
* system.c
*
* Created on: 2022年3月8日
* Author: MNIAS
*/
#include "main.h"
// App起始地址
#define AppAddr 0x8000u
// App大小
#define AppSize 0x20000u
// IAP缓存区大小 S32K148的FLASH_DRV_Program最小可写大小是8B
#define IAPBuffSize 1024u
// 私有ID
uint16_t CANID_Private;
// IAP升级标志
static uint8_t IAP_Flag = 0;
// IAP数据索引
static uint32_t IAP_DATA_Index = 0;
// IAP数据大小
static uint32_t IAP_DATA_Size = 0;
// IAP缓存区
static uint8_t IAP_DATA_Buff[IAPBuffSize];
// flash操作结果
uint8_t result = 0;
static uint32_t failAddr = 0;
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t JumpAddress;
static void return_beginSignal(void);
static void receive_IAP_begin(void);
static void receive_IAP_handle(void);
static void JumpToApp(void);
/**
* @brief 硬件错误处理
* @param None
* @retval None
*/
void HardFault_Handler(void)
{
#ifdef DEBUG_printf
SEGGER_RTT_printf(0,"Hard fault error\n");
#endif
}
/**
* @brief 系统初始化
* @param None
* @retval None
*/
void SYSTEM_Init(void)
{
CLOCK_SYS_Init(g_clockManConfigsArr, CLOCK_MANAGER_CONFIG_CNT, g_clockManCallbacksArr, CLOCK_MANAGER_CALLBACK_CNT);
CLOCK_SYS_UpdateConfiguration(0U, CLOCK_MANAGER_POLICY_AGREEMENT);
PINS_DRV_Init(NUM_OF_CONFIGURED_PINS, g_pin_mux_InitConfigArr);
CAN0_Init();
CAN1_Init();
CAN2_Init();
lptmr_Init();
get_CANID_Private(&CANID_Private);
Flash_Init();
WDOG_Init(500); // 1s
#ifdef DEBUG_printf
SEGGER_RTT_printf(0,"entery boot, private CAN ID is 0x%03x\n", CANID_Private);
#endif
}
/**
* @brief 任务处理
* @param None
* @retval None
*/
void TASK_Schedule(void)
{
// 数据第一字节为模式
switch(CANrecMsg.CAN_DATA[0])
{
case 0x60:
receive_IAP_begin();
break;
case 0x61:
receive_IAP_handle();
break;
case 0x7e:
set_CANID_Private(CANrecMsg.CAN_DATA);
break;
case 0x7F:
return_beginSignal();
break;
}
// 无升级操作
if(IAP_Flag == 0)
{
// 超时则跳转APP
if(TIM_DelayTimeCounter >= 50)
{
JumpToApp();
}
}
WDOG_Feed();
}
/**
* @brief 返回IAP开始信号
* @param None
* @retval None
*/
static void return_beginSignal(void)
{
// 返回IAP预备信号 上位机接收后将发送IAP数据大小
set_CANTransmitData(useCANx, MAILBOX_9, CANID_Private,0x63,0,0,0,0,0x1,0,0);
// 清除模式位
CANrecMsg.CAN_DATA[0] = 0;
}
/**
* @brief 准备接收IAP数据 计算IAP数据大小
* @param None
* @retval None
*/
static void receive_IAP_begin(void)
{
// 清零索引 索引从0开始
IAP_DATA_Index = 0;
// 置位IAP升级标志
IAP_Flag = 1;
// 计算IAP数据大小
IAP_DATA_Size = (CANrecMsg.CAN_DATA[4] << 24) | (CANrecMsg.CAN_DATA[5] << 16) | (CANrecMsg.CAN_DATA[6] << 8) | (CANrecMsg.CAN_DATA[7]);
#ifdef DEBUG_printf
SEGGER_RTT_printf(0,"IAP data size : %d\n", IAP_DATA_Size);
#endif
INT_SYS_DisableIRQGlobal();
// S32K148的FLASH_DRV_EraseSector最小擦除扇区大小是 4KB
result = FLASH_DRV_EraseSector(&flashSSDConfig, AppAddr, ((IAP_DATA_Size >> 12) + 1) << 12);
#ifdef DEBUG_printf
SEGGER_RTT_printf(0,"erase flash T(0)/F(!0) : %d\n", result);
#endif
// 验证擦除
result = FLASH_DRV_VerifySection(&flashSSDConfig, AppAddr, ((IAP_DATA_Size >> 12) + 1) << 12 >> 4, 1u);
#ifdef DEBUG_printf
SEGGER_RTT_printf(0,"verify erase T(0)/F(!0) : %d\n", result);
#endif
INT_SYS_EnableIRQGlobal();
// 返回IAP开始信号 上位机接收后会持续发送IAP数据
set_CANTransmitData(useCANx, MAILBOX_9, CANID_Private,0x63,0,0,0,0,0,0x1,0);
// 清除模式位
CANrecMsg.CAN_DATA[0] = 0;
}
/**
* @brief 正在接收IAP数据
* @param None
* @retval None
*/
static void receive_IAP_handle(void)
{
// 计算当前帧的IAP数据索引
uint32_t IndexTemp = (CANrecMsg.CAN_DATA[1] << 16) | (CANrecMsg.CAN_DATA[2] << 8) | CANrecMsg.CAN_DATA[3];
#ifdef DEBUG_printf
SEGGER_RTT_printf(0,"message current IAP data index : %d\nboot current IAP data index : %d\n", IndexTemp, IAP_DATA_Index);
#endif
// IAP升级标志被置位
if(IAP_Flag == 1)
{
// 判断当前帧的IAP数据索引和存储的IAP数据索引是否一致,且数据大小小于APP的大小 IndexTemp<<2 = IndexTemp*4 因为一帧报文携带了4Byte
if(IndexTemp == IAP_DATA_Index && (IndexTemp << 2) < AppSize)
{
// 计算当前缓存字节数
uint16_t currentBuffSize = ((IAP_DATA_Index << 2) % IAPBuffSize) + 4;
// 计算当前接收总字节数
uint32_t currentRecSize = (IAP_DATA_Index << 2) + 4;
uint8_t count;
// 一帧4Byte
for(count = 0; count < 4; count++)
{
// IAP数据放入缓存区
IAP_DATA_Buff[currentBuffSize - 4 + count] = CANrecMsg.CAN_DATA[4 + count];
#ifdef DEBUG_printf
SEGGER_RTT_printf(0,"IAP data buff %d : %02x\n", currentBuffSize - 4 + count, IAP_DATA_Buff[currentBuffSize - 4 + count]);
#endif
}
// 缓存区数据存满 或 接收到的IAP数据大小达到此次IAP升级数据的大小时 进行一次写入
if(currentBuffSize == IAPBuffSize || currentRecSize >= IAP_DATA_Size)
{
INT_SYS_DisableIRQGlobal();
// S32K148的FLASH_DRV_Program最小可写大小是8B
result = FLASH_DRV_Program(&flashSSDConfig, AppAddr + ((IAP_DATA_Index << 2) >> 10 << 10), IAPBuffSize, IAP_DATA_Buff);
#ifdef DEBUG_printf
SEGGER_RTT_printf(0,"write flash T(0)/F(!0) : %d\n", result);
#endif
// 检验写flash
result = FLASH_DRV_ProgramCheck(&flashSSDConfig, AppAddr + ((IAP_DATA_Index << 2) >> 10 << 10), IAPBuffSize, IAP_DATA_Buff, &failAddr, 1u);
#ifdef DEBUG_printf
SEGGER_RTT_printf(0,"check write T(0)/F(!0) : %d, fail address %x\n", result, failAddr);
#endif
INT_SYS_EnableIRQGlobal();
// 接收到的IAP数据大小达到此次IAP升级数据的总大小 即为接收完全部数据 IAP完成
if(currentRecSize >= IAP_DATA_Size)
{
// 返回IAP完成信号
set_CANTransmitData(useCANx, MAILBOX_9, CANID_Private,0x63,0,0,0,0,0,0,0x1);
// 延时5ms
TIM_DelayTimeCounter = 0;
while(TIM_DelayTimeCounter < 5);
// 返回IAP完成信号
set_CANTransmitData(useCANx, MAILBOX_9, CANID_Private,0x63,0,0,0,0,0,0,0x1);
// 延时5ms
TIM_DelayTimeCounter = 0;
while(TIM_DelayTimeCounter < 5);
// 跳转APP
JumpToApp();
}
}
IAP_DATA_Index++;
}
else
{
// 返回IAP错误信号
set_CANTransmitData(useCANx, MAILBOX_9, CANID_Private,0x62,0,0,0,0,IAP_DATA_Index >> 16, IAP_DATA_Index >> 8, IAP_DATA_Index);
}
}
// 清除模式位
CANrecMsg.CAN_DATA[0] = 0;
}
/**
* @brief 跳转APP程序
* @param None
* @retval None
*/
static void JumpToApp(void)
{
LPTMR_DRV_Deinit(INST_LPTMR1);
FLEXCAN_DRV_Deinit(INST_CANCOM1);
FLEXCAN_DRV_Deinit(INST_CANCOM2);
FLEXCAN_DRV_Deinit(INST_CANCOM3);
WDOG_DRV_Deinit(INST_WATCHDOG1);
/* Disable FIRC divider */
//SCG->FIRCDIV |= SCG_FIRCDIV_FIRCDIV2(0x000);
// 关闭全局中断
INT_SYS_DisableIRQGlobal();
JumpAddress = *(volatile uint32_t*) (AppAddr + 4);
Jump_To_Application = (pFunction) JumpAddress;
Jump_To_Application();
}
3. boot程序需要配置自身所占地址的大小,防止自身地址与APP程序地址重合导致的一些问题
4. APP程序里需要配置flash基地址偏移(同上述 3. 的路径)
bootloader源码下载地址:
https://download.csdn.net/download/m0_50669075/85034836
S32K148更多例程和源码以及最新内容下载地址:
END