S32K的bootloader CAN总线实现

本文摘要:本文章介绍如何使用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程序。

IDByte0Byte1Byte2Byte3Byte4Byte5Byte6Byte7
0x5550x7FN/AN/AN/AN/AN/AN/AN/A

表1-1

IDByte0Byte1Byte2Byte3Byte4Byte5Byte6Byte7
0x5550x63N/AN/AN/AN/A0x1N/AN/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数据。

IDByte0Byte1Byte2Byte3Byte4Byte5Byte6Byte7
0x5550x60N/AN/AN/AAPPsizeAPPsizeAPPsizeAPPsize

表1-3

IDByte0Byte1Byte2Byte3Byte4Byte5Byte6Byte7
0x5550x63N/AN/AN/AN/AN/A0x1N/A

表1-4

 (3)上位机收到表1-4的报文后,则持续发送表1-5格式的APP数据报文,直到发送完。其中Byte1-Byte3表示当前报文携带的APP数据的索引号(逐帧累加),用于和BOOT程序自身的索引号对照,保证APP数据的正确性;其Byte4-Byte7表示APP的四字节数据,Byte4-Byte7——APPByte1-APPByte4,即CAN报文的低字节存放APP的低字节数据。

IDByte0Byte1Byte2Byte3Byte4Byte5Byte6Byte7
0x5550x61indexindexindexAPPByte1APPByte2APPByte3APPByte4

表1-5

(4)BOOT程序持续接收表1-5的报文期间,每接收一帧都会对照自身索引号和报文携带索引号是否一致,以保证接收正确。如果出现不一致的情况则会返回给上位机一则表1-6的报文,其中Byte5-Byte7存放出错的索引号,上位机可以据此重新发送此索引号的APP数据。

IDByte0Byte1Byte2Byte3Byte4Byte5Byte6Byte7
0x5550x62N/AN/AN/AN/Aindexindexindex

表1-6

 (5)BOOT程序持续接收表1-5的报文期间,会依据此前计算出的APP数据总大小来判断APP数据是否接收完成。接收完成后,BOOT程序会发送一则表1-7的报文,用以通知上位机接收完成,自身则会跳转至APP执行APP程序。

IDByte0Byte1Byte2Byte3Byte4Byte5Byte6Byte7
0x5550x63N/AN/AN/AN/AN/AN/A0x1

表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更多例程和源码以及最新内容下载地址:

nxp_s32k148_template: 使用NXP的S32KDS软件基于S32_SDK_S32K1xx_RTM_3.0.0编写的的S32K148各组件测试例程,亲测可用,带有注释,适用于S32K14X系列的芯片。 (gitee.com)

END

  • 9
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿衰0110

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值