DMA数据传输
什么是DMADMADirect Memory Access直接存储器访问提供在外设与内存、存储器和存储器之间的高速数据传输使用。它允许不同速度的硬件装置来沟通而不需要依赖于CPU在这个时间中CPU对于内存的工作来说就无法使用。*DMA就是一个数据搬运工DMA的意义代替CPU搬运数据为CPU减负数据搬运的工作比较耗时间数据搬运工作时效要求高有数据来就要搬走没啥技术含量CPU 节约出来的时间可以处理更重要的事。搬运什么数据存储器、外设这里的外设指的是spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。三种搬运方式存储器→存储器例如复制某特别大的数据buf存储器→外设 例如将某数据buf写入串口TDR寄存器外设→存储器 例如将串口RDR寄存器写入某数据bufDMA框图CPU搬运数据走系统总线将APB1和APB2接的外设存储器捏的数据和SRAM进行互传DMA搬运数据先由外设发送DMA请求由仲裁器判断优先级走DMA总线将APB1和APB2接的外设存储器捏的数据和SRAM进行互传DMA控制器STM32F103C8T6只有DMA1DMA1有7个通道DMA2有5个通道一个通道每次只能搬运一个外设的数据如果同时有多个外设的DMA请求则按照优先级进行响应。DMA优先级管理软件硬件软件每个通道的优先级可以在DMA_CCRx寄存器中设置有4个等级最高级高级中级低级硬件如果2个请求它们的软件优先级相同则较低编号的通道比较高编号的通道有较高的优先权。比如如果软件优先级相同通道2优先于通道4DMA传输方式DMA_Mode_Normal正常模式一次DMA数据传输完后停止DMA传送 也就是只传输一次DMA_Mode_Circular循环传输模式当传输结束时硬件自动会将传输数据量寄存器进行重装进行下一轮的数据传输。 也就是多次传输模式指针递增模式外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时下一个要传输的地址将是前一个地址加上增量值。DMA数据对齐模式紫色为要传输的数据的宽度绿色为被传端的宽度宽度一致则正好覆盖紫色端的宽度较小则传输到绿色端的低位高位补零紫色端的宽度较大则紫色端的低位占满绿色端紫色端其余高位溢出DMA寄存器DMA中断状态寄存器(DMA_ISR)DMA中断标志清除寄存器(DMA_IFCR)位31:28保留始终读为0。位27 23 19 15 11 7 3CTEIFx清除通道x的传输错误标志(x 1 … 7) (Channel x transfer error clear)这些位由软件设置和清除。br /0不起作用br /1清除DMA_ISR寄存器中的对应TEIF标志。位26 22 18 14 10 6 2CHTIFx清除通道x的半传输标志(x 1 … 7) (Channel x half transfer clear)这些位由软件设置和清除。br /0不起作用br /1清除DMA_ISR寄存器中的对应HTIF标志。位25 21 17 13 9 5 1CTCIFx清除通道x的传输完成标志(x 1 … 7) (Channel x transfer complete clear)这些位由软件设置和清除。br /0不起作用br /1清除DMA_ISR寄存器中的对应TCIF标志。位24 20 16 128 4 0CGIFx清除通道x的全局中断标志(x 1 … 7) (Channel x global interrupt clear)这些位由软件设置和清除。br /0不起作用br /1清除DMA_ISR寄存器中的对应的GIF、 TEIF、 HTIF和TCIF标志。DMA通道x配置寄存器(DMA_CCRx)(x 1…7)位31:15保留始终读为0。位14MEM2MEM存储器到存储器模式 (Memory to memory mode)该位由软件设置和清除。br /0非存储器到存储器模式br /1启动存储器到存储器模式。位13:12PL[1:0]通道优先级 (Channel priority level)这些位由软件设置和清除。br /00低br /01中br /10高br /11最高位11:10MSIZE[1:0]存储器数据宽度 (Memory size)这些位由软件设置和清除。br /00 8位br /01 16位br /10 32位br /11保留位9:8PSIZE[1:0]外设数据宽度 (Peripheral size)这些位由软件设置和清除。br /00 8位br /01 16位br /10 32位br /11保留位7MINC存储器地址增量模式 (Memory increment mode)该位由软件设置和清除。br /0不执行存储器地址增量操作br /1执行存储器地址增量操作位6PINC外设地址增量模式 (Peripheral increment mode)该位由软件设置和清除。 0不执行外设地址增量操作br /1执行外设地址增量操作位5CIRC循环模式 (Circular mode)该位由软件设置和清除。br /0不执行循环操作br /1执行循环操作位4DIR数据传输方向 (Data transfer direction)该位由软件设置和清除。br /0从外设读br /1从存储器读位3TEIE允许传输错误中断 (Transfer error interrupt enable)该位由软件设置和清除。0禁止TE中断br /1允许TE中断位2HTIE允许半传输中断 (Half transfer interrupt enable)该位由软件设置和清除。br /0禁止HT中断br /1允许HT中断位1TCIE允许传输完成中断 (Transfer complete interrupt enable)该位由软件设置和清除。br /0禁止TC中断br /1允许TC中断DMA通道x传输数量寄存器(DMA_CNDTRx)(x 1…7)DMA通道x外设地址寄存器(DMA_CPARx)(x 1…7)DMA通道x存储器地址寄存器(DMA_CMARx)(x 1…7)DMA库函数__HAL_RCC_DMA1_CLK_ENABLE()宏定义函数用于使能 DMA1直接存储器访问控制器1的时钟。HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma)是 STM32 HAL 库中用于初始化指定的 DMA 通道/流的函数。根据传入的句柄配置参数如传输方向、数据宽度、优先级、地址增量等设置 DMA 控制器的相应寄存器并使 DMA 通道进入就绪状态等待后续的启动操作。返回值类型为HAL_StatusTypeDef可能的值HAL_OK初始化成功HAL_ERROR参数错误或硬件故障HAL_BUSYDMA 正在使用中无法重新初始化HAL_TIMEOUT超时通常不适用于此函数DMA_HandleTypeDef *hdma指向 DMA 句柄结构体的指针该结构体包含了 DMA 通道/流的所有配置信息以及状态变量。DMA_HandleTypeDef结构体typedef struct __DMA_HandleTypeDef { DMA_Channel_TypeDef *Instance; // 指向 DMA 通道/流的寄存器基地址如 DMA1_Channel1 DMA_InitTypeDef Init; // 初始化配置结构体 HAL_LockTypeDef Lock; // 锁定标志用于防止并发访问 __IO HAL_DMA_StateTypeDef State; // DMA 状态如 HAL_DMA_STATE_RESET, READY, BUSY void *Parent; // 指向关联的外设句柄如 SPI_HandleTypeDef用于回调通知 void (*XferCpltCallback)(struct __DMA_HandleTypeDef *hdma); // 传输完成回调函数指针 void (*XferHalfCpltCallback)(struct __DMA_HandleTypeDef *hdma); // 半传输完成回调 void (*XferErrorCallback)(struct __DMA_HandleTypeDef *hdma); // 传输错误回调 void (*XferAbortCallback)(struct __DMA_HandleTypeDef *hdma); // 中止传输回调 __IO uint32_t ErrorCode; // 错误码如 HAL_DMA_ERROR_TE, HAL_DMA_ERROR_FE 等 uint32_t StreamBaseAddress; // 部分系列使用流基地址用于计算 FIFO 地址 uint32_t StreamIndex; // 部分系列使用流索引号 } DMA_HandleTypeDef;关键子结构体DMA_InitTypeDeftypedef struct { uint32_t Direction; // 数据传输方向 // - DMA_PERIPH_TO_MEMORY外设到内存 // - DMA_MEMORY_TO_PERIPH内存到外设 // - DMA_MEMORY_TO_MEMORY内存到内存 uint32_t PeriphInc; // 外设地址是否递增 // - DMA_PINC_ENABLE使能递增 // - DMA_PINC_DISABLE禁止递增 uint32_t MemInc; // 内存地址是否递增 // - DMA_MINC_ENABLE使能递增 // - DMA_MINC_DISABLE禁止递增 uint32_t PeriphDataAlignment; // 外设数据宽度 // - DMA_PDATAALIGN_BYTE字节 (8位) // - DMA_PDATAALIGN_HALFWORD半字 (16位) // - DMA_PDATAALIGN_WORD字 (32位) uint32_t MemDataAlignment; // 内存数据宽度选项同上 uint32_t Mode; // DMA 传输模式 // - DMA_NORMAL正常模式传输一次后停止 // - DMA_CIRCULAR循环模式自动重载持续传输 // - DMA_PFCTRL外设流控制模式由外设控制传输长度 uint32_t Priority; // 通道优先级 // - DMA_PRIORITY_LOW // - DMA_PRIORITY_MEDIUM // - DMA_PRIORITY_HIGH // - DMA_PRIORITY_VERY_HIGH } DMA_InitTypeDef;其他重要成员说明Instance指定要初始化的具体 DMA 通道/流。例如DMA1_Channel1、DMA2_Stream0等需根据芯片型号选择。Lock内部锁机制防止在多任务环境下重复初始化或同时操作同一 DMA 通道。State反映当前 DMA 通道的状态如HAL_DMA_STATE_RESET未初始化、HAL_DMA_STATE_READY已初始化且空闲、HAL_DMA_STATE_BUSY正在传输等。Parent可选指向使用该 DMA 的外设句柄。例如若 DMA 用于 SPI 传输可将 SPI 句柄赋给此字段以便在回调中获取上下文。回调函数指针用户可注册自定义回调函数在传输完成、半完成、出错或中止时被自动调用。ErrorCode记录最近一次错误的详细原因便于调试。HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)用于启动一次 DMA 数据传输。它会将传入的源地址、目标地址和数据长度写入 DMA 控制器的相应寄存器清除之前的传输状态然后使能 DMA 通道开始传输。该函数是非阻塞的调用后立即返回传输完成后可通过回调或轮询状态获知结果。参数类型含义hdmaDMA_HandleTypeDef *指向已初始化好的 DMA 句柄结构体包含通道/流配置和状态信息。SrcAddressuint32_t源数据的起始地址可以是外设数据寄存器地址或内存地址。DstAddressuint32_t目标数据的起始地址可以是外设数据寄存器地址或内存地址。DataLengthuint32_t要传输的数据项数量单位是“项”而非字节。每个数据项的字节数由初始化时设置的PeriphDataAlignment/MemDataAlignment决定。例如若数据宽度设为半字2字节则DataLength 100表示传输 200 字节。注意事项地址对齐源地址和目标地址应与配置的数据宽度对齐如 32 位宽度时地址应为 4 的倍数。传输模式若配置为DMA_NORMAL传输完指定数量后自动停止若为DMA_CIRCULAR传输完成后会自动重载计数并继续。多次调用每次调用前需确保上一次传输已完成或已中止否则可能导致未定义行为。中断与回调若已在HAL_DMA_Init中使能了相关中断如传输完成中断则在传输结束后会自动调用注册的回调函数。__HAL_LINKDMA__HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD__,__DMA_HANDLE__)用于将外设句柄中的 DMA 处理句柄指针与实际的 DMA 句柄进行绑定。这样当 DMA 传输完成或发生错误时DMA 的中断服务程序可以通过该关联找到对应的外设句柄进而调用外设的回调函数如HAL_UART_TxCpltCallback。简单来说它建立了一条从 DMA 到外设的“反向链接”让 DMA 知道传输完成后应该通知哪个外设。参数类型含义__HANDLE__外设句柄如UART_HandleTypeDef *指向需要使用 DMA 的外设句柄例如huart1。__PPP_DMA_FIELD__外设句柄中的一个成员名外设句柄中用于存储 DMA 句柄指针的字段名。常见的有-hdmatx发送 DMA 句柄-hdmarx接收 DMA 句柄例如huart1.hdmatx就是该字段。__DMA_HANDLE__DMA_HandleTypeDef *指向已经初始化好的 DMA 句柄例如hdma_usart1_tx。通常在初始化外设如 UART、SPI、ADC的 DMA 功能时在配置好 DMA 句柄之后调用此宏将两者关联然后再启动 DMA 传输。HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)用于以 DMA 方式启动 UART 异步接收。它配置 UART 外设和 DMA 通道使 UART 接收到的数据自动通过 DMA 传输到指定的内存缓冲区无需 CPU 逐字节干预。该函数是非阻塞的调用后立即返回数据接收完成后会通过回调函数通知用户。参数类型含义huartUART_HandleTypeDef *指向 UART 句柄的指针该句柄包含 UART 外设配置、状态以及绑定的 DMA 句柄通过__HAL_LINKDMA关联。pDatauint8_t *指向接收数据缓冲区的指针DMA 会将接收到的数据连续存入该缓冲区。Sizeuint16_t期望接收的数据字节数即缓冲区大小。当 DMA 传输完指定数量的字节后会自动产生传输完成中断如果使能并调用HAL_UART_RxCpltCallback回调。注意事项使用前必须确保已通过HAL_UART_Init()初始化 UART并通过__HAL_LINKDMA(huart, hdmarx, dma_handle)将 DMA 句柄绑定到 UART 句柄。接收过程中若发生错误如溢出、帧错误等会调用HAL_UART_ErrorCallback。若希望连续接收不定长数据可配合空闲中断HAL_UARTEx_ReceiveToIdle_DMA使用。在调用此函数前应确保 UART 和 DMA 时钟已使能。HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)用于以 DMA 方式启动 UART 异步发送。它将指定的数据缓冲区内容通过 DMA 自动传输到 UART 发送数据寄存器由 UART 硬件逐字节发送出去无需 CPU 干预。该函数是非阻塞的调用后立即返回发送完成后会调用HAL_UART_TxCpltCallback回调函数若已注册。参数类型含义huartUART_HandleTypeDef *指向 UART 句柄的指针该句柄包含 UART 配置、状态以及绑定的发送 DMA 句柄通过__HAL_LINKDMA关联hdmatx。pDataconst uint8_t *指向待发送数据的缓冲区首地址。由于只读可传入常量数组或字符串。Sizeuint16_t要发送的字节数即 DMA 传输的数据项个数每个数据项为 1 字节。注意事项使用前需确保 UART 已初始化且已通过__HAL_LINKDMA(huart, hdmatx, hdma_handle)绑定发送 DMA 句柄。发送期间不可修改pData缓冲区内容直到回调函数被调用。若需要连续发送多帧数据应在上一帧发送完成回调中再次调用本函数避免冲突。__HAL_DMA_GET_FLAG()__HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__)是一个宏用于读取指定 DMA 通道/流的特定状态标志位如传输完成、半传输完成、传输错误等。它通过判断标志寄存器中对应位的值返回该标志是否被置位即事件是否发生。常用于轮询方式等待 DMA 操作完成或在中断服务程序中查询具体事件源。参数类型含义__HANDLE__DMA_HandleTypeDef *指向 DMA 句柄的指针用于确定要查询的具体 DMA 通道/流通过Instance成员定位寄存器地址。__FLAG__预定义的标志宏要检查的标志位常用取值如下不同系列可能有差异-DMA_FLAG_TC传输完成标志-DMA_FLAG_HT半传输完成标志-DMA_FLAG_TE传输错误标志-DMA_FLAG_FEFIFO 错误标志部分系列-DMA_FLAG_DME直接模式错误标志部分系列返回值非零值指定的标志位被置位事件已发生零标志位未被置位注意事项该宏不会自动清除标志位读取后如需清除需调用__HAL_DMA_CLEAR_FLAG。在中断服务程序中通常先通过此宏判断中断来源再执行相应处理并清除标志。对于支持多个流的 DMA 控制器__HANDLE__必须正确指向对应的流句柄否则可能读到错误的标志寄存器。__HAL_DMA_ENABLE()__HAL_DMA_ENABLE(__HANDLE__)是一个宏用于使能指定的 DMA 通道/流。它将 DMA 通道/流对应的使能位如DMA_SxCR.EN或DMA_CCR.EN置 1允许 DMA 开始或继续数据传输。通常在配置好 DMA 参数并设置好源/目的地址后调用此宏来启动传输或者用于恢复被暂停的传输。参数类型含义__HANDLE__DMA_HandleTypeDef *指向 DMA 句柄的指针用于确定要操作的 DMA 通道/流通过Instance成员定位控制寄存器。注意事项调用前必须确保 DMA 时钟已使能且所有配置寄存器已正确设置。若 DMA 已使能且正在传输再次调用此宏不会产生额外效果使能位已置 1。对应的禁用宏为__HAL_DMA_DISABLE(__HANDLE__)用于停止 DMA 传输。__HAL_DMA_DISABLE()__HAL_DMA_DISABLE(__HANDLE__)是一个宏用于禁用指定的 DMA 通道/流。它将 DMA 通道/流对应的使能位如DMA_SxCR.EN或DMA_CCR.EN清零立即停止当前正在进行的 DMA 数据传输。常用于终止传输、重置 DMA 状态或在配置更改前安全地关闭 DMA。参数类型含义__HANDLE__DMA_HandleTypeDef *指向 DMA 句柄的指针用于确定要操作的 DMA 通道/流通过Instance成员定位控制寄存器。注意事项禁用 DMA 后正在进行的数据传输会立即中止未传输完成的数据将丢失。若需要在传输完成后自然停止应使用DMA_NORMAL模式无需手动调用禁用。在中断服务程序中处理错误时常先调用此宏停止 DMA再进行错误清理。DMA实验内存到内存声明DMA_HandleTypeDef句柄调用HAL_DMA_Init()函数声明srt_buf[]、dst_buf[]缓冲区数组使用HAL_DMA_Start()函数开启DMA传输利用__HAL_DMA_GET_FLAG()判断DMA_FLAG_TC传输完成标志标志值为1就代表传输已完成打印传输的数据内存到外设声明DMA_HandleTypeDef句柄调用HAL_DMA_Init()函数再调用__HAL_LINKDMA()函数__HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD__,__DMA_HANDLE__)将外设句柄中的DMA 处理句柄指针eguart1_handle需要采用extern调用外部变量与实际的 DMA 句柄DMA_HandleTypeDef进行绑定。PPP_DMA_FIELD外设句柄中用于存储 DMA 句柄指针的字段名串口中有这两个DMA_HandleTypeDef *hdmatx; DMA_HandleTypeDef *hdmarx; 内存到外设使用hdmatx这一个。main.c声明一个发送缓冲区数组send_buf[]再调用HAL_UART_Transmit_DMA()函数利用串口1打印数据外设到内存声明DMA_HandleTypeDef句柄调用HAL_DMA_Init()函数再调用__HAL_LINKDMA()函数__HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD__,__DMA_HANDLE__)将外设句柄中的DMA 处理句柄指针eguart1_handle需要采用extern调用外部变量与实际的 DMA 句柄DMA_HandleTypeDef进行绑定。PPP_DMA_FIELD外设句柄中用于存储 DMA 句柄指针的字段名串口中有这两个DMA_HandleTypeDef *hdmatx; DMA_HandleTypeDef *hdmarx; 外设到内存使用hdmarx这一个。再使用HAL_UART_Receive_DMA()函数以 DMA 方式启动 UART 异步接收接受完成时URAT1触发空闲中断这时会调用USART1_IRQHandler()函数。判断如果空闲标志位置1说明数据传输已完成。使用__HAL_UART_CLEAR_IDLEFLAG()函数清除空闲中断标志位使用HAL_UART_DMAStop()函数停止UART的DMA传输防止干扰使用__HAL_DMA_GET_COUNTER()函数获取DMA通道中剩余的数据大小使用缓冲区大小 UART1_RX_BUF_SIZE 减去剩余数据大小得到接收数据的大小打印接收到的数据和它的大小清除接收数据缓冲区使用HAL_UART_Receive_DMA()函数再次以DMA 方式启动 UART 异步接收