STM32F103/AT32F403 RS485(usart1) IAP升级

#AT32 USART IAP 快速使用方法


前言

在很多应用场合,现场需要对固件代码进行升级但不便拆装设备,这时候可以采用485模式进行IAP升级,和usart iap升级代码部分是一样的,只是485电路要采用硬件自动切换收发,并且速率不能太快,实验19200bps下载速率正常。


下面以AT32F403AVGT7为例,以AT-START-AT32F403A 实验板的硬件条件详细介绍操作流程

一、概述

IAP(In Application Programming)即在应用编程,IAP 是用户自己的程序在运行过程中对 User
Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件
程序进行更新升级。通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件
程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如
USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代
码。这两部分项目代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一个项目代码开始运
行,它作如下操作:

  1. 检查是否需要对第二部分代码进行更新
  2. 如果不需要更新则转到4)
  3. 执行更新操作
  4. 跳转到第二部分代码执行
    在这里插入图片描述
    在上图所示流程中,MCU 复位后,还是从 0x08000004 地址取出复位中断向量的地址,并跳转到复
    位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数,如图标号①所示;在执
    行完 IAP 以后(即将新的 APP 代码写入 AT32 的 FLASH,灰底部分。新程序的复位中断向量起始地
    址为 0x08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并
    跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号②和③所示,同样
    main 函数为一个死循环,并且注意到此时 AT32 的 FLASH,在不同位置上,共有两个中断向量表。
    在 main 函数执行过程中,如果 CPU 得到一个中断请求,PC 指针仍强制跳转到地址 0x08000004 中
    断向量表处,而不是新程序的中断向量表,如图标号④所示;程序再根据我们设置的中断向量表偏移
    量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;在执行完中断服务程序后,程序返回
    main 函数继续运行,如图标号⑥所示。
    通过以上两个过程的分析,我们知道 IAP 程序必须满足两个要求:
  5. 新程序必须在IAP程序之后的某个偏移量为x的地址开始
  6. 必须将新程序的中断向量表相应的移动,移动的偏移量为x

二、AT32 USART IAP 快速使用方法

1.硬件电路

单片机控制,也可以用AT32F403AVGT7代替
在这里插入图片描述
485硬件收发电路
在这里插入图片描述

三、软件资源

  1. tool_release  IAP_Programmer.exe,PC机tool,用于演示IAP升级流程
  2. source_code
     bootloader,bootloader源程序,运行LED2闪烁
     app_led3_toggle,app1源程序,运行LED3闪烁
    注:工程基于keil v5和IAR8.2建立,若用户需要在其他编译环境上使用,请参考
    AT32F403A_407_Firmware_Library_V2.x.x\project\at_start_f403a\templates中各种编译环境(例如IAR6/7/8,keil 4/5,
    eclipse_gcc)进行对应修改即可。

四、IAP demo 使用

  1. 打开bootloader工程源程序,选择对应MCU型号的target编译后下载到实验板
  2. 打开IAP_Programmer.exe
  3. 选择正确的串口、APP下载地址和bin文档,点击Download下载,如下图
  4. 观察LED2/3/4闪烁,LED2闪烁-bootloader工作,LED3闪烁-app1工作
    图 2. IAP demo 上位机
    在这里插入图片描述

五、AT32 USART IAP 程序设置

地址分布
表 1. 地址分布
ITEM Address and Size
app code address 2: 0x800 4000 size: 0x40000(256K Byte)
bootloader code address 1: 0x800 0000 size: 0x4000(16K Byte)
注:bootloader区域最后一个扇区,用于存放防止升级过程掉电的flag,用户编译修改bootloader时,要保证不覆盖
flag的地址。

六、执行流程

IAP 分为 Bootloader 和 App 两部分,应用在 App 中执行,升级过程在 bootloader 中执行。程序执行
整体流程框图如下: 图 3. 程序执行流程
在这里插入图片描述

七、bootloader project 设置

  1. Keil设置
    在这里插入图片描述
  2. bootloader源程序修改Iap.h文件中
    图 5. bootloader project 中 address 2 在程序中设置
    在这里插入图片描述

八、app project 设置

IAP demo 提供了 2 个 app 程序供测试用,皆以 address 2(0x800 4000)为起始地址。app1 LED3
闪烁,app2 LED4 闪烁。以 app1 为例,设计步骤如下:

  1. Keil工程设置
    图 6. app project 中 address 2 在 Keil 设置
    在这里插入图片描述

  2. app1 源程序设置

在这里插入图片描述
3) 编译生成bin文件
通过 User 选项卡,设置编译后调用 fromelf.exe,根据.axf 文件生成.bin 文件,用于 IAP 更新。
通过以上 3 个步骤,我们就可以得到一个.bin 的 APP 程序,通过 bootloader 程序即可实现更新。
4) 开启debug app code功能
如果在设计 app code 过程中需要对 app project 进行单独调试,请按照以下操作。
a) 先下载bootloader工程
b) 再调试app工程

九、bootloader/app 与上位机串口通信协议

  1. 上位机通信协议
    图 8. 上位机通信协议
    在这里插入图片描述
  2. IAP 端下位机通信协议
    在这里插入图片描述
    注: ACK: 0xCCDD
    NACK: 0xEEFF
    Data: 0x31+ Addr + 数据 + chenksum(1byte)
    Addr:4bytes,高位在前
    Kbytes,下载数据,不足2K内容填充0xFF
    Checksum:1byte,4bytes的Addr + 2KBytes数据的校验和的低八位

十、bootloader程序

main.c文件

#include "at32f403a_407_board.h"
#include "at32f403a_407_clock.h"
#include "tmr.h"
#include "usart.h"
#include "flash.h"
#include "iap.h"

/** @addtogroup UTILITIES_examples
  * @{
  */

/** @addtogroup USART_IAP_bootloader
  * @{
  */

/**
  * @brief  main function.
  * @param  none
  * @retval none
  */
int main(void)
{
   
  system_clock_config();
  at32_board_init();

  /* config nvic priority group */
  nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);

  /* check iap_upgrade_flag flag */
  if(flash_upgrade_flag_read() == RESET)
  {
   
    /* check app starting address whether 0x08xxxxxx */
    if(((*(uint32_t*)(APP_START_ADDR + 4)) & 0xFF000000) == 0x08000000)
      app_load(APP_START_ADDR);
  }

  /* init usart used for app update */
  uart_init(19200);

 /* check whether need to upgrade, if yes, response ok to pc-tool */
  if(flash_upgrade_flag_read() != RESET)
    back_ok();

  /* init tmr used for show code running state(led cycle toggle) */
  tmr_init();

  while(1)
  {
   
    /* upgrade handle */
    iap_upgrade_app_handle();
  }
}

iap.c文件

#include "iap.h"
#include "usart.h"
#include "flash.h"
#include "tmr.h"

/** @addtogroup UTILITIES_examples
  * @{
  */

/** @addtogroup USART_IAP_bootloader
  * @{
  */

cmd_data_step_type cmd_data_step = CMD_DATA_IDLE;
cmd_ctr_step_type cmd_ctr_step = CMD_CTR_IDLE;
cmd_data_group_type cmd_data_group_struct;
update_status_type update_status = UPDATE_PRE;
static uint8_t cmd_addr_cnt = 0;
static uint32_t cmd_data_cnt = 0;
iapfun jump_to_app;

/* app_load don't optimize */
#if defined (__CC_ARM)
  #pragma O0
#elif defined (__ICCARM__)
  #pragma optimize=s none
#endif
/**
  * @brief  app load.
  * @param  app_addr
  *         app code starting address
  * @retval none
  */
void app_load(uint32_t app_addr)
{
   
  /* check the address of stack */
  if(((*(uint32_t*)app_addr) - 0x20000000) < (SRAM_SIZE * 1024))
  {
   
    /* disable periph clock */
    crm_periph_clock_enable(CRM_TMR3_PERIPH_CLOCK, FALSE);
    crm_periph_clock_enable(CRM_USART1_PERIPH_CLOCK, FALSE);
    crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, FALSE);

    /* disable nvic irq and clear pending */
    nvic_irq_disable(USART1_IRQn);
    nvic_irq_disable(TMR3_GLOBAL_IRQn);
    __NVIC_ClearPendingIRQ(USART1_IRQn);
    __NVIC_ClearPendingIRQ(TMR3_GLOBAL_IRQn);

    jump_to_app = (iapfun)*(uint32_t*)(app_addr + 4);        /* code second word is reset address */
    __set_MSP(*(uint32_t*)app_addr);                        /* init app stack pointer(code first word is stack address) */
    jump_to_app();                                          /* jump to user app */
  }
}
/**
  * @brief  take data from usart buf.
  * @param  app_addr
  *         app code starting address
  * @retval val
  *         took data
  */
uint8_t data_take(void)
{
   
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
以下是基于STM32F103的USART3串口实现RS485通信的示例代码: ``` #include "stm32f10x.h" #define USART3_DR_Base ((uint32_t)0x40004804) // 定义USART3的GPIO引脚 #define USART3_TX_GPIO_PORT GPIOB #define USART3_TX_GPIO_CLK RCC_APB2Periph_GPIOB #define USART3_TX_PIN GPIO_Pin_10 #define USART3_RX_GPIO_PORT GPIOB #define USART3_RX_GPIO_CLK RCC_APB2Periph_GPIOB #define USART3_RX_PIN GPIO_Pin_11 // 定义RS485的控制引脚 #define RS485_CTRL_GPIO_PORT GPIOB #define RS485_CTRL_GPIO_CLK RCC_APB2Periph_GPIOB #define RS485_CTRL_PIN GPIO_Pin_12 // 定义RS485的发送和接收状态 #define RS485_RX_EN() (RS485_CTRL_GPIO_PORT->BRR = RS485_CTRL_PIN) #define RS485_TX_EN() (RS485_CTRL_GPIO_PORT->BSRR = RS485_CTRL_PIN) // 定义USART3的波特率 #define USART3_BAUDRATE 9600 void USART3_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 使能GPIO和USART的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | USART3_TX_GPIO_CLK | USART3_RX_GPIO_CLK, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); // 配置USART3的GPIO引脚 GPIO_InitStructure.GPIO_Pin = USART3_TX_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(USART3_TX_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = USART3_RX_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(USART3_RX_GPIO_PORT, &GPIO_InitStructure); // 配置RS485控制引脚 GPIO_InitStructure.GPIO_Pin = RS485_CTRL_PIN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(RS485_CTRL_GPIO_PORT, &GPIO_InitStructure); // 配置USART3的参数 USART_InitStructure.USART_BaudRate = USART3_BAUDRATE; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_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_Init(USART3, &USART_InitStructure); // 使能USART3 USART_Cmd(USART3, ENABLE); } void USART3_SendByte(uint8_t byte) { // 等待发送缓冲区为空 while (USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET); // 发送数据 USART_SendData(USART3, byte); } uint8_t USART3_ReceiveByte(void) { // 等待接收到数据 while (USART_GetFlagStatus(USART3, USART_FLAG_RXNE) == RESET); // 读取数据 return USART_ReceiveData(USART3); } void RS485_SendData(uint8_t *data, uint16_t len) { // 切换为发送状态 RS485_TX_EN(); // 逐个发送字节 for (int i = 0; i < len; i++) { USART3_SendByte(data[i]); } // 等待数据发送完成 while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); // 切换为接收状态 RS485_RX_EN(); } void RS485_ReceiveData(uint8_t *data, uint16_t len) { // 切换为接收状态 RS485_RX_EN(); // 逐个接收字节 for (int i = 0; i < len; i++) { data[i] = USART3_ReceiveByte(); } } int main(void) { USART3_Config(); // 发送数据 uint8_t tx_data[] = {0x01, 0x02, 0x03, 0x04}; RS485_SendData(tx_data, sizeof(tx_data)); // 接收数据 uint8_t rx_data[4]; RS485_ReceiveData(rx_data, sizeof(rx_data)); while (1); } ``` 在这个示例代码中,我们首先配置了USART3的GPIO引脚和参数,然后定义了RS485的控制引脚和发送、接收状态的函数。在主函数中,我们先发送了一些数据,然后接收了一些数据。需要注意的是,在发送数据之前需要先切换为发送状态,在接收数据之前需要先切换为接收状态。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

章鱼哥嵌入式开发

坚持不易,你们的鼓励是我的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值