之后要学:SPI / IIC+DMA
学习的这位up主的视频:全网最清楚的DMA讲解,三种搬运模式三个例子讲清楚(STM32教程基于HAL库和CUBEIDE)_哔哩哔哩_bilibili
目录
01-基本信息
1-概述
DMA能将外设/内存与外设/内存间建立直接的通道进行数据传输,而无需CPU进行数据的传输,可以将资源用在更合适的地方
2-方向
外设-》内存、内存-》外设,内存-》内存
3-模式
正常模式
只执行一次DMA数据传输,在接收到制定的数据大小之后,结束DMA
轮询模式
可以一直进行数据的传输,不过要注意防止数据的覆盖等,需要特殊的算法进行数据的结算(以后学)
4-地址自增
有这四种自增方式
02-实例一:串口读取陀螺仪
认为适用于接收连续的数据包,协议
1-cubemx配置
配置为正常模式,外设地址不变,内存地址自增
正常模式需要每次调用完成,重新开启DMA,但算法相对简单
当前外设为串口接收寄存器,只有一个地址无需自增
内存需要存放多个数据,所以需要自增,不能覆盖
数据宽度也很重要,但是了解不深,以后再说
串口中断也要打开
在后续处理中,需要用串口中断来进行DMA接收数据完成的判断
2-代码编写
1-初始化
__HAL_UART_ENABLE_IT(&hlpuart1, UART_IT_IDLE); //使能IDLE
//开启DMA接收,
HAL_UART_Receive_DMA(&hlpuart1,uart1_rx_buf, UART1_PACK_SIZE);
DMA接收,只需要设定好初始位置,定义好数组大小、接收数据数目比接收的一帧数据大就行,后续会进行处理
使能IDLE,即空闲中断,之后在中断处理函数中进行处理
2-中断处理函数
在stm32g4xx_it.c中
void LPUART1_IRQHandler(void)
{
/* USER CODE BEGIN LPUART1_IRQn 0 */
extern uint8_t uart1_rx_buf[UART1_PACK_SIZE];//16Byte
extern uint16_t uart1_rx_size;
extern uint8_t uart1_rx_cplt_flag;
uint32_t tmp_flag = 0;
uint32_t temp;
tmp_flag =__HAL_UART_GET_FLAG(&hlpuart1,UART_FLAG_IDLE); //获取IDLE标志位
if((tmp_flag != RESET))//idle标志被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&hlpuart1);//清除标志位
HAL_UART_DMAStop(&hlpuart1);
temp = __HAL_DMA_GET_COUNTER(&hdma_lpuart1_rx);// 获取DMA中未传输的数据个数
uart1_rx_size = sizeof(uart1_rx_buf) - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
uart1_rx_cplt_flag = 1; // 接受完成标志位置1
}
/* USER CODE END LPUART1_IRQn 0 */
HAL_UART_IRQHandler(&hlpuart1);
/* USER CODE BEGIN LPUART1_IRQn 1 */
/* USER CODE END LPUART1_IRQn 1 */
}
使能IDLE中断后,在串口空闲下便会进入此中断(其他中断会进吗)然后判断串口当前是否空闲,若空闲,代表一帧数据接收完毕,然后结束DMA(之前接收函数比具体数据数量大也没关系就是这个原因,之后会重新开启),使能标志位,之后进行到串口数据处理函数中,便可以进行处理。
ps:一帧数据代表一次发送所有信息,如55 51 aa aa aa 55 52 aa aa aa,此时接受了两个帧头,但因为是一遍发过来的,所以为一帧。
ps:空闲中断只有在接收完成数据后才会打开,发送不会。
ps:所以单片机,openmv的printf,print发送的是什么呢,是一帧还是一个个发送的呢,或许之后可以用打包函数,直接打包数据发送出去就是一帧
3-串口数据处理函数
uint8_t uart1_receiveData_parse(void)
{
uint8_t ret = 1;
//如果接收到数据包
if(uart1_rx_cplt_flag)
{
//数据处理
for(uint8_t i=0;i<99;i++)
{
jy901_read_data(uart1_rx_buf[i]);
}
//清空数据包与标志位
memset(uart1_rx_buf, 0, UART1_PACK_SIZE);
uart1_rx_cplt_flag = 0;
uart1_rx_size = 0;
//重新打开DMA接收
HAL_UART_Receive_DMA(&hlpuart1,uart1_rx_buf, UART1_PACK_SIZE);
}
return ret;
}
注意,这并不是什么接收完成中断之类的,而是一个简单的函数的,需要外界的调用,我将其放在定时器中断函数中,每1ms调用一次函数,则1ms读取一次数据。
03-实例二:另一种DMA收发数据的方案
发送数据:关闭DMA在buffer与串口寄存器的通道,然后将数据写入缓冲区(buffer),全部写入写入后开启通道,则会将所有的数据发出去
接收数据:
若是发送指令后立即读取其反馈,则可以默认关闭DMA通道,然后发送完成指令后立即打开。在空闲中断服务函数中判断,若有,则将缓冲区数组的数据取出并进行计算