你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32 IAP固件升级(三)

[复制链接]
STMCU小助手 发布时间:2022-2-4 22:00
一、前言
前面介绍了IAP需要的一些基础知识,区域怎么划分,还有启动跳转过程等。当然如果需要实现IAP,通讯接口的驱动是不可或缺的。例如可以使用串口(USART)、以太网(ETH)、USB等通讯接口来进行数据收发。本实验就不使用复杂的通讯接口了,使用比较常用又比较简单的串口。当然将串口用好也不是那么简单的,使用串口中断接收数据效率一般比较低。所以为了提高效率,这里使用DMA(直接存储器存取)+USART(串口)接收数据。
注意:下面涉及到串口的基础配置这些就不过多解释了,直接上源码了。如果不会配置的,可以去看看其他大神写的博客,或者去看看一些教学视频。
一、串口配置
STM32F103有三个串口(USART1,USART2,USART3)。在实验中,因为USART1主要用来程序的调试,所以使用USART2来做IAP的通讯接口。
1、USART2的配置程序如下:a.初始化GPIO
  1. void USART2_GPIO_Config(void)
  2. {
  3.         GPIO_InitTypeDef GPIO_InitStructure;

  4.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  5.         /* 初始化 USART2_TX  ----- PA2 */
  6.         GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;
  7.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  8.         GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;       
  9.         GPIO_Init(GPIOA, &GPIO_InitStructure);

  10.         /* 初始化 USART2_RX  ----- PA3 */
  11.         GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_3;
  12.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  13.         GPIO_Init(GPIOA, &GPIO_InitStructure);  
  14. }
复制代码


b.初始化串口
在STM32的串口里支持同步模式和异步模式,我门通常使用的是异步模式;详细的是使用说明参考《STM32中文参考手册》。
同步模式虽然少用,但是它也是有优点的;在使用多一条同步时钟线的情况下,数据传输会更可靠,更可控。
下面的配置禁止了同步模式,使用的是异步模式(通常使用异步模式 USART_ClockInitStructure 数据不设置也是可以的)。
  1. void USART2_Config(u32 baudrate)
  2. {
  3.         USART_InitTypeDef USART_InitStructure;
  4.         USART_ClockInitTypeDef  USART_ClockInitStructure;
  5.         /* 初始化时钟 */
  6.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
  7. /************************使用异步模式,这个不用配置也可以*****************************/
  8.         /* 关闭同步时钟(即关闭串口的同步模式; 需要使用同步模式,则使能这个) */
  9.         USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;
  10.         /* 配置时钟稳态值为低电平(即空闲时,时钟线的电平为低电平) */
  11.         USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
  12.         /* 配置时钟相位,在时钟的第二个边沿进行数据捕获 */
  13.         USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;
  14.         /* 最后一个数据位不从时钟线上输出 */
  15.         USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;
  16.         /* 初始化 USART2 的同步参数,这里没有使用同步时钟 */
  17.         USART_ClockInit(USART2, &USART_ClockInitStructure);
  18. /************************使用异步模式,这个不用配置也可以*****************************/

  19.         /* 设置波特率 */
  20.         USART_InitStructure.USART_BaudRate = baudrate;
  21.         /* 设置数据位长度为 8 */
  22.         USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  23.         /* 设置停止位是1 */
  24.         USART_InitStructure.USART_StopBits = USART_StopBits_1;
  25.         /* 不适用奇偶校验 */
  26.         USART_InitStructure.USART_Parity = USART_Parity_No ;
  27.         /* 关闭硬件流控 */
  28.         USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  29.         /* 使能 USART2 接收和接收 */
  30.         USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  31.         /* 初始化 USART2 的异步参数,在STM32中通常使用这种传输方式 */
  32.         USART_Init(USART2, &USART_InitStructure);
  33. }
复制代码


STM32F103 DMA
1、STM32F103 DMA简单介绍
在STM32里面有两个DMA控制器(DMA1和DMA2)。两个DMA控制器有一共有12个通道(DMA1 7个通道,DMA2 5个通道)。每个DMA通道对应的外设备如下图:
对于详细的DMA介绍参考《STM32中文参考手册》,这里就不过多介绍了。

1627979-20200217190858504-1233226782.png

1627979-20200217190911146-1833204792.png


2、DMA 使用方法
DMA的使用并不复杂,通道配置过程如下:
  • 设置外设起始地址
  • 设置内存的起始地址
  • 设置数据传输的长度(要传输多长的数据)
  • 设置数据位宽
  • 设置数据的传输方向
  • 设置传输模式
  • 设置通道的优先级
  • 使能DMA通道
3、配置 USART2 的 DMA
从上面的两张外设对应DMA通道的图片知到: USART2_RX 对应DMA1的通道6,USART2_TX 对应DMA1的通道7。
a. 配置USART2_RX的DMA
这里只写简单的配置过程。DMA的更详细的使用说明,请参考《STM32中文参考手册》。配置程序如下:
  1. void USART2_DMA_RX_Config(u8* Buffer, s32 NumData)
  2. {
  3.         DMA_InitTypeDef  DMA_InitStructure;
  4.         /* 使能时钟 */
  5.         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);            
  6.         /* 将 DMA1_Channel6 设置为缺省 */
  7.         DMA_DeInit(DMA1_Channel6);
  8.         /* 配置外设的地址为串口的就收寄存器的地址 */
  9.         DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)(&(USART2->DR));       
  10.         /* 设置内存的地址 */
  11.         DMA_InitStructure.DMA_MemoryBaseAddr      = (uint32_t)(Buffer);
  12.         /* 设置传输方向为 外设到内存 */
  13.         DMA_InitStructure.DMA_DIR                 = DMA_DIR_PeripheralSRC;
  14.         /* 设置缓冲区Buffer的大小 */
  15.         DMA_InitStructure.DMA_BufferSize          = NumData;
  16.         /* 禁止外设的的地址自增 */
  17.         DMA_InitStructure.DMA_PeripheralInc       = DMA_PeripheralInc_Disable;
  18.         /* 使能内存地址自增 */
  19.         DMA_InitStructure.DMA_MemoryInc           = DMA_MemoryInc_Enable;
  20.         /* 设置外设每次传输数据的大小为字节(Byte) */
  21.         DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_Byte;
  22.         /* 设置内存每次传输数据的大小为字节(Byte) */
  23.         DMA_InitStructure.DMA_MemoryDataSize      = DMA_MemoryDataSize_Byte;
  24.         /* 将设置DMA传输的模式为循环传输 */
  25.         DMA_InitStructure.DMA_Mode                = DMA_Mode_Circular;
  26.         /* 设置当前DMA通道的优先级为高优先级 */
  27.         DMA_InitStructure.DMA_Priority            = DMA_Priority_High;
  28.         /* 禁止内存到内存传输 */
  29.         DMA_InitStructure.DMA_M2M                 = DMA_M2M_Disable;
  30.        
  31.         /* 初始化DMA1通道6 */
  32.         DMA_Init(DMA1_Channel6, &DMA_InitStructure);
  33. }
复制代码
b. 配置USART2_TX的DMA
  1. void USART2_DMA_TX_Config(u8* Buffer, s32 NumData)
  2. {
  3.         DMA_InitTypeDef  DMA_InitStructure;
  4.         /* 使能时钟 */
  5.         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);            
  6.         /* 将 DMA1_Channel7 设置为缺省 */
  7.         DMA_DeInit(DMA1_Channel7);
  8.         /* 配置外设的地址为串口的发送数据寄存器的地址 */
  9.         DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)(&(USART2->DR));       
  10.         /* 设置内存的地址 */
  11.         DMA_InitStructure.DMA_MemoryBaseAddr      = (uint32_t)(Buffer);
  12.         /* 设置传输方向为 内存到外设 */
  13.         DMA_InitStructure.DMA_DIR                 = DMA_DIR_PeripheralDST;
  14.         /* 设置缓冲区Buffer的大小 */
  15.         DMA_InitStructure.DMA_BufferSize          = NumData;
  16.         /* 禁止外设的的地址自增 */
  17.         DMA_InitStructure.DMA_PeripheralInc       = DMA_PeripheralInc_Disable;
  18.         /* 使能内存地址自增 */
  19.         DMA_InitStructure.DMA_MemoryInc           = DMA_MemoryInc_Enable;
  20.         /* 设置外设每次传输数据的大小为字节(Byte) */
  21.         DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_Byte;
  22.         /* 设置内存每次传输数据的大小为字节(Byte) */
  23.         DMA_InitStructure.DMA_MemoryDataSize      = DMA_MemoryDataSize_Byte;
  24.         /* 将设置DMA传输的模式为正常传输 */
  25.         DMA_InitStructure.DMA_Mode                = DMA_Mode_Normal;
  26.         /* 设置当前DMA通道的优先级为高优先级 */
  27.         DMA_InitStructure.DMA_Priority            = DMA_Priority_High;
  28.         /* 禁止内存到内存传输 */
  29.         DMA_InitStructure.DMA_M2M                 = DMA_M2M_Disable;
  30.         /* 初始化DMA1通道7 */
  31.         DMA_Init(DMA1_Channel7, &DMA_InitStructure);
  32. }
复制代码


三、初始化、使能串口和DMA
1、使能串口和DMA
使能串口和DMA的函数如下:
  1. void USART2_Enable(void)
  2. {
  3.         /* 使能串口2 */
  4.         USART_Cmd(USART2, ENABLE);
  5.         /* 使能串口2的DMA发送 */
  6.         USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
  7.         /* 使能串口2的DMA接收 */
  8.         USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);
  9.         /* 使能DMA1通道7 */
  10.         DMA_Cmd(DMA1_Channel7, ENABLE);       
  11.         /* 使能DMA1通道6 */
  12.         DMA_Cmd(DMA1_Channel6, ENABLE);       
  13. }
复制代码


2、初始化函数
USART2_DMA_TxBuff和USART2_DMA_RxBuff是一个全局数组,用于DMA发送或者接收数据;以后需要发送数据和接收数据就操作这两个数组(buffer)就可以了
  1. void USART2_Init(u32 baudrate)
  2. {
  3.         /* 初始化USART2的GPIO */
  4.         USART2_GPIO_Config();
  5.         /* 设置串口 */
  6.         USART2_Config(baudrate);
  7.        
  8.         /* 设置USART2的发送DMA */
  9.         USART2_DMA_TX_Config(USART2_DMA_TxBuff, 0);
  10.         /* 设置USART2的接收DMA */
  11.         USART2_DMA_RX_Config(USART2_DMA_RxBuff, USART2_DMA_RXBUFFLEN);
  12.         /* 使能 */
  13.         USART2_Enable();
  14. }
复制代码

*** 配置到这里,基本就可以使用DMA接收和发送数据了;使用DMA发送数据是比较简单的,但是使用DMA接收数据,需要费一点点功夫。 ***

四、DMA接收串口数据
1、处理接收到的数据
由于使用了DMA接收数据,所以不需要再操作 USART2 的 DR 寄存器了(数据寄存器);硬件会直接将数据copy到USART2_DMA_RxBuff数组里面。又因为在接收的DMA设置函数里将DMA的模式配置成了循环模式,所以USART2_DMA_RxBuff的本质是一个环形缓冲区。
接下来就先上程序再慢慢分析吧。接收处理的代码如下:
  1. /* 说明一下。函数里的 USART2_Service 是一个全局的函数指针,用于处理接收到的数据的 (需要怎么处理,就看业务的需求是什么了) */
  2. /* USART2_Service 函数指针定义如下: void (*USART2_Service)(u8 *buff, u16 len); */
  3. /* 用户需要自己初始化这个函数指针 */
  4. /* 可以将 USART2_Service 设置为一个回调函数的样式;在这里就使用全局的函数指针了 */
  5. void USART2_DMA_Rx(void)
  6. {        
  7.         u32 pos;
  8.         /* USART2_RX_Pos 指向环形缓冲区数据的头部 */
  9.         static volatile u16   USART2_RX_Pos;
  10.        
  11.         /* DMA1_Channel6 -> CNDTR 接收到的数据查长度 */
  12.         /* 算出pos当前的指向,缓冲区数据的尾部 */
  13.         pos = USART2_DMA_RXBUFFLEN - (DMA1_Channel6 -> CNDTR);
  14.         /* 当前的pos上一次的pos大 */
  15.         if(pos > USART2_RX_Pos)
  16.         {
  17.                 /* 算个出buff的起始地址,算出接收到数据的长度 */
  18.                 USART2_Service(USART2_DMA_RxBuff + USART2_RX_Pos, pos - USART2_RX_Pos);
  19.                 /* 改变buffer的起始指向 */
  20.                 USART2_RX_Pos = pos;
  21.         }
  22.         /* 如果上一次的pos比当前的pos大,说明有部分数据在前面 */
  23.         else if(pos < USART2_RX_Pos)
  24.         {
  25.                 /* 计算buffer的起始地址,先将后面部分的数据放入到内存管理 */
  26.                 USART2_Service(USART2_DMA_RxBuff + USART2_RX_Pos, USART2_DMA_RXBUFFLEN - USART2_RX_Pos);
  27.                 if(pos != 0)
  28.                 {
  29.                         /* 如果头部还有数据,也将头部前面的数据放入内存管理 */
  30.                         USART2_Service(USART2_DMA_RxBuff, pos);
  31.                 }
  32.                 /* 改变头部的起始指向 */
  33.                 USART2_RX_Pos = pos;
  34.         }
  35.         else
  36.         {;}
  37. }
复制代码

*** 下面具体分析 USART2_DMA_Rx函数的原理 ***

2、代码块1的分析
  1.     /* 当前的pos上一次的pos大 */
  2.     if(pos > USART2_RX_Pos)
  3.     {
  4.         /* 算个出buff的起始地址,算出接收到数据的长度 */
  5.         USART2_Service(USART2_DMA_RxBuff + USART2_RX_Pos, pos - USART2_RX_Pos);
  6.         /* 改变buffer的起始指向 */
  7.         USART2_RX_Pos = pos;
  8.     }
复制代码

这部分代码是处理接收到的数据是下面的情况(也就是需要处理绿色区的数据)

1627979-20200218234327546-246178205.png

3、代码块2的分析
  1.     /* 如果上一次的pos比当前的pos大,说明有部分数据在前面 */
  2.     else if(pos < USART2_RX_Pos)
  3.     {
  4.         /* 计算buffer的起始地址,先将后面部分的数据放入到内存管理 */
  5.         USART2_Service(USART2_DMA_RxBuff + USART2_RX_Pos, USART2_DMA_RXBUFFLEN - USART2_RX_Pos);
  6.         if(pos != 0)
  7.         {
  8.             /* 如果头部还有数据,也将头部前面的数据放入内存管理 */
  9.             USART2_Service(USART2_DMA_RxBuff, pos);
  10.         }
  11.         /* 改变头部的起始指向 */
  12.         USART2_RX_Pos = pos;
  13.     }
复制代码

这部分代码是处理接收到的数据是下面的情况(也就是需要处理绿色区的数据)

五、总结
总结一下使用方法:
  • 调用 USART2_Init 函数初始化串口和DMA
  • 初始化 USART2_Service 函数指针;这个函数的参数有两个,一个是数据的指针,一个是接收到数据的长度
  • 通过大循环不断调用USART2_DMA_Rx函数处理接收到的数据;
实验现象:
实验是在application环境下的,通过给USART2发送数据,USART2接收到的数据后再从USART1里打印出来。实验现象如下图:
左边是给USART2发送数据,右边是将接收到的数据通过USART1打印出来。

1627979-20200305224532936-1354975181.png

***注意: ***
使用这个方法接收串口的数据有优点也有缺点
  • 优点是:效率比较高,使用比较方便
  • 缺点是:实时性的要求比较高。如果缓冲区比较小,实时性又比较低;当有大量数据连续传来时,很容易就会造成数据的覆盖。在单线程里面使用时,尽量少使用大的延时函数。


1627979-20200218235143257-1496577085.png
收藏 评论0 发布时间:2022-2-4 22:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版