STM32串口IAP

发布者:温柔微笑最新更新时间:2016-10-10 来源: eefocus关键字:STM32  串口  IAP 手机看文章 扫描二维码
随时随地手机看文章
让STM32的应用程序能够通过串口在线升级,这就是STM32的串口IAP。要实现串口升级,简单来说,就是给STM32编写一个bootloader引导程序,就想计算机的BIOS一样,在这段代码中接收串口的数据,然后将数据固化到STM32内部指定的flash地址空间中,接着再跑到这段代码执行。
接触过Linux uboot的应该会注意到,除了功能的实现外,bootloader的界面设计也非常重要。通过串口在计算机的串口软件中实现一个简洁的界面,列出bootloader的各项功能,并支持用户选择。实现这样一个人际交互的界面也是一个优秀的bootloader必不可少的一部分。
本文就要讲讲如何设计这个有功能有界面的bootloader程序!还是基于我自己的规范工程。
1、工程的修改
1)串口升级当然需要用到USART与FLASH了,我的原工程已经添加了串口的库文件stm32f10x_usart.c与stm32f10x_flash.c,所以就不需要在添加这两个文件了。
2)拷贝《STM32多路软定时器》一文中编写SoftTimer.c与SoftTimer.h文件分别保存在工程文件的BSP文件下的src与inc文件夹中,并将SoftTimer.c文件添加到BSP工程组中。
3)既然用到了定时器,所以将库文件stm32f10x_tim.c文件添加到STM32F10x_StdPeriod_Driver工程组中。
4)打开stm32f10x_conf.h文件,将原先屏蔽的:“#include stm32f10x_tim.h”语句的屏蔽去掉。
5)新建IAP.c和IAP.h两个文件分别保存到BSP文件夹下的src与inc两个文件中。并将IAP.c文件添加到BSP工程组中。
工程的架构如下图所示:
STM32串口IAP - ziye334 - ziye334的博客
2、IAP.c与IAP.h文件的编写
考虑到开发板资源,我采用串口1作为升级的通道,所以原先在规范工程中作为调试接口的串口1的相关代码需要从BSP.c与BSP.h两个文件中完全删除掉。
按照之前的习惯,在IAP.c第一需要编写的函数应为:IAP_Init(),在这个函数中,配置串口相关的代码,如配置串口引脚,串口时钟,串口属性,串口中断等,具体的代码如下:

/*************************************************************
Function : IAP_Init
Description: IAP初始化函数,初始化串口1
Input : none
return : none
*************************************************************/
void IAP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

RCC_APB2PeriphClockCmd(COM1_RCC, ENABLE);//使能 USART1 时钟
RCC_APB2PeriphClockCmd(COM1_GPIO_RCC, ENABLE);//使能串口2引脚时钟

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//配置 USART1 的Tx 引脚类型为推挽式的
GPIO_InitStructure.GPIO_Pin = COM1_TX_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(COM1_GPIO_PORT, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//配置 USART1 的Rx 为输入悬空
GPIO_InitStructure.GPIO_Pin = COM1_RX_PIN;
GPIO_Init(COM1_GPIO_PORT, &GPIO_InitStructure);

USART_InitStructure.USART_BaudRate = 115200;//设置波特率为115200
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//设置数据位为8位
USART_InitStructure.USART_StopBits = USART_StopBits_1;//设置停止位为1位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//没有硬件流控
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//发送与接收

USART_ITConfig(COM1, USART_IT_RXNE, ENABLE);//接收中断使能
USART_Init(COM1,&USART_InitStructure);//串口2相关寄存器的配置
USART_Cmd(COM1,ENABLE); //使能串口2

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//通道设置为串口2中断
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//中断占优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//打开中断
NVIC_Init(&NVIC_InitStructure);
}

串口初始化完毕后,当然顺带得将发送与接收的函数编写完,有与上面配置了中断接收,所以在IAP.c文件中,只需要编写串口发送函数就可以了:

/*************************************************************
Function : IAP_SerialSendByte
Description: 串口发送字节
Input : c-要发送的字节
return : none
*************************************************************/
static void IAP_SerialSendByte(u8 c)
{
USART_SendData(COM1, c);
while (USART_GetFlagStatus(COM1, USART_FLAG_TXE) == RESET) {}
}
/*************************************************************
Function : IAP_SerialSendStr
Description: 串口发送字符串
Input : none
return : none
*************************************************************/
void IAP_SerialSendStr(u8 *s)
{
while(*s != '\0')
{
IAP_SerialSendByte(*s);
s++;
}
}

至于串口接收数据的相关实现需要借助一个环形的缓冲区。所谓的环形缓冲本质就是定义一个有限的数组UsartBuffer[MAXBUFFER],然后通过两个变量:UsartWptr和UsartRptr分别标注当前环形缓冲区的读与写位置,当接收到一个数据,写位置变量UsartWptr就会向后偏移一个位置;当读走一个数据,UsartRptr也会向后偏移一个位置;当UsartWptr等于UsartRptr时,说明缓冲区中没有新的数据,否者存在未读数据。环形缓冲能够大大节省处理器的资源,它具体的原理也不在多说,下面看看它的实现:

#define MAXBUFFER 512 //缓冲大小
u8 UsartBuffer[MAXBUFFER]; //数据缓冲区
u16 UsartWptr = 0;
u16 UsartRptr = 0;

/*************************************************************
Function : IAP_BufferWrite
Description: 写缓冲区
Input : none
return : none
*************************************************************/
void IAP_BufferWrite(void)
{
if(UsartWptr == (UsartRptr - 1))//缓冲区存满了
{
return;//返回
}

UsartBuffer[UsartWptr] = USART_ReceiveData(COM1);//存取串口数据
UsartWptr++;//缓冲写位置值自增
UsartWptr = UsartWptr%MAXBUFFER;//保证写位置值不溢出
}

/*************************************************************
Function : IAP_BufferRead
Description: 读缓冲区
Input : none
return : none
*************************************************************/
static u8 IAP_BufferRead(u8 *data)
{
if(UsartRptr == UsartWptr)//无数据可读
{
return 0;
}
*data = UsartBuffer[UsartRptr];//读取缓冲区数据
UsartRptr++;//读位置值自增
UsartRptr = UsartRptr % MAXBUFFER;//保证读位置值不溢出
return 1;
}

接下去先讲讲bootloader的界面,然后在在根据界面中的功能选项再将具体的实现方法。bootloader的界面需要设计得简洁明了,下面就是我自己设计的界面:

/*************************************************************
Function : IAP_ShowMenu
Description: 显示菜单界面
Input : none
return : none
*************************************************************/
void IAP_ShowMenu(void)
{
IAP_SerialSendStr("\r\n+================(C) COPYRIGHT 2014 Ziye334 ================+");
IAP_SerialSendStr("\r\n| In-Application Programing Application (Version 1.0) |");
IAP_SerialSendStr("\r\n+----command----+-----------------function------------------+");
IAP_SerialSendStr("\r\n| 1: FWUPDATA | Update the firmware to flash |");
IAP_SerialSendStr("\r\n| 2: FWERASE | Erase the current firmware |");
IAP_SerialSendStr("\r\n| 3: BOOT | Excute the current firmware |");
IAP_SerialSendStr("\r\n| 4:REBOOT | Reboot |");
IAP_SerialSendStr("\r\n| ?: HELP | Display this help |");
IAP_SerialSendStr("\r\n+===========================================================+");
IAP_SerialSendStr("\r\n\r\n");
IAP_SerialSendStr("STM32-IAP>>");
}

上面的界面是不是设计得很漂亮。界面用各种符号实现了一个类似表格的界面,界面的第一行强调了版权所属以及设计时间,第二行在讲明bootloader的总体功能及版本,之后的几行就是具体罗列bootloader能实现的具体功能及说明,最后还有设计“STM32-IAP>>"字样,提示输入选择。
先实现界面中罗列的第一个功能:FWUPDATA即固件升级。要实现固件升级自然就是将数据固化到芯片flash中去的过程。 flash空间的程序烧写代码如下:

extern u8 rcvTimeout; //接收超时标志

/*************************************************************
Function : IAP_UpdataProgram
Description: 更新程序
Input : none
return : none
*************************************************************/
static void IAP_UpdataProgram(void)
{
u8 blockNum = 0; //块,一块4页,对于STM32F10X_HD来说,1页2Kbytes
u8 n = 0;
u8 data = 0;
u8 datalow = 0;
u8 datahigh = 0;
u32 UserMemoryMask = 0;

rcvTimeout = 0; //清除接收超时标志位
blockNum = (IAP_ADDR - FLASH_BASE_ADDR) >> 12; //计算flash块
UserMemoryMask = ((u32)(~((1 << blockNum) - 1)));//计算掩码
//查看块所在区域是否写保护
if((FLASH_GetWriteProtectionOptionByte() & UserMemoryMask) != UserMemoryMask)
{
FLASH_EraseOptionBytes (); //关闭写保护
}
while(1)
{
switch(n)
{
case 0:
if(IAP_BufferRead(&data)) //接收地字节数据
{
datalow = data;
n = 1;
}
else
{
break;
}
case 1:
if(IAP_BufferRead(&data)) //接收高字节数据
{
datahigh = data;
n = 0;
IAP_FlashProgramdata(((u16)(datalow)) | ((u16)(datahigh << 8)));
}
if(rcvTimeout)//接收超时,错误或者接收结束
{
datahigh = 0xff;
n = 0;
IAP_FlashProgramdata(((u16)(datalow)) | ((u16)(datahigh << 8)));
}
default:
break;
}
if(rcvTimeout)//接收超时
{
break;
}
}
}

要想烧写程序到的flash空间中去,首先需要检测这段空间是否写保护了,如果被写保护了,则关闭写保护。接着在while(1)中每次循环就读读取2个字节拼凑成半字,然后调用IAP_FlashProgramdata()函数将半字烧写进flash中。这里有人会问,为什么不凑齐4个字节即一个字的时候,再烧写进flash中?这样做自然也可以,但是代码实现相对复杂。下面在给出IAP_FlashProgramdata()函数代码:

/*************************************************************
Function : IAP_FlashProgramdata
Description: 烧写数据
Input : data-要烧写的数据
return : none
*************************************************************/
static void IAP_FlashProgramdata(u16 data)
{
static u32 flashwptr = IAP_ADDR;

FLASH_Unlock();//flash上锁
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);//清除flash相关标志位
FLASH_ProgramHalfWord(flashwptr, data); //烧写半字数据
if(flashwptr == IAP_ADDR)//开始烧写
{
IAP_SerialSendStr("\r\nUpdating firmware to 0x8005000 ");
}
IAP_SerialSendStr(".");//显示烧写进程
flashwptr = flashwptr + 2;//移动烧写地址
FLASH_Lock();//flash解锁
}

在每次烧写flash一个字或者半字时需要将Flash解锁,如果没有解锁,就不会烧写成功的,在烧写完后在将flash锁上,这么做是出于安全考虑。代码中IAP_ADDR就是升级的目标flash地址,在IAP.h中定义,这里我定义的地址是0x8005000,在最开始烧写时,会在串口界面上显示:"Updating firmware to 0x8005000"信息。然后每烧写完成后打印'.',这样的话就可以在串口上看到烧写的进度了。
再来实现界面中的第二个功能:FWERASE擦除flash空间代码。这里的擦除指的是擦除IAP_ADDR地址后的flash空间。需要注意的是flash的擦除并不像烧写一样可以一个字或半字地进行操作,对于擦除来说,它的最小单位是page页,它只能实现页擦除,每一页的也的大小更具处理器的类型而略有不同,在IAP.h中会给出差异的。flash擦除的代码如下:

/*************************************************************
Function : IAP_FlashEease
Description: 擦除Flash
Input : none
return : none
*************************************************************/
static void IAP_FlashEease(void)
{
u16 eraseCounter = 0;
u16 nbrOfPage = 0;

nbrOfPage = (FLASH_BASE_ADDR + FLASH_SIZE - IAP_ADDR)/PAGE_SIZE;//计算page数

FLASH_Unlock(); //解除flash擦写锁定
FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);//清除flash相关标志位
for(eraseCounter = 0; eraseCounter < nbrOfPage; eraseCounter++)//开始擦除
{
IAP_SerialSendStr(".");//显示进度
FLASH_ErasePage(IAP_ADDR + (eraseCounter * PAGE_SIZE));//擦除
}
FLASH_Lock();//flash擦写锁定
}

再来实现界面中的第三个功能:BOOT启动。BOOT启动即程序运行指定空间代码。这个功能的实现需要一些技巧,还是先来看它的相关代码:

typedef void (*pFunction)(void);
pFunction Jump_To_Application;
u32 JumpAddress; //跳转地址

/*************************************************************
Function : IAP_JumpToApplication
Description: 跳转到升级程序处
Input : none
return : none
*************************************************************/
void IAP_JumpToApplication(void)
{
if(((*(__IO u32 *)IAP_ADDR) & 0x2FFE0000) == 0x20000000)//有升级代码,IAP_ADDR地址处理应指向主堆栈区,即0x20000000
{
JumpAddress = *(__IO u32 *)(IAP_ADDR + 4);//获取复位地址
Jump_To_Application = (pFunction)JumpAddress;//函数指针指向复位地址
__set_MSP(*(__IO u32*)IAP_ADDR);//设置主堆栈指针MSP指向升级机制IAP_ADDR
Jump_To_Application();//跳转到升级代码处
}
}

代码中可以看到,在最开始处,自定义了一个无参数无返回值的函数指针,然后定义了指向这种结构的函数指针Jump_To_Application,还定义了一个变量JumpAddress。JumpAddress 获取升级代码复位处地址(JumpAddress = *(__IO u32 *)(IAP_ADDR + 4);)然后函数指针Jump_To_Application指向这个代码,接下去设置主堆栈地址(__set_MSP(*(__IO u32*)IAP_ADDR);)其中__set_MSP函数在core_cm3.c中定义。函数指针指向升级空间复位地址处以及主堆栈地址指向升级空间主堆栈地址处,这两个条件满足后,就可以执行升级程序了(Jump_To_Application();)。这里不能忽略的是if里的条件,条件肯呢过理解起来有困难,这里稍微解释下:判断IAP_ADDR地址处即升级代码的指向的主堆栈地址是不是0x20000000(RAM空间的起始地址),如果是说明有升级代码,如果不是则说明升级处没有升级代码。
再来实现界面中的第四个功能:REBOOT重启。这里的REBOOT的意思可能跟你理解有出入,我将重新显示界面功能作为REBOOT功能。这里没有给出指定函数来实现,后面会讲到这个功能的实现。
最后再实现界面的最后一个功能:HELP帮助功能。代码如下:

/*************************************************************
Function : ShwHelpInfo
Description: 显示帮助信息
Input : none
return : none
*************************************************************/
static void ShwHelpInfo(void)
{
IAP_SerialSendStr("\r\nEnter '1' to updtate you apllication code!");
IAP_SerialSendStr("\r\nRnter '2' to erase the current application code!");
IAP_SerialSendStr("\r\nEnter '3' to go to excute the current application code!");
IAP_SerialSendStr("\r\nEnter '4' to restart the system!");
IAP_SerialSendStr("\r\nEnter '?' to show the help infomation!\r\n");
}

这样的界面的功能相关的函数都编写好了,下面需要在编写一个用户选择功能的函数,这个函数中根据用户的选择调用对应的功能函数实现。也就是说这个函数实现了与用户的交互。在将这个函数之前,需要编写一个函数IAP_GetKey()用来获取用户的输入的值,代码如下:

/*************************************************************
Function : IAP_GetKey
Description: 获取键入值
Input : none
return : 返回键值
*************************************************************/
static u8 IAP_GetKey(void)
{
u8 data;
while(!IAP_BufferRead(&data)){}//从缓冲区中获取键值
return data;
}

当调用这个函数时,没有收到串口数据,函数会一直阻塞。
接下去就要讲讲怎么实现这段交互代码了,代码如下:

/*************************************************************
Function : IAP_WiatForChoose
Description: 功能选择
Input : none
return : none
*************************************************************/
void IAP_WiatForChoose(void)
{
u8 c = 0;

while (1)
{
c = IAP_GetKey();//获取键值
IAP_SerialSendByte(c);//串口返回键值
switch(c)
{
case '1': //FWUPDATA固件升级
if((IAP_GetKey() == '\r'))//检测回车键
{
IAP_SerialSendStr("\r\nErasing...");
IAP_FlashEease();//擦除Flash
IAP_SerialSendStr("\r\nErase done!\r\n");
IAP_SerialSendStr("Please send firmware file!\r\n");
IAP_UpdataProgram();//烧写升级代码
IAP_SerialSendStr("\r\nFirmware update done!\r\n");
IAP_SerialSendStr("Booting...\r\n");
Delay_ms(500);
NVIC_SystemReset();//重启
}
break;
case '2'://FWERASE固件擦除
if((IAP_GetKey() == '\r'))//检测回车键
{
IAP_SerialSendStr("\r\nErasing...");
IAP_FlashEease();//擦除Flash
IAP_SerialSendStr("\r\nErase done!\r\n");
return;//退出循环
}
break;
case '3'://BOOT执行升级程序
if((IAP_GetKey() == '\r'))
{
IAP_SerialSendStr("\r\nBotting...\r\n");
if(((*(__IO u32 *)IAP_ADDR) & 0x2FFE0000) != 0x20000000)
{
IAP_SerialSendStr("No user program! Please download a firmware!\r\n");
}
Delay_ms(500);
NVIC_SystemReset();
return;//退出循环
}
break;
case '4'://REBOOT系统重启
if((IAP_GetKey() == '\r'))//检测回车键
{
IAP_SerialSendStr("\r\nRebooting...\r\n");
return;//退出循环
}
break;
case '?'://HELP帮助
if((IAP_GetKey() == '\r'))
{
ShwHelpInfo();//显示帮助信息
return;//退出循环
}
break;
default:
IAP_SerialSendStr("\r\nInvalid Number! The number should be either 1、2、3、4or5\r\n");
return;//退出循环
}
}
}

这是一个无线循环函数,在显示完界面后,就会等待用户选择输入(c = IAP_GetKey();),接着将用户输入的值回显在界面上(IAP_SerialSendByte(c);),然后根据输入的值执行对应功能代码(switch(c)..case语句),如果输入选项不是制定的功能则会提示信息。用户的输入需要按下键盘的回车才会有效,所以在case语句中,需要再等待用户出入回车(if((IAP_GetKey() == '\r'))),当用户按下回车,才会正真执行对应的代码。当用户选择'1'时,开始固件升级,要想烧写程序,先要擦除flash空间才能烧写,所以这个功能只要调用IAP_FlashEease()和IAP_UpdataProgram()这两个函数并加些进度提示就可以了,最后再软件重启下就可以了;当用户选择'2'即擦除flash空间,则直需调用IAP_FlashEease()函数擦写flash空间,然后return跳出循环退出函数。当用户选择'3',BOOT,先判断下升级处代码是否有效(if(((*(__IO u32 *)IAP_ADDR) & 0x2FFE0000) != 0x20000000)),如果无效,则显示提示信息,然后软件重启下;当用户选择‘4’REBOOT,则显示相关信息,然后退出函数;当用户选择'?'HELP时,则调用ShwHelpInfo()显示帮助信息。
这样的话IAP.c文件代码就全部写完了,下面在各处IAP.h的代码:

#ifndef __IAP_H__
#define __IAP_H__
#include "stm32f10x.h"

#define FLASH_BASE_ADDR 0x8000000 //Flash基地址
#define IAP_ADDR 0x8005000 //升级代码地址

#if defined (STM32F10X_MD) || defined (STM32F10X_MD_VL)
#define PAGE_SIZE (0x400) // 1 Kbyte
#define FLASH_SIZE (0x20000) // 128 KBytes
#elif defined STM32F10X_CL
#define PAGE_SIZE (0x800) // 2 Kbytes
#define FLASH_SIZE (0x40000) // 256 KBytes
#elif defined STM32F10X_HD || defined (STM32F10X_HD_VL)
#define PAGE_SIZE (0x800) // 2 Kbytes
#define FLASH_SIZE (0x80000) // 512 KBytes
#elif defined STM32F10X_XL
#define PAGE_SIZE (0x800) // 2 Kbytes
#define FLASH_SIZE (0x100000) // 1 MByte
#else
#error "Please select first the STM32 device to be used (in stm32f10x.h)"
#endif

void IAP_Init(void);
void IAP_SerialSendStr(u8 *s);
void IAP_ShowMenu(void);
void IAP_WiatForChoose(void);
void IAP_BufferWrite(void);
void IAP_JumpToApplication(void);

#endif

在这个.h文件中可以看到我们的升级处地址IAP_ADDR为0x8005000,下面还有定义了不同型号的STM32对应的页大小以及flash空间,我使用的是STM32F103ZET6处理器,属于STM32F10X_HD系列处理器,所以他的有0x80000即512K的flash空间,它的页大小为2K。

3、stm32f10x_it.c文件的修改
由于使用了我之前写的软定时器,所以需要编写它的中断服务函数:

/*************************************************************
Function : TIM2_IRQHandler
Description: 定时器2中断服务程序
Input : none
return : none
*************************************************************/
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
SoftTimer_TimerExecute();
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}

还要编写串口的中断服务程序:

/*************************************************************
Function : USART1_IRQHandler
Description: 串口1中断服务程序
Input : none
return : none
*************************************************************/
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)//检测接收中断标志位
{
SoftTimer_TimerStart(0, 100, RcvTimeoutSet, (void*)0, TIMER_ONESHOT);//开启软定时器1
IAP_BufferWrite();//存放接收到的数据
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除标志位
}
}

在串口的中断函数中,启动了一个软定时器,定时时间为100ms,即用于定时相邻两个字节的间隔是否超时。调用IAP_BufferWrite()将接收到的数据保存在环形缓冲区中。                                    
既然开启了一个软定时器,还需要编写它的超时回调函数,在这个函数中,将超时标志位置1,代码如下:

u8 rcvTimeout = 0; //接收超时标志

/*************************************************************
Function : RcvTimeoutSet
Description: 串口接收数据超时
Input : parameter-参数
return : none
*************************************************************/
void RcvTimeoutSet(void *parameter)
{
rcvTimeout = 1; //设置接收超时标志
}

4、main函数的编写
串口升级的流程一般是:根据一个按键是否按下,在决定是升级程序还是执行升级后的代码,所以需要先初始化一个按键以及检测函数。我的开发板PA8d对应的就是一个按键,它的代码如下:

/*************************************************************
Function : KeyInit
Description: 初始化按键
Input : none
return : none
*************************************************************/
void KeyInit (void)
{
GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA , &GPIO_InitStructure);
}

/*************************************************************
Function : GetKey
Description: 获取按键状态
Input : none
return : none
*************************************************************/
u8 GetKey (void)
{
return (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8));
}

然后就也可以编写main函数了,代码如下:

/*************************************************************
Function : main
Description: main入口
Input : none
return : none
*************************************************************/
int main(void)
{
BSP_Init();//板子初始化
KeyInit();//初始化按键
if(!GetKey ())//按键按下,进入升级界面
{
set: IAP_Init();//初始化串口
SoftTimer_Init();//初始化软定时器
shw: IAP_ShowMenu();//显示功能菜单
IAP_WiatForChoose(); //等待选择界面
goto shw;//重新显示界面
}
else
{
IAP_JumpToApplication();//跳转到升级出代码执行
goto set;//没有升级程序或者升级程序错误才会执行到这句,然后天转到升级界面
}
}

当系统上电过后,没有检测到按键按下,则跳到升级代码去执行,如果存在升级代码,程序是不会执行到IAP_JumpToApplication()后面的goto set;语句的,但如果不存在升级代码,则会利用goto跳到串口升级的代码中去让用户去下载升级代码。
当系统上电后,检测到按键按下,就去指向串口升级代码。代码执行到IAP_WiatForChoose()函数时会更具用户选择会否跳出这个函数,如果跳出了这个函数,就会执行goto语句跳到shw代码处,即重新显示一下界面。
这里可能有人提出:你怎么用goto语句,不是说最好不要用goto语句吗?其实,之所以限制goto的使用是怕代码的错乱。但是如果你在一个函数中使用goto语句,而且goto的目标也在这个函数中,这么使用反而减少了代码量增加了理解,正如上面的代码,goto与goto的目的地都在main函数中,明眼人一看就知道程序的流程。
 
 
5、测试
当系统上电的时候,因为没有升级程序,所以自动会进入串口升级程序中,最先会显示下面的界面:
STM32串口IAP - ziye334 - ziye334的博客
所以下面来下载升级程序,输入1,按下回车,先会擦除flash空间就会提示你输入升级文件:
STM32串口IAP - ziye334 - ziye334的博客
  在Transfer->Send Binary,选择要升级的.bn文件,然后串口就开始烧写收到的数据,并打印进度,程序烧写完之后就会自动运行升级后的代码,如下:
STM32串口IAP - ziye334 - ziye334的博客
  开发板重新上电,并按下按键,又会显示菜单,然后输入2,回车,就开始擦除flash,擦除完成后,就会重新显示界面如下:
STM32串口IAP - ziye334 - ziye334的博客
  接下去用户输入3,回车,就会执行升级后的代码,如下:
STM32串口IAP - ziye334 - ziye334的博客
  系统重新上电,同时按下按键,又会进入到这个菜单界面,输入4,回车,就会重新显示菜单界面,如下:
STM32串口IAP - ziye334 - ziye334的博客
再输入?,回车,就会显示帮助信息,然后又会重新显示菜单界面,如下:
STM32串口IAP - ziye334 - ziye334的博客

关键字:STM32  串口  IAP 引用地址:STM32串口IAP

上一篇:STM32升级文件的制作
下一篇:STM32 多路软定时器

推荐阅读最新更新时间:2024-03-16 15:14

技术文章—详解串口转换CAN模块“透明带标识转换”
UART转CAN的应用已广泛应用于各行各业,因此对于数据帧转换的形式要求也逐渐增多,目前主流的转换形式包括透明转换、透明带标识转换以及自定义转换。具体是如何实现?本文将为大家介绍其中的透明带标识转换。 1 . 适用场景 串口转CAN模块在什么时候需要用到呢?一是老产品面临升级,需要用到CAN总线通信,但硬件平台中的MCU没有集成CAN总线的控制器。二是选用的MCU已经包含CAN总线接口,但数量上不能满足项目需求。若出现类似以上两种情况且MCU有闲置串口,则可以选用串口转CAN模块解决。 图1 应用行业 2 . 使用方法 该类模块可以很方便地嵌入到具有UART接口的设备中,在不需改变原有硬件结构的前提下使设
[汽车电子]
技术文章—详解<font color='red'>串口</font>转换CAN模块“透明带标识转换”
STM32 禁用swd-jtag下载口后,重新下载程序的方法
由于工作需要,复用了PA15的时候,程序禁用swd-jtag功能,网上多方查找解决方法。 在重新上电的时候,保证BOOT0为高电平,BOOT1为低电平,即可禁止程序从烧写过的代码启动,这时,重新烧写程序即可。 STM32三种启动模式对应的存储介质均是芯片内置的,它们是: 1)用户闪存=芯片内置的Flash。 2)SRAM=芯片内置的RAM区,就是内存啦。 3)系统存储器=芯片内部一块特定的区域,芯片出厂时在这个区域预置了一段Bootloader,就是通常说的ISP程序。这个区域的内容在芯片出厂后没有人能够修改或擦除,即它是一个ROROMM区。 在每个STM32的芯片上都有两个管脚BOOT0和BOOT1,这两个管脚在芯片复位
[单片机]
FlyMcu串口ISP下载STM32程序教程
开始写之前也是不能用的,但是为了使用,决定边探索怎么使用边写一篇博客! 最开始用过正点原子的开发板进行ISP下载,感觉挺好用,按照说明就好。 但是正点原子的板子用的是如下的一块电路: 这个电路很简单使用,但是如果你不懂,也很纠结,这里感觉有篇帖子写的很详细,可供大家参考,就不自己解释了,链接如下: http://www.51hei.com/bbs/dpj-35947-1.html 但是,很多人会想用一个USB转串口的小模块下载程序,怎么弄呢? 首先有一个小串口模块,注意转换为的电平是3.3V,不是标准的232电平那种的,东西如下 之后呢,将RXD,TXD,GND,VCC缺一不可的连接到你的STM32板子
[单片机]
FlyMcu<font color='red'>串口</font>ISP下载<font color='red'>STM32</font>程序教程
stm32 启动过程
当前的嵌入式应用程序开发过程里,并且C语言成为了绝大部分场合的最佳选择。如此一来main函数似乎成为了理所当然的起点——因为C程序往往从main函数开始执行。但一个经常会被忽略的问题是:微控制器(单片机)上电后,是如何寻找到并执行main函数的呢?很显然微控制器无法从硬件上定位main函数的入口地址,因为使用C语言作为开发语言后,变量/函数的地址便由编译器在编译时自行分配,这样一来main函数的入口地址在微控制器的内部存储空间中不再是绝对不变的。相信读者都可以回答这个问题,答案也许大同小异,但肯定都有个关键词,叫“启动文件”,用英文单词来描述是“Bootloader”。 无论性能高下,结构简繁,价格贵贱,每一种微控制器(处理器)都
[单片机]
分享一个STM32菜单框架
相信很多攻城狮都用过液晶屏,想写好一点的ui好像不太可能或且花费很多时间,直接写吧,感觉好像很零碎,coding都怕了。 下面介绍一个简单易用的菜单框架,你会发现它能做多层菜单而且结果清晰。 基本原理: 如上图液晶显示一屏我们定义为一个page,page中的项目定义为i te m;这样page就是item的容器了。当我们选中其中的一个item进去后是不是又是一个page呢,如下图。 这样的话每一个item的下面都对应一个page,这样是不是就构成一个多层的菜单了。 他们是什么关系呢? 一个page中有item,那么用结构体就可以实现啦;item下面又有page,那么在item中加一个page的指针指向item
[单片机]
分享一个<font color='red'>STM32</font>菜单框架
STM32_EXIT中断
今天讲解“STM32F103 EXIT中断”,关于EXIT中断里面有很多学问,以我的工作经验来看,有几点是特别容易犯错的,我会在文章最后重点讲解。 每次讲解的不仅仅是基础,而是重点,不起眼的重点,容易被人忽视的重点。关注微信公众号“EmbeddDeveloper”还有更多精彩等着你。 今天提供并讲解的软件工程,基于前面的软件工程“TIM延时”修改而来。若有疑问,请关注微信公众号获取更多信息。 本着免费分享的原则,将讲解的工程源代码分享给大家,还望看到的朋友分享、关注和推广一下微信公众号,增加一下人气。 每天提供下载的“软件工程”都是在硬件板子上进行多次测试、并保证没问题才上传至360云盘。 今天的软件工程下载地
[单片机]
STM32_EXIT中断
STM32 控制lcm液晶ILI9341驱动的液晶驱动程序
/* 选择BANK1-BORSRAM1 连接 TFT,地址范围为0X60000000~0X63FFFFFF * FSMC_A16 接LCD的DC(寄存器/数据选择)脚 * 16 bit = FSMC 对应HADDR * 寄存器基地址 = 0X60000000 * RAM基地址 = 0X60020000 = 0X60000000+2^16*2 = 0X60000000 + 0X20000 = 0X60020000 * 当选择不同的地址线时,地址要重新计算。 */ //#define Bank1_LCD_D ((u32)0x60020000) //Disp Data ADDR //#define Bank1_LCD_C ((u3
[单片机]
STM32学习笔记—DAC基础内容及常见问题
DAC,Digital-to-Analog Converter(数模转换器),DA转换和AD转换有着同样重要的作用,在许多场合都能看到DAC的应用。 今天是第8篇分享,《STM32学习笔记》之DAC基础内容及常见问题。 DA转换器是把数字量转变成模拟量的器件,按模拟量输出类型通常分为:电流和电压输出类型。常见的DAC是电压输出型,在STM32中集成的DAC转换模块为电压输出型数模转换器。 STM32 DAC 基础内容 STM32内部集成的DAC输出通道和功能与型号有关,一般有1到3个通道。 下面结合STM32F4描述一下DAC基本的参数信息: 1. DAC分辨率 分辨率决定了DAC的转换精度,目前STM32内部集成的DA
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
热门活动
换一批
更多
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

更多精选电路图
换一换 更多 相关热搜器件
更多每日新闻
随便看看
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved