STM32 DMA工作原理及编程示例(HAL库/标准库)

1、DMA简介

DMA,全称为:Direct Memory Access,即直接存储器访问。

DMA传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,减轻CPU负担,使 CPU 的效率大为提高。

DMA可用于存储器和存储器、外设和存储器、存储器和外设之间的传输。(也可用于外设与外设之间的传输,但感觉用的较少),这里的外设指的是外设的数据寄存器DR(Data Register),存储器指的是运行内存SRAM和闪存Flash。

闪存(Flash)、SRAM、外设的 SRAM、APB1、APB2 和 AHB 外设均可作为访问的源和目标。

DMA的通道

STM32F103有12个独立可配置的通道(数据转运的路径):DMA1(7个通道),DMA2(5个通道)每个通道都支持软件触发和特定的硬件触发。

软件触发

例如:存储器(Flash)--->存储器(SRAM)/存储器(SRAM)--->存储器(SRAM),软件触发以后,DMA就会以最快的速度,将指定个数的数据全部转运完成。指定个数:DMA传输计数器的数量(最大数量:65536)

特定的硬件触发

一般外设的数据转换完成是有一定时机的。例如:外设(ADC的DR)-->存储器(SRAM),用DMA来转运ADC的数据,ADC每个通道AD转换完成后,就硬件触发一次DMA,DMA再转运数据,触发一次转运一次,这样转运的数据才是正确的。

特定:每个DMA通道的硬件触发源不同。数据从一个地方转运到另一个地方,需要一个通道,各个通道相互独立、互不干扰。

2、存储器映像(STM32F103)

本图片源自B战江科大STM入门教程PPT

ROM和RAM

ROM:只读存储器,存储的数据掉电不丢失。         

RAM:随机存储器(可读写寄存器),数据可以被动态地读取、写入和修改。

ROM用于存储固化的程序和数据,且数据不易变更,RAM用于存储运行中的程序和临时数据,两种存储器相互补充以实现有效的数据处理和存储。

Flash:存储C语言编译后的程序代码和常量数据。

系统存储器和选项字节,实际上他们的存储介质也是Flash,但为了区别主闪存(程序存储器Flash),这两块的位置位于ROM区的最后。

系统存储器:一般由厂家写入,不允许修改。       选项字节:存的主要是Flash的写保护、读保护、看门狗等配置。

外设寄存器和内核外设寄存器,实际上他们的存储介质也是SRAM,但为了区别运行内存SRAM,这两块的位置同样位于RAM区的最后

内核外设:如NVIC和Systick

Flash与SRAM使用注意点

1、在程序中定义一个变量:int aa=0x66; 变量aa就存储在SRAM里面  变量地址以0x20开头。

在程序中定义一个const变量(常量):  const int aa=0x66;  常量aa就存储在Flash里面   变量地址以0x08开头。

当我们的程序中出现一大批不需要修改的数据(查找表、字库数据等)时,就可以定义成常量,从而节省SRAM的空间。

2、想查找某个寄存器地址:外设地址+寄存器偏移地址   如:ADC1->DR(ADC1是结构体指针,访问结构体成员时会用到->)

0地址的别名区

这里需要把我们要执行的程序映射到0地址来,如果映射在Flash区,就从Flash执行,如果映射在系统存储器区,就从系统存储器执行,如果映射到SRAM就从SRAM启动,怎么选择,由BOOT0和BOOT1两个引脚决定。

存储器完整映像(STM32F103)

程序存储器、数据存储器、寄存器和输入输出端口被组织在同一个4GB的线性地址空间内。数据字节以小端格式存放在存储器中。一个字里的最低地址字节被认为是该字的最低有效字节,而最高地址字节是最高有效字节。

对于各种寄存器的理解

寄存器是一种特殊的存储器。一方面,CPU可以对寄存器进行读写,就像读写运行内存SRAM一样。另一方面,寄存器的每个位之后都连接了一根导线,这些导线可以控制外设电路的状态,比如引脚的高低电平、导通和断开开关、切换数据选择器或者多位结合起来当做计数器、数据寄存器。寄存器是连接软件和硬件的桥梁,软件读写寄存器就相当于控制硬件的执行。

3、DMA框图

主动单元与被动单元

主动单元拥有存储器的访问权,主动单元有内核(CPU)和DMA。

被动单元他们的存储器只能被主动单元读写,被动单元是各种存储器。

仲裁器:虽然多个通道可以独立转运数据,但最终DMA总线只有一条,所有的通道只能分时复用这一条DMA总线。在总线矩阵,也有一个仲裁器,如果DMA与CPU要访问同一个目标,那么DMA就会暂停CPU的访问,以防止冲突,不过总线仲裁器,仍然保证CPU得到一半的总线带宽,使CPU正常工作。

AHB从设备:DMA自身的寄存器,用于配置DMA参数,连接在了总线矩阵右边的AHB总线上。所以DMA既是总线矩阵的主动单元,可以读写各种存储器,也是AHB总线上的被动单元,CPU可以对DMA进行配置。

DMA请求:用于硬件触发DMA的数据转运。

有无DMA参与数据转运的传输路径比较

没有DMA参与的数据传输(使用CPU)

CPU传输数据以内核作为中转站,用ADC采集数据转移到运行内存SRAM举例说明。

1.内核通过系统总线经总线协调,获取ADC采集的数据。

2.内核再通过系统系统经总线协调,把数据存放SRAM中。

使用DMA对数据进行传输(不使用CPU,减轻CPU负担)

DMA的传输过程(ADC通过DMA转运数据到运行内存SRAM):

1.外设向DMA发出请求

2.DMA产生应答

3.外设释放请求

4.启动DMA传输

DMA传输具体路径(个人理解):

①DMA控制器从AHB外设获取ADC采集的数据,存储在DMA通道中

②使用AHB把外设ADC采集的数据经由DMA通道存放在SRAM中。

DMA传输过程总结

每次DMA传送由3个操作组成,

1.取数据。第一次从源地址(如ADC的DR)取数据,之后在哪取由DMA参数地址是否自增决定。

2.存数据。第一次在目的地址(如运行内存SRAM定义的数组)存数据,之后在哪存由DMA参数地址是否自增决定。

3.传输计数器减1。

相关注意点

Flash是ROM(只读存储器)的一种,如果通过总线直接访问的话,无论是CPU还是DMA,都是只读的,只能读取数据而不能写入。如果的DMA的目的地址填了Flash的地址,那么转运时就会出错。Flash也不是绝对不可写入,要配置接口控制器对Flash进行写入。

小结

DMA转运本质都是存储器与存储器之间的数据转运,从某个地址取内容,再放到另一个地址去。

CPU和DMA作为主动单元可访问各种存储器。

对于DMA来说:闪存Flash:只可以读,不可以写。 运行内存SRAM:任意读写。

外设寄存器:有的寄存器是可读/可写的,具体由不同的寄存器决定。

但大多数我们用的是数据寄存器(DR),数据寄存器是可以正常读写的。

4、DMA基本结构

本图片源自B战江科大STM入门教程PPT

两个站点:外设站点和存储器站点

两个站点只是那么规定的,其实外设站点也可以写存储器的地址,表示存储器-->存储器。存储器站点也可以写外设地址。

STM32手册中的存储器是特指Flash和SRAM,不包括外设寄存器。外设寄存器一般称为外设。

方向:外设-->存储器    存储器-->外设   存储器-->存储器(Flash-->SRAM、SRAM-->SRAM两种,因为Flash是只读的)

实际传输方向(从某个地址传到另一个地址)由两个站点地址和方向参数决定。

数据宽度:指定一次转运的数据宽度   8位:字节   16位:半字   32位:字(字的概念看后面的拓展)

地址是否自增:指定一次转运完成后,下次转运是不是要把地址移动到下一位置去,相当于指针p++。

例如:ADC连续转换模式,利用DMA转运数据,外设地址是ADC_DR寄存器地址,显然外设地址是不需要自增的,如果自增,那么下次转运就跑到其他地址上了。

存储器(如一个数组)这边,地址就需要自增,每转运一个数据后,就往后移一下地址,便于将新数据存储起来,否则就会将上次的数据覆盖掉。

传输计数器:DMA每转运一次,计数器就减1,减到零就不会进行数据转运了。

另外计数器减到0之后,之前自增的地址也会恢复到起始地址的位置。

故DMA传输完成一次(计数器减到零),再次开启,数据会从之前的源地址和目的地址开始传递。

再次开启的方法:1.重置传输计数器的值,重置前要失能DMA  2.打开DMA自动重装器,即软件上开启循环模式。

自动重装器:如果设置了自动重装(循环模式/正常模式),当计数器减为0后,就会自动重装计数器的起始值。

采用什么触发:由M2M(memory to memory)参数决定,

1:软件触发(不是调用函数,是以最快的速度,连续不断的触发DMA)。 

0:硬件触发时还在相应的硬件外设中调用XXX_DMACmd,开启触发信号的输出。

软件触发和循环模式不能同时用(否则DMA转运就停不下来)   

开关控制:DMA_cmd(DMA_ENABLE/DMA_DISABLE)  DMA使能后,DMA就准备就绪,可以进行转运了。

DMA转运的前提:

1.开启DMA。(标准库:DMA_ENABLE  HAL库:相关开启函数)

2.传输计数器大于0 。

3.触发源:必须有触发信号。(HAL库DMA参数似乎没有规定DMA由什么触发)

相关注意点

当传输计数器等于0且没有自动重装时,无论是否触发都不会进行转运了,此时就要DMA_DISABLE关闭DMA,再在计数器写入计数值(写传输计数器时必须先DMA_DISABLE),再DMA_ENABLE,开启DMA,DMA才能继续工作。

5、DMA的触发源

DMA1触发源:

DMA2触发源:

原则是当一个通道有多个硬件触发时,多个硬件都可以触发(或门),但一般只有一个硬件进行触发。

6、DMA数据宽度对齐

如果是小的数据转到大的数据,高位就补0

如果把大的数据转到小的数据,高就会被舍弃掉

7、DMA中断

8、DMA转运实例

1、利用DMA将数据从运行内存SRAM转运到另一个SRAM中。(数组数据传到另一个数组中去)

本图片源自B战江科大STM入门教程PPT

DMA相关配置

外设地址:位于SRAM的数组名DataA

外设地址是否自增:是

数据宽度:8位

存储器地址:位于SRAM的数组名DataB

存储器地址是否自增:是

数据宽度:8位

传输计数器个数:7次(与数组个数相符)

自动重装器:设置为0,转运一次即可

触发方式:软件触发

标准库具体程序

①初始化函数及DMA开始转运函数

#include "stm32f10x.h"          // Device header

uint16_t MyDMA_Size;			//定义全局变量,用于记住Init函数的Size,供Transfer函数使用

/**
  * 函    数:DMA初始化
  * 参    数:AddrA 原数组的首地址
  * 参    数:AddrB 目的数组的首地址
  * 参    数:Size 转运的数据大小(转运次数)
  * 返 回 值:无
  */
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
	MyDMA_Size = Size;					//将Size写入到全局变量,记住参数Size
	
	/*开启时钟*/
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);					//开启DMA的时钟
	
	/*DMA初始化*/
	DMA_InitTypeDef DMA_InitStructure;									//定义结构体变量
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;		  //外设基地址,给定形参AddrA
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	
                                                                  //外设数据宽度,选择字节
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//外设地址自增,选择使能

	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;			//存储器基地址,给定形参AddrB
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;	
                                                                 //存储器数据宽度,选择字节
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;		//存储器地址自增,选择使能
	
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;	 //数据传输方向,选择由外设到存储器
	DMA_InitStructure.DMA_BufferSize = Size;			 //转运的数据大小(转运次数)
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;		 //模式,选择正常模式
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;			 //存储器到存储器,选择使能
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//优先级,选择中等
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);//将结构体变量交给DMA_Init,配置DMA1的通道1
	
	/*DMA使能*///这里先不给使能,初始化后不会立刻工作,等后续调用Transfer后,再开始
	DMA_Cmd(DMA1_Channel1, DISABLE);
}

/**
  * 函    数:启动DMA数据转运
  * 参    数:无
  * 返 回 值:无
  */
void MyDMA_Transfer(void)
{
	DMA_Cmd(DMA1_Channel1, DISABLE);	//DMA失能,在写入传输计数器之前,需要DMA暂停工作
	DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);//写入传输计数器,指定将要转运的次数
	DMA_Cmd(DMA1_Channel1, ENABLE);		//DMA使能,开始工作
	
	while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);	//等待DMA工作完成
	DMA_ClearFlag(DMA1_FLAG_TC1);						//清除工作完成标志位
}

②主函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"

uint8_t DataA[] = {0x01, 0x02, 0x03, 0x04};				//定义测试数组DataA,为数据源
uint8_t DataB[] = {0, 0, 0, 0};							//定义测试数组DataB,为数据目的地

int main(void)
{
	//1、DMA初始化,把源数组和目的数组的地址传入
	MyDMA_Init((uint32_t)DataA, (uint32_t)DataB, 4); 
	
    //2、OLED显示转运之前数组DataB	
    OLED_ShowHexNum(2, 1, DataB[0], 2);		
    OLED_ShowHexNum(2, 4, DataB[1], 2);
	OLED_ShowHexNum(2, 7, DataB[2], 2);
	OLED_ShowHexNum(2, 10, DataB[3], 2);
		
    //3、使能DMA转运,从DataA转运到DataB
	MyDMA_Transfer();
    //上面的MyDMA_Transfer();可用下面注释的代码代替,区别在于下面使用CPU对数据进行转运	
    //uint8_t i = 0;
    //for(i = 0; i < 4; i++)
    //  {
    //      DataB[i]=DataA[];
    //  }
	
    //4、OLED显示转运之后数组DataB	
	OLED_ShowHexNum(2, 1, DataB[0], 2);		
	OLED_ShowHexNum(2, 4, DataB[1], 2);
	OLED_ShowHexNum(2, 7, DataB[2], 2);
	OLED_ShowHexNum(2, 10, DataB[3], 2);
		
}

HAL库具体程序

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"  
#include "./BSP/led/led.h"
#include "./BSP/beep/beep.h" 
#include "./BSP/key/key.h" 
#include "./BSP/WWDG/wwdg.h"
#include "string.h"//里面有memset(dest_buf, 0, 10);函数

DMA_HandleTypeDef g_dma_handler;
//内存到内存 DMA传输   所有通道都支持
uint8_t src_buf[10]={0x0a, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
uint8_t dest_buf[10];

void dma_init(void)
{
	__HAL_RCC_DMA1_CLK_ENABLE();//DMA1通道使能
	
	g_dma_handler.Instance=DMA1_Channel1;
	
	g_dma_handler.Init.Direction=DMA_MEMORY_TO_MEMORY;
	g_dma_handler.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE;
	g_dma_handler.Init.MemInc=DMA_MINC_ENABLE;     //内存地址自增
    g_dma_handler.Init.Mode=DMA_NORMAL;            //内存到内存不支持循环模式
	
	g_dma_handler.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;
	g_dma_handler.Init.PeriphInc=DMA_PINC_ENABLE;  //外设地址自增  这里其实还是内存
	g_dma_handler.Init.Priority=DMA_PRIORITY_HIGH;
	HAL_DMA_Init(&g_dma_handler);
	
	HAL_DMA_Start(&g_dma_handler,(uint32_t)src_buf,(uint32_t)dest_buf,0);
    //最后一个参数是传输长度,初始化时不让他传输
	

}
void enble_dma_transmit(uint16_t cndtr)
{
	__HAL_DMA_DISABLE(&g_dma_handler);  //失能DMA,设置传输长度
    DMA1_Channel1->CNDTR=cndtr;         //或g_dma_handler.Instance->CNDTR=cndtr;	
    __HAL_DMA_ENABLE(&g_dma_handler);   //使能DMA
    //也可以仿照标准库函数,在传输完成函数里判断是否传输完成,这样在主函数中就不需要再判断了
    //while(__HAL_DMA_GET_FLAG(&g_dma_handler,DMA_FLAG_TC1)==RESET);
    //__HAL_DMA_CLEAR_FLAG(&g_dma_handler,DMA_FLAG_TC1);//赋值传输长度并等待转换完成
}
int main(void)
{ 
	
	uint8_t key=0;
	HAL_Init();                        //初始化HAL库
	sys_stm32_clock_init(RCC_PLL_MUL9);//9倍频,72MHz
	delay_init(72);                    //延时初始化
	led_init();
	beep_init();
	key_init();
	usart_init(115200);
	dma_init();	                   
	
	while(1)
	{
        key=key_scan(0);
		if (key == WKUP_PRES)
        {
            //将0赋给地址为dest_buf的前10个元素/或者直接在数组定义时赋0
		    memset(dest_buf, 0, 10);
            enble_dma_transmit(10);
			while(1)
            {
                if(__HAL_DMA_GET_FLAG(&g_dma_handler,DMA_FLAG_TC1))//通道1
			    printf("传输完成\r\n");
				__HAL_DMA_CLEAR_FLAG(&g_dma_handler,DMA_FLAG_TC1);
		    }
			 
		}			
	}
}

此标准库与HAL库程序的异同

1.判断DMA传输完成函数既可以放在DMA_transfer()函数里面判断,也可以放在主函数中判断,大同小异,个人倾向于放在传输函数里面,这样一来只要出了传输函数,DMA就已经传输完成了,方便进行其他的操作。

2.启动DMA传输的条件相同,标准库和HAL库都是1.失能DMA(只有失能才能对传输计数器的个数进行设置)   2.对传输计时器个数赋值   3.使能DMA开始传输。另外赋传输个数时标准库使用函数赋个数,HAL库示例直接使用寄存器操作。再次重述一下DMA启动传输的3个条件:①对DMA进行使能  ②传输计数器个数不为0 ③必须有触发信号(软件触发/硬件触发)。

3.触发DMA的参数标准库有特别指出,参数是M2M。而HAL库并没有特别指出。在后面的外设与DMA配合转运数据的实验中,HAL库用__HAL_LINKDMA(&g_uart1_handler, hdmatx,g_dma_handle)函数来连接硬件与DMA。后面会详细说。

2、ADC配合DMA进行数据转运

DMA第1种运用在实际运用的很少(为了理解DMA的使用),DMA用的最多的就是和外设相互配合,用DMA实现对数据的转运,最典型的就是ADC+DMA相互配合,ADC每转换完成一次数据,就触发DMA对转换的数据进行转运,实现对ADC转换数据的保存。(ADC只有一个数据寄存器,每次转换的结果存放在数据寄存器中,当有多个ADC通道需要转换时,最新ADC转换的数据会覆盖之前的转换数据。)

本图片源自B战江科大STM入门教程PPT

DMA相关配置:

外设地址:ADC_DR寄存器地址

外设地址是否自增:否

数据宽度:16位(半字)

存储器地址:位于SRAM的数组名ADValue

存储器地址是否自增:是

数据宽度:16位(半字)     由ADC_DR的位数决定

传输计数器个数:7次(与ADC通道数对应)

自动重装器:若要连续扫描图中的7个通道就设置为1,否则设置为0

触发方式:ADC硬件触发

ADC连续模式的缺点就是每个通道转换完成后就会覆盖ADC_DR的值。

ADC的连续扫描模式可以与DMA的的循环模式是相互配合的。

此实验代码会在后续文章中讲解。

9、相关拓展

因为STM32的CPU是32位的,寻址范围也是32位的范围(0x0000 0000-0xffff ffff)最大存储空间4G。 

32位机/64位机:字长是32位/字长是64位。           

32位机的地址总线也是32根。(每个地址对应一个字节的数据,例如0x0000 00ff(地址):0000 0001(8位数据))

CPU的字长表示每次处理数据的能力,总是以8的倍数为单位。

欢迎提问讨论,共同进步,望诸君共勉!

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32 HAL库中,使用DMA实现串口数据收发也是非常简单的,只需调用相应的HAL库函数即可。以下是一个基本的示例: ```c #include "stm32f10x.h" #include "stm32f10x_hal.h" #define USART_RX_BUFFER_SIZE 256 volatile uint8_t usart_rx_buffer[USART_RX_BUFFER_SIZE]; volatile uint16_t usart_rx_write_index = 0; volatile uint16_t usart_rx_read_index = 0; UART_HandleTypeDef huart1; void usart_init(void) { GPIO_InitTypeDef GPIO_InitStruct; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_DMA1_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct.Pin = GPIO_PIN_10; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; HAL_UART_Init(&huart1); HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn); HAL_UART_Receive_DMA(&huart1, (uint8_t*)usart_rx_buffer, USART_RX_BUFFER_SIZE); } void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { usart_rx_write_index = USART_RX_BUFFER_SIZE - huart->hdmarx->Instance->CNDTR; while (usart_rx_read_index != usart_rx_write_index) { uint8_t data = usart_rx_buffer[usart_rx_read_index]; usart_rx_read_index = (usart_rx_read_index + 1) % USART_RX_BUFFER_SIZE; // 处理接收到的数据 // ... if (usart_rx_read_index == usart_rx_write_index) { break; } } HAL_UART_Receive_DMA(&huart1, (uint8_t*)usart_rx_buffer, USART_RX_BUFFER_SIZE); } int main(void) { usart_init(); while (1) { // 主循环中不需要处理接收到的数据 } } void DMA1_Channel5_IRQHandler(void) { HAL_DMA_IRQHandler(huart1.hdmarx); } ``` 在上述代码中,我们使用了HAL库函数 `HAL_UART_Receive_DMA` 来启动USART1的DMA接收,同时使用了HAL库函数 `HAL_UART_RxCpltCallback` 来处理接收到的数据。需要注意的是,我们需要在 `HAL_UART_RxCpltCallback` 中再次调用 `HAL_UART_Receive_DMA` 来启动下一轮DMA接收。 此外,在HAL库中,我们也需要在主函数中调用 `HAL_NVIC_EnableIRQ` 函数来使能DMA中断,并且需要在中断处理函数中调用 `HAL_DMA_IRQHandler` 函数来处理DMA中断。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值