第一次来这里分享自己的心得,在开源电子网转了很久也没见到一份精简的代码(优秀的代码不少啊),今天给大家讲讲STM8的IAP
STM8 IAP可以做什么
做远程程序更新,我们只需要烧入底层程序(boot),就可以像手机更新系统那样来更新我们单片机的软件,当然出厂也是可以连boot和app都烧进去的
这里不多废话 讲重点
我们STM8使用IAR软件开发,我们需要给boot定义一个地址,这里定义地址为0x8000到0x88F0,为了方便大家看懂并且学习,我截图截整个工程
这是boot的地址
至于为什么选择0x88F0 我们根据实际编译出的程序大小自由选择 一般要保证选择的flash足够 我这里用STVP查看我的程序截止于0x88B0 截图如下
这是APP的地址 0x8900 0x9FFF APP这里就不方便截图了 大家可以写一个简单的串口APP程序或者闪灯程序 十分简单的
这里大家可能会问 如何设置地址,就像第一张图 IAR软件是在一个文件中 lnkstm8l151f3.icf 为了方便大家可以像我一样可以把这个文件添加到工程中
我的文件地址是
:IARstm8config,这个地址取决于你安装IAR的根目录地址,添加工程文件大家不会的可以百度一下,选择文件夹点击鼠标右键,
找到对应的芯片型号即可
这里还需要将这个文件引用到工程中,我测试过,不引用也是没问题的,多做一步也是很简单的
然后编译工程,烧入板子,我们的boot程序就弄好了
下面给大家讲讲程序 ,我们的程序尽量简洁(因为STM8Lflash太小),编译优化我选择了中等
代码贴出来方法大家移植
[mw_shl_code=c,true]#include "usart.h"
#include "string.h"
///////////////////////////////////////////////////////////////////
//函数名称:main ///
//功能描述:程序入口,处理程序更新 ///
//作 者:baishuyuan ///
//日 期:2018/11/26 ///
//注意事项:程序不可大于APP起始地址 ///
///////////////////////////////////////////////////////////////////
const u8 FLASHKEY = 0xFF;//APP标志位
#define MAIN_USER_Start_ADDR ((uint32_t)0x8000+0xB00-4)//用户代码的起始地址
#define MIN_USER_Start_ADDR (MAIN_USER_Start_ADDR+4)//用户代码的起始地址 字节偏移5个字节
u16 APP_BIT_ADDR = 0x1000;//用户代码的起始地址
#define PWR_HOLD_GPIO GPIOD
#define PWR_HOLD_GPIO_Pin GPIO_Pin_0
/*******************************************************************************
**函数名称:void delay(unsigned int ms) Name: void delay(unsigned int ms)
**功能描述:大概延时
**入口参数:unsigned int ms 输入大概延时数值
**输出:无
******************************************************************************/
void delay(unsigned int ms)
{
unsigned int x , y;
for(x = ms; x > 0; x--) /* 通过一定周期循环进行延时*/
for(y = 1000 ; y > 0 ; y--);
}
//定义指向函数的指针类型
typedef void ( *AppMainTyp)(void);
uint32_t FLASH_ReadWord(uint32_t Address)
{
return(*(PointerAttr uint32_t *) (uint16_t)Address);
}
#include "stm8l15x_itc.h"
//重新初始化STM8的中断向量表 把它重新定义到APP的中断向量中
void STM8_HanderIqr_Init(void)
{
disableInterrupts(); //关闭中断
uint8_t Index;
FLASH_Unlock(FLASH_MemType_Program);
for(Index = 1; Index < 0X20;Index++)
{
if(FLASH_ReadWord(0X8000+4*Index)!=(0X82000000+MIN_USER_Start_ADDR+Index*4))
{
FLASH_ProgramWord(0X8000+4*Index,0X82000000+MIN_USER_Start_ADDR+Index*4);
}
}
FLASH_Lock(FLASH_MemType_Program);
}
//跳转到用户代码
void goto_app(void)
{
//重定义STM8的中断向量
STM8_HanderIqr_Init();
//跳转至APP
asm("LDW X, SP ");
asm("LD A, $FF");
asm("LD XL, A ");
asm("LDW SP, X ");
asm("JPF $8900");//0x8900 是APP的首地址
}
/*******************************************************************************
**函数名称:void EEPROM_Byte_Write(unsigned int address , unsigned char date)
**功能描述:向EEPROM中固定地址写入一个字节数据
**入口参数:unsigned int address , unsigned char date
address :要写入数据的存储地址
date :一个字节数据
**输出:无
*******************************************************************************/
void EEPROM_Byte_Write(unsigned int address , unsigned char date)
{
FLASH_SetProgrammingTime(FLASH_ProgramTime_TProg); //设定编程时间为标准编程时间
//MASS 密钥,解除EEPROM的保护
FLASH_Unlock(FLASH_MemType_Data);
FLASH_ProgramByte(address , date); //把数据写入相应的存储地址
while(FLASH_GetFlagStatus(FLASH_FLAG_EOP) == 1); //等待编程结束
}
/**********************************************************************
* 函数名称: XOR_Inverted_Check
* 功能描述:数据的异或取反校验
* para:
inBuf: 需检测的字符串
inLen: 所需检测的字符串长度
***********************************************************************/
u8 XOR_Inverted_Check(unsigned char* inBuf, unsigned char inLen)
{
u8 check = 0, i;
for (i = 0; i < inLen; i++)
{
check ^= inBuf
;
}
check = ~check;
return check;
}
u8 Write_App(u8 * Write_Data,u8 Write_Len)
{
static u32 addr = MIN_USER_Start_ADDR;//得到flash地址
u8 i = 0;
for(i=3;i<(Write_Len-1);i++)
{
if(i<(Write_Len-1))
{
FLASH_ProgramByte(addr , USART_RX_BUF); //把数据写入相应的存储地址
}
addr++;//为下次写入做准备
}
if(Write_Len<24)
{
EEPROM_Byte_Write(APP_BIT_ADDR,FLASHKEY);
addr=MAIN_USER_Start_ADDR;//重置flash写地址
}
return i;
}
void Memset()
{
memset(USART_RX_BUF,0,USART_RX_STA);
USART_RX_STA=0;
}
void main()
{
u8 STR[]={0x7b,0x05,0xf2,0x03,0x70,'
','
',' '};
u32 i=0;
disableInterrupts(); //关闭系统总中断
GPIO_DeInit(GPIOD);
/* Port D in output push-pull 0 */
GPIO_Init(GPIOD,GPIO_Pin_All, GPIO_Mode_Out_PP_Low_Slow);
GPIO_Init(PWR_HOLD_GPIO, PWR_HOLD_GPIO_Pin, GPIO_Mode_Out_PP_Low_Slow); //默认all电源为关 D0 推挽-输出低-高速
GPIO_SetBits(PWR_HOLD_GPIO, PWR_HOLD_GPIO_Pin);
CLK_SYSCLKDivConfig(CLK_SYSCLKDiv_1); //内部时钟为1分频 = 16Mhz
FLASH_Unlock(FLASH_MemType_Program);//MASS 密钥,解除FLASH的保护
FLASH_Unlock(FLASH_MemType_Data);//MASS 密钥,解除EEPROM的保护
FLASH_SetProgrammingTime(FLASH_ProgramTime_TProg); //设定编程时间为标准编程时间
while(FLASH_GetFlagStatus(FLASH_FLAG_PUL) == RESET);
//如果用户代码更新完成标记存在 则表示当前已经有APP代码 直接运行
if(FLASH_ReadByte(APP_BIT_ADDR+1)==0 && FLASH_ReadByte(MIN_USER_Start_ADDR)==0x82)//出厂首次烧录入口
{
// FLASH_ProgramByte(APP_BIT_ADDR , FLASHKEY); //写入更新完成标志位
// goto_app(); //运行APP
}
else if(FLASH_ReadByte(MIN_USER_Start_ADDR)==0x82 && FLASH_ReadByte(APP_BIT_ADDR)==FLASHKEY && FLASH_ReadByte(APP_BIT_ADDR+1)!=0)//关机重启设备入口
{
// goto_app(); //运行APP
}
else//更新APP入口
{
// if( FLASH_ReadByte(APP_BIT_ADDR)!=0x00)//标志位清空 这段是APP的
// {
// FLASH_EraseByte(APP_BIT_ADDR);
// }
// for(i=MAIN_USER_Start_ADDR;i<0x9FF0;i++)//限定地址不可超过0x9FF0,清空flash
// {
// FLASH_EraseByte(i); //把数据写入相应的存储地址
// }
}
enableInterrupts(); //使能系统总中断
//等待USART1接收字符中断产生,中断服务函数在 stm8l15x_it.c文件里的 函数 INTERRUPT_HANDLER(USART1_RX_TIM5_CC_IRQHandler,28)
UART1_Init(9600);//串口初始化
UART1_TX_STR(STR);//接收第一帧数据
while(1)
{
UART1_TX_STR(STR);//发送测试数据
delay(1000);//短暂延迟
i=0xFFFFF;
while(i>10)
{
i--;
};
if(UPDATE_APP_OK)UPDATE_APP_OK++;//串口接收完成标志位
if(UPDATE_APP_OK>5)//接收完成了
{
UPDATE_APP_OK=0;
if((USART_RX_BUF[USART_RX_STA-1])==//CRC
(XOR_Inverted_Check(USART_RX_BUF,USART_RX_STA-1)))
{
if(USART_RX_STA>5 && USART_RX_STA<=USART_RX_LEN)
{
if(Write_App(USART_RX_BUF,USART_RX_STA)==(USART_RX_LEN-1))
{
UART1_TX_STR(STR);//持续接收
}
else
{
Memset();
STR[3]=0x05;
STR[4]=0x76;
UART1_TX_STR(STR);//go to app sending data
// goto_app();//进入更新后的APP
}
}
}
Memset();
}
}
}[/mw_shl_code]
还有usart.c的代码
[mw_shl_code=c,true]#include "usart.h"
u8 USART_RX_BUF[USART_RX_LEN];
u16 USART_RX_STA=0;
u8 UPDATE_APP_OK = 0;
/******************************************************************************
**函数名称:void UART1_Init(unsigned int baudrate)
**功能描述:初始化USART模块
**入口参数:unsigned int baudrate -> 设置串口波特率
**输出:无
******************************************************************************/
void UART1_Init(unsigned int baudrate)
{
CLK_PeripheralClockConfig(CLK_Peripheral_USART1 , ENABLE); //使能USART1时钟
USART_Init(USART1, //设置USART1
baudrate, //波特率设置
USART_WordLength_8b, //数据长度设为8位
USART_StopBits_1, //1位停止位
USART_Parity_No, //无校验
(USART_Mode_Rx | USART_Mode_Tx)); //设置为发送接收双模式
USART_ITConfig(USART1, USART_IT_RXNE , ENABLE);
USART_Cmd(USART1 , ENABLE);
}
//串口发送函数
void UART1_TX_STR(unsigned char * str)
{
u16 STRLEN = 0;
while(str[STRLEN] != ' ')
{
USART_SendData8(USART1 , str[STRLEN]);
while(USART_GetFlagStatus(USART1 , USART_FLAG_TXE) == 0);
STRLEN++;
}
}
/**
* @brief USART1 RX / Timer5 Capture/Compare Interrupt routine.
* @param None
* @retval None
*/
INTERRUPT_HANDLER(USART1_RX_TIM5_CC_IRQHandler,28)
{
/* In order to detect unexpected events during development,
it is recommended to set a breakpoint on the following instruction.
*/
u8 Res=0;
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res=USART_ReceiveData8(USART1); //(USART1->DR);//读取接收到的数据
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清除中断标志位
if(USART_RX_STA<USART_RX_LEN)
{
UPDATE_APP_OK=1;//接收到数据了 接收完成标志位
USART_RX_BUF[USART_RX_STA] = Res;
USART_RX_STA++;
}
}
}[/mw_shl_code]
usart.h的代码
[mw_shl_code=c,true]#ifndef __USART_H
#define __USART_H
#include "stm8l15x.h"
#define USART_RX_LEN 24//可接收的数据长度
extern u8 UPDATE_APP_OK ;
extern u8 USART_RX_BUF[USART_RX_LEN];
extern u16 USART_RX_STA;
void UART1_Init(unsigned int baudrate);
void UART1_TX_STR(unsigned char * str);
#endif[/mw_shl_code]
这些代码都比较简单,就是根据通讯协议传输APP的烧录文件,一次写20个字节,小于20个则认为APP的程序已经是最后一帧
着重给大家讲讲那几个flash地址,我在eeprom写了两个地址,分别是0x1000和0x1001,0x1000是更新标志位,0x1001是出厂烧录标志位,
这个位以后每更新一次程序我都会在APP中将他加1个,以此记录次数
下面截图部分通讯协议方便大家参考
3.命令
蓝 {MOD}为 MCU->HOST
红 {MOD}为 HOST->MCU
5.校验和
将前面各字节异或
6.APP 更新
发送更新命令后,原有的 app 将立马擦除,继而进入 bootloader 层, 然后发送 ok 以请求更
新数据,更新完毕后发送 go_to_App
如果发送的更新数据小于 20(不连协议帧),系统将认为这是最后一帧数据,平时数据必须等
于 20,如有刚好 20 个结束,可发送空参数
可能有人会问,烧录文件发什么,这个根据你上位机来,什么方便发什么,IAR可以生成几种文件,方法自行百度
简单的给大家讲讲更新的思路,上位机请求,APP随机写eeprom中更新次数,清空更新完成标志位,重启单片机
在boot中擦除APP程序,发送命令请求数据,写入数据完成继续发送,重复至更新完成,更新完成后将更新完成
标志位写入,重定义中断向量表,跳转至APP
我在想要不要附上工程,最后决定附上boot工程,至于APP工程,有愿意相信楼主的可以加我QQ,1285973921,为了方便大家学习
我用STM32F103C8T6写了个模拟上位机发送更新数据
实验1boot.rar
(8.46 MB, 下载次数: 9808)
2018-11-28 15:09 上传
点击文件名下载附件
写这篇帖子是为了感谢原子哥,感谢开源电子网带来的诸多方便,做这些东西我用了差不多整整三天才搞明白,不懂的可以加我QQ问我
希望大家帮忙顶下 谢谢大家
哈哈,老铁你真相了。ST官方有STM8的Bootloader源码和上位机部分源码,通信部分只提供给dll,boot源码支持stm8全系列AFSL等等,STVD和IAR都行,汇编也有,上位机用的vs环境下C++写的。我看了下boot里uart是查询接收,APP中断和boot中断非独立,boot不能进行中断,感兴趣的可以去下来看看。
一周热门 更多>