stm32串口dma发送/接收程序

发布者:SereneWanderer最新更新时间:2024-04-22 来源: elecfans关键字:stm32  串口  dma  发送  接收 手机看文章 扫描二维码
随时随地手机看文章

  串口可以配置成用DMA的方式接收数据,不过DMA需要定长才能产生接收中断,如何接收可变长度的数据呢?

  方法有以下3种:

  1.将RX脚与一路时钟外部引脚相连,当串口一帧发完,即可利用此定时器产生超时中断。这个实时性较高,可以做到1个字节实时监测。


  2.不改变硬件,开启一个定时器监控DMA接收,如果超时则产生中断。这个实时性不高,因为超时时间必须要大于需要接收帧的时间,精度不好控制。

  3.STM32单片机有的串口可以监测总线是否处于空闲,如果空闲则产生中断。可以用它来监测DMA接收是否完毕。这种方式实时性很高。

  串口DMA发送:

  发送数据的流程:

  前台程序中有数据要发送,则需要做如下几件事

  1. 在数据发送缓冲区内放好要发送的数据,说明:此数据缓冲区的首地址必须要在DMA初始化的时候写入到DMA配置中去。

  2. 将数据缓冲区内要发送的数据字节数赋值给发送DMA通道,(串口发送DMA和串口接收DAM不是同一个DMA通道)

  3. 开启DMA,一旦开启,则DMA开始发送数据,说明一下:在KEIL调试好的时候,DMA和调试是不同步的,即不管Keil 是什么状态,DMA总是发送数据。

  4. 等待发送完成标志位,即下面的终端服务函数中的第3点设置的标志位。或者根据自己的实际情况来定,是否要一直等待这个标志位,也可以通过状态机的方式来循环查询也可以。或者其他方式。 判断数据发送完成:

  启动DMA并发送完后,产生DMA发送完成中断,在中断函数中做如下几件事:

  1. 清DMA发送完成中断标志位 2. 关闭串口发送DMA通道

  3. 给前台程序设置一个软件标志位,说明数据已经发送完毕

  串口DMA接收:

  接收数据的流程:

  串口接收DMA在初始化的时候就处于开启状态,一直等待数据的到来,在软件上无需做任何事情,只要在初始化配置的时候设置好配置就可以了。

  判断数据数据接收完成:

  这里判断接收完成是通过串口空闲中断的方式实现,即当串口数据流停止后,就会产生IDLE中断。这个中断里面做如下几件事:

  1.关闭串口接收DMA通道,2点原因:1.防止后面又有数据接收到,产生干扰。2.便于DMA的重新配置赋值,下面第4点。

  2. 清除DMA 所有标志位

  3. 从DMA寄存器中获取接收到的数据字节数

  4.重新设置DMA下次要接收的数据字节数,注意,这里是给DMA寄存器重新设置接收的计数值,这个数量只能大于或者等于可能接收的字节数,否则当DMA接收计数器递减到0的时候,又会重载这个计数值,重新循环递减计数,所以接收缓冲区的数据则会被覆盖丢失。

  5. 开启DMA通道,等待下一次的数据接收,注意,对DMA的相关寄存器配置写入,如第4条的写入计数值,必须要在关闭DMA的条件进行,否则操作无效。

  说明一下,STM32的IDLE的中断在串口无数据接收的情况下,是不会一直产生的,产生的条件是这样的,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一断接收的数据断流,没有接收到数据,即产生IDLE中断。

  串口用DMA方式发送和接收,分以下几步:

  1)串口初始化

  2)DMA初始化

  3)发送数据

  4)接收数据

  我们按部就班:

  1) 串口初始化 — 使用串口一

  #define DMASIZE 1024

  // 配置串口一的发送和接收的GPIO口功能,以及中断

  static void _uart1_gpio_init(void)

  {

  NVIC_InitTypeDef NVIC_InitStructure;

  GPIO_InitTypeDef GPIO_InitStructure;

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |

  RCC_APB2Periph_USART1 |

  RCC_APB2Periph_AFIO, ENABLE) ;

  GPIOA-》CRH&=0XFFFFF00F;

  GPIOA-》CRH|=0X000008B0;//IO状态设置 10pin_上拉输入 9pin_推挽输出

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  /* Configure USART1 Rx as input floating */

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* Configure USART1 Tx as alternate function push-pull */

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

  GPIO_Init(GPIOA, &GPIO_InitStructure);

  /* Enable the USART1 Interrupt */

  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel;

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

  NVIC_Init(&NVIC_InitStructure);

  USART_ClearFlag(USART1, USART_FLAG_TC); /* 清发送外城标志,Transmission Complete flag */

  USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);// 采用空闲中断,目的是在产生空闲中断时,说明接收或者发送已经结束,此时可以读取DMA中的数据了。

  //USART_ITConfig(USART1, USART_IT_TC, ENABLE);

  //USART_ITConfig(USART1, USART_IT_FE, ENABLE);

  }

  // 设置对应串口的波特率

  static void _uart_setbaudrate(USART_TypeDef* USARTx,u32 value)

  {

  USART_InitTypeDef USART_InitStructure;

  USART_InitStructure.USART_BaudRate =value;

  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(USARTx, &USART_InitStructure);

  USART_Cmd(USARTx, ENABLE);

  2)初始化DMA

  u8 sendbuf[1024];

  u8 receivebuf[1024];

  static void _uart1_dma_configuration()

  {

  DMA_InitTypeDef DMA_InitStructure;

  /* DMA1 Channel6 (triggered by USART1 Rx event) Config */

  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 ,

  ENABLE);

  /* DMA1 Channel5 (triggered by USART1 Rx event) Config */

  DMA_DeInit(DMA1_Channel5);

  DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;// 初始化外设地址,相当于“哪家快递”

  DMA_InitStructure.DMA_MemoryBaseAddr =(u32)receivebuf;// 内存地址,相当于几号柜

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为数据来源,即为收快递

  DMA_InitStructure.DMA_BufferSize = DMASIZE ;// 缓存容量,即柜子大小

  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增,即柜子对应的快递不变

  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 内存递增

  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设字节宽度,即快递运输快件大小度量(按重量算,还是按体积算)

  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存字节宽度,即店主封装快递的度量(按重量,还是按体质进行封装)

  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 正常模式,即满了就不在接收了,而不是循环存储

  DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;// 优先级很高,对应快递就是加急

  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 内存与外设通信,而非内存到内存

  DMA_Init(DMA1_Channel5, &DMA_InitStructure);// 把参数初始化,即拟好与快递公司的协议

  DMA_Cmd(DMA1_Channel5, ENABLE);// 启动DMA,即与快递公司签订合同,正式生效

  /* DMA1 Channel4 (triggered by USART1 Tx event) Config */

  DMA_DeInit(DMA1_Channel4);

  DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base; // 外设地址,串口1, 即发件的快递

  DMA_InitStructure.DMA_MemoryBaseAddr =(u32)sendbuf;// 发送内存地址

  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;// 外设为传送数据目的地,即发送数据,即快递是发件

  DMA_InitStructure.DMA_BufferSize = 0; //发送长度为0,即未有快递需要发送

  DMA_Init(DMA1_Channel4, &DMA_InitStructure);//初始化

  USART_ITConfig(USART1, USART_IT_TC, ENABLE);// 使能串口发送完成中断

  USART_DMACmd(USART1, USART_DMAReq_Tx|USART_DMAReq_Rx, ENABLE);// 使能DMA串口发送和接受请求

  }

  }


  数据发送

  流程:串口发送数据,全部数据发送完毕后,会产生一个发送中断,所以

  发送数据分为两部分,

  A、发送数据

  B、中断处理

  A、发送数据

  u16 Uart_Send_Data(void* buffer, u16 size)

  {

  if(!size) return 0;// 判断长度是否有效

  while (DMA_GetCurrDataCounter(DMA1_Channel4));// 检查DMA发送通道内是否还有数据

  if(buffer) memcpy(sendbuf, buffer,(size 》 1024?1024:size));

  //DMA发送数据-要先关 设置发送长度 开启DMA

  DMA_Cmd(DMA1_Channel4, DISABLE);

  DMA1_Channel4-》CNDTR = size;// 设置发送长度

  DMA_Cmd(DMA1_Channel4, ENABLE); // 启动DMA发送

  return size;

  }

  B、中断处理

  1)中断处理相关准备工作

  typedef enum _UartEvent_

  {

  E_uart_0 = 0,// 没有事件

  E_uart_tc=0x40, //发送完成

  E_uart_idle=0x80, //接收完成

  }UartEvent;

  u16 receivelen = 0;// 声明接收数据长度

  UartEvent event;//申明一个事件参数

  //清除DMA 缓存,并终止DMA

  void Uart_Dma_Clr(void)

  {

  DMA_Cmd(DMA1_Channel4, DISABLE);

  DMA1_Channel4-》CNDTR=0;

  DMA_Cmd(DMA1_Channel5, DISABLE);

  DMA1_Channel5-》CNDTR=DMASIZE ;

  DMA_Cmd(DMA1_Channel5, ENABLE);

  }

  // 获取一个事件,事件分为发送完成事件和接收完成事件,可以根据事件进行进行处理

  UartEvent Uart_Get_Event(void)

  {

  UartEvent e;

  if(!DMA1_Channel5-》CNDTR) Uart_Dma_Clr();// 如果产生一个事件后,接收数据通道已经没有了缓存空间,进行清除DMA清空

  return event;

  }

  // 清除对应的事件

  void Uart_Clr_Event(UartEvent event_in)

  {

  event&=~event_in;

  }

  2) 中断处理,当所有数据发送完毕,串口1产生一个发送完成中断

  void Uatr1_Back_IRQHandler()

  {

  u8 tem;

  if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)

  {

  tem=USART1-》SR;//先读SR,然后读DR才能清除

  tem=USART1-》DR;

  tem=tem;

  Uart_Set_Event(E_uart_idle);

  receivelen =DMASIZE - DMA1_Channel5-》CNDTR;// 总的buf长度减去剩余buf长度,得到接收到数据的长度

  USART_ClearITPendingBit(USART1, USART_IT_IDLE);

  }

  **if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部数据发送完成,产生该标记**

  {

  USART_ClearITPendingBit(USART1, USART_IT_TC); // 清除完成标记

  DMA_Cmd(DMA1_Channel4, DISABLE); // 关闭DMA

  DMA1_Channel4-》CNDTR=0; // 清除数据长度

  Uart_Set_Event(E_uart_tc); //设置发送完成事件

  }

  }

  4、接收数据

 stm32串口dma发送/接收程序

  根据上图描述,流程如下:

  1、串口接收到数据

  2、DMA自动取走数据

  3、DMA把数据存到内存receive[1024]中

  

  4、串口接收完毕后会产生一个空闲中断

  根据上面流程,我们接收数据需要做到两步:

  1)串口产生一个空闲中断后,设置一个接收完成事件

  中断处理:

  void Uatr1_Back_IRQHandler()

  {

  u8 tem;

  **if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)**

  {

  tem=USART1-》SR;//先读SR,然后读DR才能清除

  tem=USART1-》DR;// 清除DR

  tem=tem; // 防止编译器警告

  Uart_Set_Event(E_uart_idle);// 设置接收完成(空闲)事件

  receivelen =DMASIZE - DMA1_Channel5-》CNDTR;// 总的buf长度减去剩余buf长度,得到接收到数据的长度

  USART_ClearITPendingBit(USART1, USART_IT_IDLE); // 清除空闲中断

  }

  if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部数据发送完成,产生该标记

  {

  USART_ClearITPendingBit(USART1, USART_IT_TC); // 清除完成标记

  DMA_Cmd(DMA1_Channel4, DISABLE); // 关闭DMA

  DMA1_Channel4-》CNDTR=0; // 清除数据长度

  Uart_Set_Event(E_uart_tc); //设置发送完成事件

  }

  }

  2)接收数据函数检测事件,如果发现是接收完成事件,取走数据,并且做相关清除操作

  u8 Uart_Receive_Data(u8*recbuf u16 *revLen)

  {

  u8 *str;

  if( event & E_uart_idle) // 是否产生空闲中断

  {

  str = Uart_Get_Data(revLen);

  memcpy(recbuf,receivebuf,*revLen);

  Uart_Clr_Event(E_uart_idle);

  Uart_Dma_Clr();

  return TRUE;

  }

  else

  {

  revLen = 0;

  return FALSE;

  }

  }


关键字:stm32  串口  dma  发送  接收 引用地址:stm32串口dma发送/接收程序

上一篇:基于STM32的简易四轴飞行器系统的设计实现
下一篇:STM32中GPIO是如何工作的?想知道吗?

推荐阅读最新更新时间:2024-11-12 08:46

STM32中assert_param()的使用
在STM32的固件库和提供的例程中,到处都可以见到assert_param()的使用。如果打开任何一个例程中的stm32f10x_conf.h文件,就可以看到实际上assert_param是一个宏定义; 在固件库中,它的作用就是检测传递给函数的参数是否是有效的参数。 所谓有效的参数是指满足规定范围的参数,比如某个参数的取值范围只能是小于3的正整数,如果给出的参数大于3, 则这个assert_param()可以在运行的程序调用到这个函数时报告错误,使程序员可以及时发现错误,而不必等到程序运行结果的错误而大费周折。 这是一种常见的软件技术,可以在调试阶段帮助程序员快速地排除那些明显的错误。 它确实在程序的运行上牺牲了效率(但只是在调试
[单片机]
关于STM32 使用sprintf 死机问题
在使用 sprintf 函数时遇到的造成死机的两种原因: 1、 指针未声明内存 char *p; sprintf(p, %d,%d,%f ,1,1,2.1); 解决方法:对指针申请内存,或定义成数组类型。 2、打印float/double 类型数据。 解决方法:修改为int类型打印。 有网友说栈空间不足造成的死机,本人测试后以上两种死机原因均为改善。 启动文件中 Heap_Size 为 0x00000200修改为0x00000C00 Heap_Size EQU 0x00000200 Stack_Size EQU 0x00000400 Heap_Size EQU 0x00
[单片机]
STM32之DAC例程
#include stm32f10x.h /* RCC时钟配置 */ void RCC_config() { ErrorStatus HSEStartUpStatus; /* RCC寄存器设置为默认配置 */ RCC_DeInit(); /* 打开外部高速时钟 */ RCC_HSEConfig(RCC_HSE_ON); /* 等待外部高速时钟稳定 */ HSEStartUpStatus = RCC_WaitForHSEStartUp(); if(HSEStartUpStatus == SUCCESS) { /* 设置HCLK = SYSCLK */ RCC_HCLKConfig(RCC_SYSCL
[单片机]
示波器应用 测发射接收延时方法
示波器测发射接收延时方法 1、示波器设置单次触发方式 2、因为接收延时于发射,所以在接收的波形产生点设置触发点信号源给出波形,并在发射端输入波形,在接收端输出波形 3、要将发射和接收的波形错开,避免发射有波形时示波器因为捕获到而触发
[测试测量]
STM32-嵌入式学习笔记1-使用HSE和HSI配置时钟
RCC主要作用:时钟 设置SYSCLK 设置AHB分频因子····配置好这些因子就能对时钟进行完整的配置。 时钟树如图: 系统时钟的选择是在启动时进行,复位时内部8MHz的RC振荡器被选为默认的CPU时钟,随后可以 选择外部的、具失效监控的4~16MHz时钟;当检测到外部时钟失效时,它将被隔离,系统自动地切 换到内部的RC振荡器,如果使能了中断,软件可以接收到相应的中断。同样,在需要时可以采取对 PLL时钟完全的中断管理(如当一个间接使用的外部振荡器失效时)。 多个预分频器用于配置AHB的频率、高速APB(APB2)和低速APB(APB1)区域。AHB和APB的最高频 率是36MHz。 编程要领: 1)
[单片机]
STM32-嵌入式学习笔记1-使用HSE和HSI配置时钟
STM32高级开发(16)-CMSIS DAP调试工程
最近公司的项目在等供应商的设备有点空闲的时间了,就折腾了下ARM官方开源的CMSIS DAP调试器的方案,用的是X893大神的方案,下面附上他的个人主页和在GitHub上的项目链接(我是用的是其中stlinkv2.1的软硬件方案): (http://akb77.com/g/stm32/cmsis-dap-adapter/) (https://github.com/x893/CMSIS-DAP) 这个调试器方案可以说极具性价比,SWD接口速度可以达到10M的全速,还附带一个最高支持到115200bps的串口,而且连接一根线就可以识别为两个设备,既可以单独的作为一个调试器使用,也可以集成到其他项目的PCB板上作为板载调试器和
[单片机]
<font color='red'>STM32</font>高级开发(16)-CMSIS DAP调试工程
STM32 IAR工程->Keil MDK转换详解
我在STM32的学习中发现,大部分的STM32示例程序都是基于IAR开发环境的,但我认为使用Keil MDK开发环境更加方便,可以利用RVMDK强大的外设仿真功能加速STM32的开发。我在以前的Blog文章里介绍过如何在RVMDK中建立STM32 工程,以及如何使用RVMDK的软件仿真功能,下面我将详细说明怎样将已有的IAR工程移植到RVMDK。 不管是IAR还是RVMDK,编程时使用的都是STM32的固件函数库,唯一不同的是启动文件。RVMDK在建立STM32工程时会自动创建启动文件 STM32F10x.s,而IAR使用的启动文件是cortexm3_macro.s。此外,两者对中断向量表的管理也不一样。
[单片机]
<font color='red'>STM32</font> IAR工程->Keil MDK转换详解
一种STM32微控制器处理电机控制的设计和实现
变频器是利用电力半导体器件的通断作用将工频电源变换为另一频率的电能控制装置,能实现对交流异步电机的软起动、变频调速、提高运转精度、改变功率因数、过流/过压/过载保护等功能。变频器集成了高压大功率晶体管技术和电子控制技术,得到广泛应用。变频器的作用是改变交流电机供电的频率和幅值,因而改变其运动磁场的周期,达到平滑控制电动机转速的目的。变频器的出现,使得复杂的调速控制简单化,用变频器+交流鼠笼式感应电动机组合替代了大部分原先只能用直流电机完成的工作,缩小了体积,降低了维修率,使传动技术发展到新阶段。本文将探讨基于ARM的标准微控制器如何在一个被DSP和FPGA长期垄断的市场上打破复杂的控制模式,我们将以意法半导体的基于Cortex-M
[单片机]
一种<font color='red'>STM32</font>微控制器处理电机控制的设计和实现
小广播
设计资源 培训 开发板 精华推荐

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

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

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved