STM32 串口DMA,非中断发送,接收。

  DMA 能完成外设到内存的 或 内存的外设的数据搬运,不用CPU参与。
  下面以串口发送为例,说明操作步骤:

1、初始化通道的CCR寄存器

DMA有x个通道,每个通道都有一组寄存器
在这里插入图片描述
1、数据传输方向
2、循环模式------是否循环操作
3、外设地址增量
4、内存地址增量
5、外设数据宽度 8位,16位还是32位
6、内存地址宽度
7、通道优先级
8、是否是 存储器到存储器------复位后为0,表示外设与存储器通信,所以HAL这位不设置

初始化就是配置上面这8条。

用STM32CubeMX生成的DMA初始化代码 HAL_DMA_Init(DMA_HandleTypeDef *hdma) 如下:

  /* Prepare the DMA Channel configuration */
  tmp |=  hdma->Init.Direction        |              
          hdma->Init.PeriphInc           | hdma->Init.MemInc           |
          hdma->Init.PeriphDataAlignment | hdma->Init.MemDataAlignment |
          hdma->Init.Mode                | hdma->Init.Priority;

  /* Write to DMA Channel CR register */
  hdma->Instance->CCR = tmp;

  旧版的FW_V3.3.0库还多初始化了3个参数
1、数据长度
2、外设地址
3、内存地址

  tmpreg |= DMA_InitStruct->DMA_DIR | DMA_InitStruct->DMA_Mode |
            DMA_InitStruct->DMA_PeripheralInc | DMA_InitStruct->DMA_MemoryInc |
            DMA_InitStruct->DMA_PeripheralDataSize | DMA_InitStruct->DMA_MemoryDataSize |
            DMA_InitStruct->DMA_Priority | DMA_InitStruct->DMA_M2M;

  /* Write to DMAy Channelx CCR */
  DMAy_Channelx->CCR = tmpreg;

/*--------------------------- DMAy Channelx CNDTR Configuration ---------------*/
  /* Write to DMAy Channelx CNDTR */
  DMAy_Channelx->CNDTR = DMA_InitStruct->DMA_BufferSize;

/*--------------------------- DMAy Channelx CPAR Configuration ----------------*/
  /* Write to DMAy Channelx CPAR */
  DMAy_Channelx->CPAR = DMA_InitStruct->DMA_PeripheralBaseAddr;

/*--------------------------- DMAy Channelx CMAR Configuration ----------------*/
  /* Write to DMAy Channelx CMAR */
  DMAy_Channelx->CMAR = DMA_InitStruct->DMA_MemoryBaseAddr;

2、我们STM32CubeMX生成的DMA初始化代码以后,还不能用DMA,因为数据从哪里搬到哪里,搬多少个字节还没有初始化。HAL库是在我们调用类似HAL_UART_Transmit_DMA()这样的函数的时候才初始化这几个参数的。

  如果想实现旧版一样的功能,可以直接在上面的初始化代码中加入:
hdma_usart1_tx.Instance->CNDTR=0;
hdma_usart1_tx.Instance->CPAR = USART1_DR_Base;
hdma_usart1_tx.Instance->CMAR = (uint32_t)Com_Data.SendBuff;
SET_BIT(huart->Instance->CR3, USART_CR3_DMAT);//使能DMA发送,CR3是串口1中的寄存器,不是DMA中的寄存器

  在HAL_UART_Transmit_DMA()函数中,调用HAL_DMA_Start_IT()---->DMA_SetConfig()来设置源地址,目标地址,传输字节数。

  以上配置完以后,最后需要EN通道:
旧版EN的方法:DMA_Cmd (DMA1_Channel4,ENABLE);//即把CCR的最后一位置1
hal库EN的方法:在HAL_DMA_Start_IT()—>__HAL_DMA_ENABLE(hdma);

3、将串口与DMA这两个变量关联起来

在初始化HAL_UART_MspInit()----->HAL_DMA_Init()以后,有一条语句:
__HAL_LINKDMA(huart,hdmarx,hdma_usart1_rx);
//相当于
huart->hdmarx=&(hdma_usart1_rx);
hdma_usart1_rx.Parent=huart;
  这条只是把串口变量huart1 与 DMA变量hdma_usart1_rx相互连接起来,像链表一样。后面在串口模块中使用huart->hdmarx.Init.Direction就可以访问DMA变量中的方向参数;在DMA模块中使用huart=hdma_usart1_rx.Parent;huart->TxXferCount=0;就可以设置串口发送的字节数。

4、通过仿真可以查看地址

通过查数据手册知道DMA的基地址是0x4002 0000
在这里插入图片描述
通道5即串口接收通道,偏移地址是0x08+20*(通道号-1)=88=0x58
在这里插入图片描述

5、串口DMA HAL库的使用

  在Init_Uart(void)初始化函数中调用下来两个函数
A、MX_DMA_Init();-------只设置了DMA中断优先级

B、MX_USART1_UART_Init()
------1)、设置串口和DMA相关寄存器:HAL_UART_Init(&huart1)
------------a,设置底层:HAL_UART_MspInit(huart);
------------------(1)串口引脚初始化
------------------(2)DMA通道初始化,即初始化传输的8个参数:HAL_DMA_Init(&hdma_usart1_rx)
------------------(3)把串口变量和DMA变量连接起来:__HAL_LINKDMA(huart,hdmarx,hdma_usart1_rx)
------------------(4)设置串口中断优先级
------------b,设置串口波特率,停止位等:UART_SetConfig(huart);
------------c,设置串口CR2 CR3寄存器
------------d,EN串口:__HAL_UART_ENABLE(huart);
------2)、使能串口空闲中断:__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
------3)、开始DMA接收:HAL_UART_Receive_DMA(&huart1, (uint8_t*)rx_buffer, RX_LEN);
-------------------这个函数里会设置中断回调函数

下面详细分析其中重要的步骤:

A、设置DMA通道中断的优先级

static void MX_DMA_Init(void) 
{
  __HAL_RCC_DMA1_CLK_ENABLE();
  HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 6, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
  HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 6, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
}

(2)DMA通道初始化

  设置DMA通道的函数在文件stm32f1xx_hal_msp.c中,而DMA通道变量是定义在用户的c文件中,所以要在stm32f1xx_hal_msp.c的最前面把DMA变量声明为外部变量

extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;

(3)为了用串口变量访问DMA变量,或用DMA变量访问串口变量,把它们连接起来

在这里插入图片描述
在DMA变量中,定义了一个 *Parent;指针
在这里插入图片描述
以下语句把它两个变量相互连接起来:

__HAL_LINKDMA(huart,hdmarx,hdma_usart1_rx);
    //相当于
    huart->hdmarx=&(hdma_usart1_rx);
    hdma_usart1_rx.Parent=huart;

2)、串口空闲中断

  串口接收数据后,当一个空闲帧被检测到时(一个byte的高电平(空闲)状态),如果IDLEIE位被设置将产生一个中断

3)、DMA串口接收

  首先,定义一个数组用来接收串口数据,数组的长度要设置大于些

#define RX_LEN  30
static  char  rx_buffer[RX_LEN];

  其次,调用HAL_UART_Receive_DMA(&huart1, (uint8_t*)rx_buffer, RX_LEN);来接收数据

  再次,当DMA接收到一些字节时,突然有一个空闲字节,那就会产生空闲中断,在串口中断函数HAL_UART_IRQHandler(UART_HandleTypeDef *huart)中增加空闲中断代码

	//如果是空闲中断
  tmp_flag = __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE);
  tmp_it_source= __HAL_UART_GET_IT_SOURCE(huart, UART_IT_IDLE);
  if((tmp_flag != RESET) && (tmp_it_source != RESET))
  {
		__HAL_UART_CLEAR_IDLEFLAG(huart);//清除标志
		tmp_flag=huart->Instance->SR;//读SR可以实现清除状态寄存器
		tmp_flag=huart->Instance->DR;
		HAL_UART_DMAStop(huart);
		tmp_flag=huart->hdmarx->Instance->CNDTR;//获取未传输数据个数
		__HAL_DMA_DISABLE(huart->hdmarx);//关闭DMA接收	
		HAL_UART_RxIdleCallback(tmp_flag);//把未接收字节数传过去
		__HAL_DMA_ENABLE(huart->hdmarx);//开启DMA接收,在传未接收字节数时,先关DMA,防止又接收到数据,改变了未接收字节数    
   } 
}

在空闲中断中,获取剩余未接收的字节数tmp_flag,开始我们设置的是接收RX_LEN个字节,两个相减就得到已接收的字节数。处理接收到的字节后,重新启动DMA接收。

//串口接收完回调函数
void HAL_UART_RxIdleCallback(uint32_t rx_count)
{
	uart_pak data;
	uint32_t rx_cou;
	rx_cou=RX_LEN-rx_count;//算出已接收字节数
	
	strcpy(data.str,rx_buffer);
	data.strLen = rx_cou;
	printFromISR(&data);//输出
	
	HAL_UART_Receive_DMA(&huart1, (uint8_t *)rx_buffer, RX_LEN);
}

串口中断函数是在stm32f1xx_hal_uart.c中,它的空闲中断中会调用HAL_UART_RxIdleCallback(uint32_t tmp_flag)回调函数 来处理接收到的数据,回调函数是用户在其它文件中定义的,所以在文件的开始要把它个函数声明为外部函数。
在这里插入图片描述
在使用函数的c文件开始声明外部函数,有一个好处,就是这个外部函数可以写在任何其它c文件里。而在头文件中声明,必须包含相应的头文件。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值