本文将分文四个部分介绍,分别是关于DMA的简单认识,DMA从内存到内存的实验,DMA从内存到外设的实验,DMA从外设到内存的实验。
1.什么是DMA?
直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传 输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。
啥意思呢?如果CPU是古代的皇帝,那么DMA就是宰相。众所众知,皇帝有啥事都可以管,我们的CPU也一样。但是那么多事情,皇帝一个人忙得过来吗?忙不过来的,这时候需要宰相协助皇帝处理事务。小问题宰相自己解决,不用“打扰”皇帝,大问题皇帝亲自发布意见处理。同样的道理,CUP要管理很多事,负责执行指令、控制系统操作、进行运算和处理、处理中断以及管理存储器等功能。但是做事情都要讲效率,重要的事情CPU来做,不重要的事情就交给DMA来做就好了。比如说简单的数据传输,CPU可以做,但让CPU来做就有点“大材小用了”,所以作为宰相的DMA就开始愉快的上班了。
2.DMA的通道
这里使用的是stm32103c8t6,只有一个DMA可以用。但是这一个DMA也是很复杂的,如下图
这张图是手册里的,通过看图我们可以发现,DMA1有7个通道。每一个通道上都有特定的硬件触发和通用的软件触发。知道就可以,这个不用过于纠结,到时候可以使用stm32cube配置。
别看有7个通道,但是真正到使用的时候,也要一个一个来。比如说,优先级相同的情况下,通道一和通道五都触发了,但是按照默认的优先级,是通道一先开始传输数据,通道一传输完成关闭后,在开始起通道五。当然,优先级也是可以更改的。
3.DMA转运的规则
3.1两种传输的方式
DMA_Mode_Normal(正常模式):一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次。
DMA_Mode_Circular(循环传输模式):当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式。
这个在手册上也有说明,如果按照标椎库的话,需要自己去慢慢地配置,但是在stm32cube中,我们负责选择就好了。下面我们做试验会说的。
3.2指针是否自增
指针自增:每次发送或者接收完自己设置的长度字节后,指针向下加一个字节的长度。用于接收或者发送一个新的字节长度。这种情况下,接受方的数据不会被替代,都会留下来。
指针不自增:每次发送或者接收完自己设置的长度字节后,指针的位置不变。这种情况下,接受方的数据会被替代,要及时转移。因为接收方确实是接受了来自发送方的所有内容,但最后留下来的确实最后一次接收的数据。
1.简要说明
首先要说明的是,手册告诉我们,在这个模式下,无法使用(循环传输模式)
我们来看一下储存的东西
发现了吗,我程序是在ROM里面储存的,所以掉电不丢失;我们外设还有运行内存的设备都在RAM里,所以掉电会丢失。所以你想一下,存储器到存储器的模式如果用循环,那不是要一直接收数据,哪来那么多的空间来储存呢。所以发送一次就结束,下次重启单片机,内容没了,继续重新接收就是了。
内存中有两种,一种是Flash,一种是SARM。
Flash的内容一般是只读的,想要改,需要比较麻烦一点的配置,就不说了。一般来说,Flash是作为发送方使用的,SARM是作为接收方使用的;当然你也可以把SARM既作为接收方,由作为发送方。我们这里的测试就是让SARM即作为接收方又作为发送方。
在单片机里,无论是全局变量,静态变量还是局部变量通常存储在静态随机存取存储器(Static Random Access Memory,SRAM)中,只不过是在不同的位置罢了。就算是两个想同类型的变量,也在同一大区域有不同的地址。所以当你设置了变量,不管是什么变量,SARM都已经给他开辟好了地址,当你是用HAL库函数的时候,程序后自己找到。
2.代码部分
目标使用DMA的方式将在内存中srcBuf的内容复制到desBuf中,搬运完之后将数组desBuf的内容打印到屏幕。
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
#include "stdio.h"
#define BUF_SIZE 16
uint32_t srcBuf[BUF_SIZE] = {
0x00000000,0x11111111,0x22222222,0x33333333,
0x44444444,0x55555555,0x66666666,0x77777777,
0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB,
0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF
};//源地址函数是32位,对应HAL里的开始函数
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
uint32_t desBuf[BUF_SIZE];目标地址函数是32位,对应HAL里的开始函数
int fputc(int ch, FILE *f)//printf重映射
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
// 开启数据传输
HAL_DMA_Start(&hdma_memtomem_dma1_channel1,(uint32_t)srcBuf, (uint32_t)desBuf, sizeof(uint32_t) * BUF_SIZE);
// 等待数据传输完成,DMA_FLAG_TCx是传输完成标志,如完成传输,该为置1
while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel1, DMA_FLAG_TC1) == RESET);
// 串口助手里面打印数组内容
for (int i = 0; i < BUF_SIZE; i++)
printf("Buf[%d] = %X\r\n", i, desBuf[i]);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
3.串口显示
4.stm32cube配置
SYS
RCC
DMA
1.简要说明
我们这里的外设收的是单片机的片上外设,如UART,IIC,SPI,ADC等,这里我们用的是串口1,我们要使用DMA的方式将内存数据搬运到串口1发送寄存器,同时闪烁LED。
2.代码部分
#include "main.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
#define BUF_SIZE 1000
unsigned char sendBuf[BUF_SIZE];
void SystemClock_Config(void);
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
for (int i = 0; i < BUF_SIZE; i++)
sendBuf[i] = 'A';
// 将数据通过串口DMA发送
HAL_UART_Transmit_DMA(&huart1, sendBuf, BUF_SIZE);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
HAL_Delay(100);
}
/* USER CODE END 3 */
}
数组中的大小类型是依据HAL库中HAL_UART_Transmit_DMA函数的要求写的。
3.串口显示
在DMA的配置中选择了循环模式,所以无限发A。
4.stm32cube配置
SYS与RCC同上
GPIO
DMA
1.简要说明
这个说实话,比前两个复杂。不像前两个,配置了一大堆,然后写个几个简单的函数就完事了。这里我们继续用串口1,下面说一下执行的思路以及所用的函数:
① 使能IDLE空闲中断;
我们首先要用串口助手来给单片机串口外设的接收,接着就可以读出来。RXNE置一,如果IDLE空闲,则表明DMA接收来自串口的数据已经完成,同时进入了中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
② 使能DMA接收;
HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);这个函数写入之后,就开起了串口向DMA缓冲区输送数据的通道,串口不断的向DMA的缓冲区传输数据缓冲区,然后发送给内存里的rcvBuf储存。
③一帧数据接收完毕,串口暂时空闲,触发串口空闲中断;
利用__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)// 判断IDLE标志位是否被置位
④在中断服务函数中,清除中断标志位,关闭DMA传输(防止干扰);
__HAL_UART_CLEAR_IDLEFLAG(&huart1);// 清除IDLE标志位
HAL_UART_DMAStop(&huart1);// 停止DMA传输,防止干扰
这里要说的是,这个函数很有意思,它是有选择的关闭,它可以关闭串口向DMA的传输,也可以关闭DMA向内存的传输,这取决于串口的状态,下面的两个函数是我进入停止函数后发现的
也就是说,想要终止串口向DMA传输,只要满足Rx传输在错误检测或接收完成后就可以,很明显,我们DMA接收完成,可以实现关闭串口向DMA传输。而DMA输出不满足,所以DMA向内存传输不会被关闭,还可以继续使用。
⑤在中断服务函数中,计算刚才收到了多少个字节的数据;
__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//计算剩余多少个字节未被发送
⑥在中断服务函数中,处理缓冲区数据,开启DMA传输,开始下一帧接收;
HAL_UART_Transmit_DMA(&huart1, rcvBuf, rcvLen);//通过DMA 将内存的数据发给串口助手验证
就是发送啥,输出啥
HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);//使能DMA接收,为下一次接受做准备
2.代码部分
main.c
uint8_t rcvBuf[BUF_SIZE]; // 接收数据缓存数组
uint8_t rcvLen = 0; // 接收一帧数据的长度
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使能IDLE空闲中断
HAL_UART_Receive_DMA(&huart1,rcvBuf,100); // 使能DMA接收中断
while (1)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
HAL_Delay(300);
}
main.h
#define BUF_SIZE 100
stm32f1xx_it.c
extern uint8_t rcvBuf[BUF_SIZE];
extern uint8_t rcvLen;
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
if((__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE) == SET)) // 判断IDLE标志位是否被置位
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);// 清除标志位
HAL_UART_DMAStop(&huart1); // 停止DMA传输,防止干扰
uint8_t temp=__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
rcvLen = BUF_SIZE - temp; //计算数据长度
HAL_UART_Transmit_DMA(&huart1, rcvBuf, rcvLen);//发送数据
HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);//开启DMA
}
/* USER CODE END USART1_IRQn 1 */
}
3.串口显示
4.stm32cube配置
SYS,RCC配置同上
GPIO
NVIC
DMA