STM32
直播中

刘伟

8年用户 1720经验值
私信 关注
[问答]

如何使用STM32F4 DMA收发数据呢

为什么要使用DMA方式收发数据呢?怎么使用DMA呢?



回帖(1)

韩俊

2021-11-18 15:37:13
  STM32F4 USART1 使用DMA发送、接收数据
  一、为什么使用DMA方式收发数据
  DMA,全称Direct Memory Access ,即直接存储器访问。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送的同类,能使CPU的效率大大提高。直接存储器访问(DMA)用于在外设与存储器之间以及存储器与存储器与存储器之间提供数据传输。它无需CPU参与而自动移动数据,即DMA就是一个数据搬运工。
  二、怎么使用DMA
  既然DMA是一个数据搬运工,那么根据数据传输三要素,必须知道数据的源、目的和长度。在STM32F4中还需要设置数据传输方向等其它项,具体参考代码。
  三、STM32F4 DMA简介
  STM32F4有2个DMA,每个DMA控制器有8个数据流,每个数据流有多达8个通道,但是仅DMA2数据流能够执行存储器到存储器之间的数据传输。其中DMA2的请求映射如下:
  
  
  从图中可以看出串口1的接收和发送分别位于DMA2数据流2(或数据流5)的通道4和数据流7的通道4。
  四、代码解析
  本程序的功能为把接收到的数据发送出去。
  1、main函数
  int main(void)
  {
  /*******初始化*******/
  USART1_Initializes();
  DMA_Initializes();
  NVIC_Initializes();
  while(1)
  {
  if(USART1_RX_LEN!=0)
  {
  DMA_USART1_Send(DMA_USART1_RX_BUF,USART1_RX_LEN); //发送接收到的数据
  USART1_RX_LEN=0; //接收状态清零
  }
  }
  }
  2、串口初始化
  要使用串口收发数据,首先得初始化串口。本例中串口1管脚选用PA9、PA10,因为用到了RS485通讯,还有一个管脚PA6作为控制收发,这些都需要进行初始化配置。串口1的波特率配置为1200000,可以根据需要进行选择,波特率低的话传输会比较稳定,数据不容易出错。
  void USART1_Initializes(void)
  {
  USART1_GPIO_Configuration();
  USART1_MODE_Configuration();
  }
  void USART1_GPIO_Configuration(void)
  {
  GPIO_InitTypeDef GPIO_InitStructure;
  /* RCC时钟配置 */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能GPIO时钟
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //使能USART1时钟
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); //GPIOA9复用为USART1
  GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); //GPIOA10复用为USART1
  /* 定义USART-TX 引脚为复用输出 */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //IO口的第9pin发送、第10pin接收
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //IO口速度
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //IO口复用
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
  GPIO_Init(GPIOA, &GPIO_InitStructure); //USART1输出IO口
  //PA6推挽输出,485模式控制
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //IO口的第6脚
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //IO口速度
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //IO口输出
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  }
  void USART1_MODE_Configuration(void)
  {
  USART_InitTypeDef USART_InitStructure;
  /******************************************************************
  USART参数初始化: 波特率 传输位数 停止位数 校验位数
  1200000 8 1 0(NO)
  *******************************************************************/
  USART_InitStructure.USART_BaudRate = USART1_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_Mode = USART_Mode_Rx | USART_Mode_Tx; //使用接收和发送功能
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//不用流量控制
  USART_Init(USART1, &USART_InitStructure); //初始化USART1
  USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //使能USART1空闲中断
  USART_Cmd(USART1, ENABLE); //使能USART1
  USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //使能USART1DMA接收
  USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能USART1DMA发送
  USART_ClearFlag(USART1, USART_FLAG_TC);
  USART1_RX; //RS485默认接收状态
  }
  除了串口初始化配置,还需要配置用到的DMA数据流通道,即DMA2的Stream5与Stream7。
  void DMA_Initializes(void)
  {
  DMA_InitTypeDef DMA_InitStructure;
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能
  DMA_DeInit(DMA2_Stream5);
  while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE);//等待DMA可配置
  /* 配置 DMA2 Stream5,USART1接收 */
  DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择
  DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1-》DR; //DMA外设地址
  DMA_InitStructure.DMA_Memory0BaseAddr = (u32)DMA_USART1_RX_BUF; //DMA 存储器0地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; //外设到存储器模式
  DMA_InitStructure.DMA_BufferSize = DMA_USART1_RX_BUF_LEN; //数据传输量
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设非增量模式
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器增量模式
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据长度:8位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据长度:8位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //使用普通模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //中等优先级
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器突发单次传输
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外设突发单次传输
  DMA_Init(DMA2_Stream5, &DMA_InitStructure); //初始化DMA Stream
  DMA_Cmd(DMA2_Stream5, ENABLE);
  DMA_DeInit(DMA2_Stream7);
  while (DMA_GetCmdStatus(DMA2_Stream7) != DISABLE);//等待DMA可配置
  /* 配置DMA2 Stream7,USART1发送 */
  DMA_InitStructure.DMA_Channel = DMA_Channel_4; //通道选择
  DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1-》DR; //DMA外设地址
  DMA_InitStructure.DMA_Memory0BaseAddr = (u32)DMA_USART1_TX_BUF; //DMA 存储器0地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //存储器到外设模式
  DMA_InitStructure.DMA_BufferSize = DMA_USART1_TX_BUF_LEN; //数据传输量
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设非增量模式
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器增量模式
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据长度:8位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据长度:8位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //使用普通模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //中等优先级
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器突发单次传输
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外设突发单次传输
  DMA_Init(DMA2_Stream7, &DMA_InitStructure); //初始化DMA Stream7
  DMA_ITConfig(DMA2_Stream7, DMA_IT_TC, ENABLE); //DMA2传输完成中断
  DMA_Cmd(DMA2_Stream7, DISABLE); //不使能
  }
  接着是NVIC配置,因为需要用到USART1的IDLE空闲中断和DMA2_Stream7的中断。
  void NVIC_Configuration(void)
  {
  NVIC_InitTypeDef NVIC_InitStructure;
  /*USART1中断 */
  #if USART1_INT_ENABLE
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //IRQ通道
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //主优先级
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //从优先级
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能通道
  NVIC_Init(&NVIC_InitStructure);
  #endif
  #if DMA2_Stream7_IQ_ENABLE
  NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn ;//串口1发送中断通道
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
  NVIC_Init(&NVIC_InitStructure);
  #endif
  }
  最后是中断服务函数和DMA发送函数的编写。这里使用USART1的空闲中断来处理接收不定长数据。需要注意的点有:
  1.使用DMA发送时每次发送数据前需要配置发送的数据长度,此时要注意应先关闭DMA,然后配置数据长度,最后开启DMA发送,同时在DMA发送中断里面不要忘记清除相应的中断标志位。
  2.DMA接收长度达到配置长度后会导致接收中断,此时在中断处理函数内要先关闭DMA,然后读出数据长度,清掉相应的中断标志位,最后重新配置DMA接收长度并打开DMA接收。在这里的DMA中断指示为了防护一次性接收数据超过DMA配置长度。
  3.USART1空闲中断,利用空闲中断可以很好地判断DMA接收不定长度的数据是否完成。初始化USART时就已经打开空闲中断。当数据接收完成后会触发USART1空闲中断。在中断内应首先关闭DMA,读出DMA接收到的数据长度,清除DMA标志,重新配置DMA接收长度,清除空闲中断标志IDLE。这里要注意清除IDLE要由软件序列清除即依次读取USART1-》SR;和USART1-》DR;
  #if USART1_INT_ENABLE
  /*****************************************************
  *function: 串口1中断服务函数
  *param: none
  *return: none
  ******************************************************/
  void USART1_IRQHandler(void)
  {
  if(USART_GetITStatus(USART1,USART_IT_IDLE) != RESET)
  {
  u8 clear=clear;
  DMA_Cmd(DMA2_Stream5, DISABLE); //关闭DMA,防止处理其间有数据
  clear = USART1-》SR;
  clear = USART1-》DR; //清除IDLE标志
  USART1_RX_LEN = DMA_USART1_RX_BUF_LEN - DMA_GetCurrDataCounter(DMA2_Stream5);
  DMA_ClearFlag(DMA2_Stream5,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5
  | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5); //清除DMA2_Steam5传输完成标志
  DMA_SetCurrDataCounter(DMA2_Stream5, DMA_USART1_RX_BUF_LEN);
  DMA_Cmd(DMA2_Stream5, ENABLE);
  }
  if(USART_GetFlagStatus(USART1,USART_IT_TXE)==RESET) //串口发送完成
  {
  USART_ITConfig(USART1,USART_IT_TC,DISABLE);
  USART1_RX; //重新进入接收状态
  }
  }
  #endif
  #if DMA2_Stream7_IQ_ENABLE
  /*****************************************************
  *function: DMA发送完成中断
  *param: none
  *return: none
  ******************************************************/
  void DMA2_Stream7_IRQHandler(void)
  {
  //清除标志
  if(DMA_GetFlagStatus(DMA2_Stream7,DMA_FLAG_TCIF7)!=RESET)//等待DMA2_Steam7传输完成
  {
  DMA_ClearFlag(DMA2_Stream7,DMA_FLAG_TCIF7); //清除DMA2_Steam7传输完成标志
  DMA_Cmd(DMA2_Stream7,DISABLE);
  USART_ITConfig(USART1,USART_IT_TC,ENABLE); //打开串口发送完成中断
  }
  }
  #endif
  最后附上DMA发送的代码。
  void DMA_USART1_Send(u8 *data,u16 size)
  {
  memcpy(DMA_USART1_TX_BUF,data,size); //复制数据到DMA发送缓存区
  while (DMA_GetCmdStatus(DMA2_Stream7) != DISABLE); //确保DMA可以被设置
  DMA_SetCurrDataCounter(DMA2_Stream7,size); //设置数据传输长度
  USART1_TX; //串口发送模式
  DMA_Cmd(DMA2_Stream7,ENABLE); //打开DMA数据流,开始发送
  //while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET); //等待发送完成
  //USART1_RX; //串口接收模式
  }
举报

更多回帖

发帖
×
20
完善资料,
赚取积分