在开始写文章之前,要先吐槽一下国产单片机,不对是国产单片机厂家,他们的技术支持实在太烂了,烂的让你怀疑等技术支持还不如自己啃手册,尤其是那种中间有代理商的厂家,技术支持更是良莠不齐,时效性也不行,问个问题等几天之后回复,最可气的是回复告诉你这个功能芯片不支持,真是伤不起了。如果成本不是特别敏感,研发实力不是很强大,不要碰国产单片机,特别是有中间代理的。
一、BOOT程序移植
HC32官方提供了一个自定义协议的IAP升级demo,自定义协议,但是苦于没有提供上位机程序源码,不方便以后升级,而且官方的升级软件看着也很不完善。如下图所示。所以决定自己移植一下Xmodem协议。
之前在别的单片机上用过Xmodem协议做串口IAP升级协议,所以这里,只需要移植一下就可以了。Xmodem框架如下图所示。
因为框架已经写好,所以只需要修改对应的接口就可以了。在进行IAP的过程主要涉及到如下四个外设
flash擦除写入
定时器中断
1、hc32L130上电默认时钟为内部4M时钟,如果不想使用高的波特率这步可以省略,因为串口波特率我要使用115200,如果不倍频的话PCLK是4M,那么波特率115200误差会达到8.51%,而要想可靠使用串口,波特率误差应该小于2%。所以我将系统时钟倍频到48M,这时115200误差率0.16%,如下图所示。
将4MRCH倍频到48M代码如下。
void App_RCH4MHzToPll48MHz (void)
{
M0P_FLASH->BYPASS = 0x5A5A;
M0P_FLASH->BYPASS = 0xA5A5;
M0P_FLASH->CR_f.WAIT = FlashWaitCycle1;
M0P_SYSCTRL->PLL_CR_f.FRSEL = SysctrlPllInFreq4_6MHz;
M0P_SYSCTRL->PLL_CR_f.FOSC = SysctrlPllOutFreq36_48MHz;
M0P_SYSCTRL->PLL_CR_f.DIVN = SysctrlPllMul12;
M0P_SYSCTRL->PLL_CR_f.REFSEL = SysctrlPllRch;
M0P_SYSCTRL->PLL_CR_f.STARTUP = SysctrlPllStableCycle16384;
M0P_SYSCTRL->SYSCTRL2 = 0x5A5A;
M0P_SYSCTRL->SYSCTRL2 = 0xA5A5;
M0P_SYSCTRL->SYSCTRL0_f.PLL_EN = TRUE;
while(TRUE && (1 != M0P_SYSCTRL->PLL_CR_f.STABLE))
{
;
}
M0P_SYSCTRL->SYSCTRL2 = 0x5A5A;
M0P_SYSCTRL->SYSCTRL2 = 0xA5A5;
M0P_SYSCTRL->SYSCTRL0_f.CLKSW = SysctrlClkPLL;
//更新Core时钟(HCLK)
SystemCoreClockUpdate();
}
2、串口初始化代码
void UART0_Config(uint32_t baudRate)
{
stc_gpio_cfg_t stcGpioCfg;
stc_uart_cfg_t stcCfg;
stc_uart_baud_t stcBaud;
DDL_ZERO_STRUCT(stcCfg); //初始化变量
DDL_ZERO_STRUCT(stcBaud); //初始化变量
DDL_ZERO_STRUCT(stcGpioCfg);
Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE); //GPIO外设模块时钟使能
stcGpioCfg.enDir = GpioDirOut;
Gpio_Init(GpioPortA,GpioPin9,&stcGpioCfg);
Gpio_SetAfMode(GpioPortA,GpioPin9,GpioAf1); //配置PD00 为UART1 TX
stcGpioCfg.enDir = GpioDirIn;
Gpio_Init(GpioPortA,GpioPin10,&stcGpioCfg);
Gpio_SetAfMode(GpioPortA,GpioPin10,GpioAf1);//配置PA0D 为UART1 RX
Sysctrl_SetPeripheralGate(SysctrlPeripheralUart0,TRUE);//使能UART1外设时钟门控开关
stcCfg.enRunMode = UartMskMode1; //模式1
stcCfg.enStopBit = UartMsk1bit; //1位停止位
stcCfg.enMmdorCk = UartMskEven; //偶校验
stcCfg.stcBaud.u32Baud = baudRate; //波特率baudRate
stcCfg.stcBaud.enClkDiv = UartMsk8Or16Div; //通道采样分频配置
stcCfg.stcBaud.u32Pclk = Sysctrl_GetPClkFreq(); //获得外设时钟(PCLK)频率值
Uart_Init(M0P_UART0, &stcCfg); //串口初始化
// Uart_EnableIrq(M0P_UART1,UartRxIrq);
// EnableNvic(UART1_IRQn, IrqLevel3, TRUE); ///<系统中断使能
Uart_ClrStatus(M0P_UART0,UartRC); //清接收请求
}
3、flash初始化代码
Flash_Init(12, TRUE); // PCLK 为48M
4、定时器3这里给xmodem提供时间基准定时时间为4ms。
//Timer3 配置
void App_Timer3Cfg(uint16_t u16Period)
{
uint16_t u16ArrValue;
uint16_t u16CntValue;
stc_tim3_mode0_cfg_t stcTim3BaseCfg;
//结构体初始化清零
DDL_ZERO_STRUCT(stcTim3BaseCfg);
Sysctrl_SetPeripheralGate(SysctrlPeripheralTim3, TRUE); //Base Timer外设时钟使能
stcTim3BaseCfg.enWorkMode = Tim3WorkMode0; //定时器模式
stcTim3BaseCfg.enCT = Tim3Timer; //定时器功能,计数时钟为内部PCLK
stcTim3BaseCfg.enPRS = Tim3PCLKDiv64; //PCLK/64 0.75M
stcTim3BaseCfg.enCntMode = Tim316bitArrMode; //自动重载16位计数器/定时器
stcTim3BaseCfg.bEnTog = FALSE;
stcTim3BaseCfg.bEnGate = FALSE;
stcTim3BaseCfg.enGateP = Tim3GatePositive;
Tim3_Mode0_Init(&stcTim3BaseCfg); //TIM3 的模式0功能初始化
u16ArrValue = 0x10000 - 750*u16Period ;
Tim3_M0_ARRSet(u16ArrValue); //设置重载值(ARR = 0x10000 - 周期)
u16CntValue = 0x10000 - 750*u16Period;
Tim3_M0_Cnt16Set(u16CntValue); //设置计数初值
Tim3_ClearIntFlag(Tim3UevIrq); //清中断标志
Tim3_Mode0_EnableIrq(); //使能TIM3中断(模式0时只有一个中断)
EnableNvic(TIM3_IRQn, IrqLevel3, TRUE); //TIM3 开中断
}
定时器3中断函数
void TIM3_IRQHandler(void)
{
static uint8_t i,count;
//Timer3 模式0 计时溢出中断
if(TRUE == Tim3_GetIntFlag(Tim3UevIrq))
{
// count++;
// if(count>10)
{
count = 0;
xmodem_timeout++;
if(0 == i) //测试
{
LED0(LED_OFF);
i++;
}
else
{
LED0(LED_ON);
i = 0;
}
}
Tim3_ClearIntFlag(Tim3UevIrq); //Timer3模式0 中断标志清除
}
}
下面开始移植Xmodem协议程序,首先将框架程序添加进工程
修改xmodem_HW.C硬件接口。将串口发送接收程序移植进去。
//串口收发,实用查询方式。
void xm_port_write(uint8 *ch)
{
/* 一直等待, 直到缓冲区为空 */
M0P_UART0->ICR_f.TCCF = 0;
M0P_UART0->SBUF = (uint8_t)*ch;
while(M0P_UART0->ISR_f.TC == 0);
M0P_UART0->ICR_f.TCCF = 0;
}
//串口接收函数,需要移植
sint8 xm_port_read(uint8 *ch)
{
if(Uart_GetStatus(M0P_UART0, UartRC))
{
Uart_ClrStatus(M0P_UART0,UartRC);
*ch = M0P_UART0->SBUF;
return 1;
}
/* 返回接收到的8位数据 */
return 0;
}
xmodem.c中KBflashcopy函数添加flash擦除写入函数。
void KBflashcopy(uint32_t flash_addr,uint8_t* src_addr,sint32 count)
{
uint32_t start_addr = flash_addr;
uint16_t i = 0;
uint32_t temp = 0;
DI();
Flash_Erase(start_addr);
Flash_Erase(start_addr+512);
for(i=0;i
回到main.c中在定时器3中断中添加xmodem超时计数变量xmodem_timeout,需要外部引入下。
void TIM3_IRQHandler(void)
{
static uint8_t i,count;
//Timer3 模式0 计时溢出中断
if(TRUE == Tim3_GetIntFlag(Tim3UevIrq))
{
// count++;
// if(count>10)
{
count = 0;
xmodem_timeout++;
if(0 == i)
{
LED0(LED_OFF);
i++;
}
else
{
LED0(LED_ON);
i = 0;
}
}
Tim3_ClearIntFlag(Tim3UevIrq); //Timer3模式0 中断标志清除
}
}
主程序中挂载xm_receive()函数。FLASH_APP_ADDR为APP程序写入起始地址。
写入完成后执行APP程序,实现方法为
//跳转到应用程序段
//appxaddr:用户代码起始地址
void iap_load_app(uint32_t appxaddr)
{
if(((*(__IO uint32_t*)appxaddr)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法
{
jump2app=(iapfun)*(__IO uint32_t*)(appxaddr+4); //用户代码区的第二个字为程序开始地址(复位地址)
//MSR_MSP(*(__IO uint32_t*)appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
__set_MSP(*(__IO uint32_t *)appxaddr);
jump2app(); //跳转到APP.
}
}
完整的主程序
int32_t main(void)
{
App_RCH24MHzToPll48MHz();
Flash_Init(12, TRUE);
LED_GPIO_Config();
UART0_Config(115200);
App_Timer3Cfg(4); //4ms
Tim3_M0_Run(); //TIM3 运行。
i = xm_receive(FLASH_APP_ADDR); //xmodem接收的APP地址从APP_ADDR开始
M0P_RESET->PERI_RESET = 0; //所有外设复位
M0P_RESET->PERI_RESET = 0xFFFFFFFF;
while(1)
{
iap_load_app(FLASH_APP_ADDR); ///*IAP 跳转*///
//
};
}
程序移植完成编译通过后,下载进目标板打开上位机软件即可开始IAP更新程序了。
二、APP程序keil环境设置
APP 程序主要修改中断向量的偏移,打开APP程序中的启动文件startup_hc32l130j8ta.s文件,需要修改两处。
第一处在堆栈定义下面添加中断向量偏移,new_vect_table EQU 0x00001800 ;中断向量偏移长度
第二处在RAMCODE处添加 LDR R2, =new_vect_table
到这里启动文件就修改好了,接下来打开keil 魔术棒,修改编译生成的程序的偏移地址。因为APP占用了8K的flash,所以偏移量设置成8K,即0x1800。
在生成bin文件,网上教程很多,不详细写了。
三、一切准备就绪后打开上位机软件,上位机软件是基于一个QT开源软件修改的,感兴趣的可以上GitHub上搜索。
选择正确的串口,连接串口,加载生产的main.bin程序,点击升级,即可完成APP更新。
总体上是单片机上电先运行Boot,然后运行app,这里有个问题,如果boot里面将PCLK倍频到了48M,在APP中再进行倍频单片机就会死机,所以建议APP中上电读取下当前系统频率,如果已经倍频了就不要在倍频了,否则分分钟死机。