下面以STM32G474为例,使用DMA来存储USART1的接收数据。
1. 查看硬件支持
首先查看要使用的DMA支持的通道数,在手册中有如下说明。
根据上图可以看到,对于不同的设备类型有不同的DMA通道数量。设备类型分类如下图所示。
我使用的是STM32G474因此是属于Category 3,也就是说,DMA1有8个通道,DMA2也有8个通道。
2. DMA框图
由上图可以看到,DMA请求是通过 DMA_MUX 过来的,然后通过仲裁器对来的请求源进行仲裁,根据优先级来确定先处理那些请求,然后再返回DMA_ack。每个DMA有分别有两个接口为 AHB主接口 和 AHB子接口,数据由子接口传递到主接口。
3. DMA_MUX(DMA矩阵)
跟GPIO矩阵一样,DMA也有矩阵,通过矩阵来选择请求源。
框图如下图所示:
3.1 DMA requests from peripherals 外设请求源
左侧 DMA requests from peripherals 是外设请求源,在 DMAMUX_CxCR中配置 DMAREQ_ID[6:0] 进行选择,其映射表如下表所示。
如:我要将DMA_MUX的ch0选择USART1_RX为DMA请求输出,则应该将 DMAMUX_C0CR 的 DMAREQ_ID 配置为24(上表USART1_RX映射为24)。
3.2 DMA请求生成器
在DMA_MUX框图的左侧有一个 DMA请求生成器,可通过外部信号或者其他信号来触发 DMA请求生成器 来产生DMA请求给DMA_MUX选择。
我认为可以这么理解:
- 外设过来的信号直接到DMA_MUX的叫DMA请求
- 外部引脚或者一些其他信号 要通过 DMA请求生成器 来生成DMA请求的(生成后传递给DMA_MUX),触发DMA请求生成器的信号叫 触发信号
G474的有4个DMA触发生成器(在3.1章节中映射表的前4个请求源就是由 DMA请求生成器 生成的DMA请求源),通过DMAMUX_RGxCR寄存器配置选择触发源。
因为我没有使用外部触发功能,因此没有做测试,大概的思路应该是这样的,因此对DMA请求生成器不做过多介绍,如有错误欢迎讨论。
3.3 同步功能
同步功能可以认为是门控,当 DMAMUX_CxCR 中的 SE 置位,代表同步功能有效,当检测到同步信号有效,则会将请求源的信号传递给框图末端的 DMA request out,即 请求 给了 DMA 。
因为我没有使用DMA同步功能,因此没有做测试,大概的思路应该是这样的,如有错误欢迎讨论。
3.4 输出信号
框图右侧有两种输出信号,一个就是DMA请求输出信号,另一个是DMA事件信号。
DMA_MUX请求输出信号与DMA通道一 一对应,即:
DMA_MUX通道 | DMA通道 |
---|---|
DMAMUX1_Channel0 | DMA1_Channel1 |
DMAMUX1_Channel1 | DMA1_Channel2 |
DMAMUX1_Channel2 | DMA1_Channel3 |
DMAMUX1_Channel3 | DMA1_Channel4 |
DMAMUX1_Channel4 | DMA1_Channel5 |
DMAMUX1_Channel5 | DMA1_Channel6 |
DMAMUX1_Channel6 | DMA1_Channel7 |
DMAMUX1_Channel7 | DMA1_Channel8 |
DMAMUX1_Channel8 | DMA2_Channel1 |
DMAMUX1_Channel9 | DMA2_Channel2 |
DMAMUX1_Channel10 | DMA2_Channel3 |
DMAMUX1_Channel11 | DMA2_Channel4 |
DMAMUX1_Channel12 | DMA2_Channel5 |
DMAMUX1_Channel13 | DMA2_Channel6 |
DMAMUX1_Channel14 | DMA2_Channel7 |
DMAMUX1_Channel15 | DMA2_Channel8 |
由于没有使用事件,因此也不介绍,其大概也就是置位标志位,或者几个请求后置位标志位或者中断,因为不使用,因此不介绍。
3.5 DMA_MUX中断
DMAMUX的中断都是 生成器 或者 同步模式 的,我没用,因此不管。
4.DMA的使用
4.1 DMA的配置步骤
DMA通道配置步骤
根据以下顺序配置DMA通道:
- 配置外设寄存器地址在 DMA_CPARx寄存器
- 配置内存地址在DMA_CMARx寄存器
- 配置传输数据数量在DMA_CNDTRx寄存器
- 在DMA_CCRx寄存器中配置以下参数:
- 通道优先级
- 数据传输方向
- 循环模式
- 外设地址 和 内存地址 增长模式
- 外设 和 内存 数据格式大小
- 中断使能一半 和/或 完全传输 和/或传输错误
4.2 配置示例
以 USART1 外设为例,将 USART1 的 RDR 数据(接收数据,波特率38400)通过 DMA2_CH2 存储到 RX_Buffer 变量中,详细代码如下。
4.2.1 USART1 初始化配置
c
#define USART1_SendByte(byte) USART1->TDR = byte
#define USART1_EnableReceive() USART1->CR1 |= USART_CR1_RE;
#define USART1_DisableReceive() USART1->CR1 &= ~(USART_CR1_RXNEIE | USART_CR1_RE)
#define USART1_EnableSend() USART1->CR1 |= USART_CR1_TE;\
USART1->CR1 |= (USART_CR1_TXEIE | USART_CR1_TCIE)
#define USART1_DisableSend() USART1->CR1 &= ~(USART_CR1_TXEIE | USART_CR1_TE | USART_CR1_TCIE)
#define USART1_DisableTXEInt() USART1->CR1 &= ~USART_CR1_TXEIE
void USART1_Function_Init(void)
{
/*
在RCC_CFGR寄存器中配置USART时钟源fCK
当前配置为SYSCLK 168MHz
*/
//步骤1 配置CR1寄存器的M标志位,即配置数据长度
USART1->CR1 = (USART1->CR1 & 0x00000000)
// |USART_CR1_RXFFIE //bit31:RXFIFO Full interrupt enable
// |USART_CR1_TXFEIE //bit30:TXFIFO empty interrupt enable
// |USART_CR1_FIFOEN //bit29:FIFO mode enable
// |USART_CR1_M1 //bit28:【00|1起始位,8数据位】【01|1起始位,9数据位】【10|1起始位,7数据位】
// |USART_CR1_EOBIE //bit27:【1|EOBF标志位中断使能】
// |USART_CR1_RTOIE //bit26:【1|RTOF标志位中断使能】
// |(0 << USART_CR1_DEAT_Pos) //bit[25:21]:DE时间
// |(0 << USART_CR1_DEDT_Pos) //bit[20:16]:DE时间
// |USART_CR1_OVER8 //bit15:【0|过采样/16】【1|过采样/8】
// |USART_CR1_CMIE //bit14:字符匹配中断使能(CMF标志位)
// |USART_CR1_MME //bit13:静音模式使能【0|一直活动】【1|静音模式】配合WAKE唤醒
// |USART_CR1_M0 //bit12:【00|1起始位,8数据位】【01|1起始位,9数据位】【10|1起始位,7数据位】
// |USART_CR1_WAKE //bit11:唤醒方法配置【0|空闲总线唤醒】【1|地址唤醒】
// |USART_CR1_PCE //bit10:奇偶校验使能
// |USART_CR1_PS //bit09:【0|偶校验】【1|奇校验】
// |USART_CR1_PEIE //bit08:PE中断使能位
// |USART_CR1_TXEIE //bit07:TXE中断使能位
// |USART_CR1_TCIE //bit06:TC中断使能位
// |USART_CR1_RXNEIE //bit05:RXNE/ORE中断使能位
// |USART_CR1_IDLEIE //bit04:IDLE中断使能位
// |USART_CR1_TE //bit03:发送使能
// |USART_CR1_RE //bit02:接收使能
// |USART_CR1_UESM //bit01:【0|USART不能在停止模式唤醒】【1|当配置USART的RCC时钟,可以在停止模式被唤醒】
// |USART_CR1_UE //bit00:使能USART模块
;
//步骤2 设置波特率(16bit)
USART1->BRR = 4375; //bit[15:4]:USARTDIV[15:4]//168000000/38400 = 4375
//bit[3:0]:【OVER8=0| USARTDIV[3:0]】【OVER8=1| USARTDIV[3:0]右移1位,此时BIT3必须为0】
//步骤3 配置停止位长度
USART1->CR2 = (USART1->CR2 & 0x00000086)
// |(0x00 << USART_CR2_ADD_Pos) //bit[31:24]:地址匹配或者数据匹配的数据
// |USART_CR2_RTOEN //bit23:接收超时使能
// |USART_CR2_ABRMODE_1 //bit[22:21]:【00|起始位检测】【01|start->10检测】
// |USART_CR2_ABRMODE_0 //自动波特率检测 【10|0x7f检测】【11|0x55检测】
// |USART_CR2_ABREN //bit20:自动波特率检测使能【0|禁止】【1|使能】
// |USART_CR2_MSBFIRST //bit19:【0|先发送低位】【1|先发送高位】
// |USART_CR2_DATAINV //bit18:【0|正常】【1|二进制取反(1对应低电平)】
// |USART_CR2_TXINV //bit17:【0|正常(VDD为空闲)】【1|TX引脚认为VDD为忙,GND为空闲】
// |USART_CR2_RXINV //bit16:【0|正常(VDD为空闲)】【1|RX引脚认为VDD为忙,GND为空闲】
// |USART_CR2_SWAP //bit15:【0|正常】【1|TX和RX引脚功能调换】
// |USART_CR2_LINEN //bit14:LIN总线使能
// |USART_CR2_STOP_1 //bit[13:12]:【00|1停止位】【01|0.5停止位】
// |USART_CR2_STOP_0 //停止位选择 【10|2停止位】【11|1.5停止位】
// |USART_CR2_CLKEN //bit11:CK引脚使能标志位
// |USART_CR2_CPOL //bit10:【0|低值稳定】【1|高值稳定】
// |USART_CR2_CPHA //bit09:【第一个时钟沿发送或接受数据】【第二个时钟沿发送或接受数据】
// |USART_CR2_LBCL //bit08:【0|最后一个数据位的时钟脉冲不输出到CK引脚】【1|最后一个数据位的时钟脉冲输出到CK引脚】
// |USART_CR2_LBDIE //bit06:LBDF标志位中断使能
// |USART_CR2_LBDL //bit05:该位用于在 11 位或 10 位中断检测之间进行选择。【0|10位断点检测】【1|11位断点检测】
// |USART_CR2_ADDM7 //bit04:【0|4位地址检测】【1|7位地址检测】
// |USART_CR2_DIS_NSS //bit03:0: SPI slave selection depends on NSS input pin. 1: SPI slave is always selected and NSS input pin is ignored.
// |USART_CR2_SLVEN //bit00:Synchronous Slave mode enable
;
//步骤4 通过置位CR1的UE标志位使能USART模块
USART1->CR1 |= USART_CR1_UE;
//步骤5 配置DMA
USART1->CR3 = (USART1->CR3 & 0x00010000)
// |(0 << USART_CR3_TXFTCFG_Pos) //bit[29:31]:
// // 000:TXFIFO reaches 1/8 of its depth
// // 001:TXFIFO reaches 1/4 of its depth
// // 010:TXFIFO reaches 1/2 of its depth
// // 011:TXFIFO reaches 3/4 of its depth
// // 100:TXFIFO reaches 7/8 of its depth
// // 101:TXFIFO becomes empty
// |USART_CR3_RXFTIE //bit28:RXFIFO阈值中断启用
// |(0 << USART_CR3_RXFTCFG_Pos) //bit[25:27]:
// // 000:Receive FIFO reaches 1/8 of its depth
// // 001:Receive FIFO reaches 1/4 of its depth
// // 010:Receive FIFO reaches 1/2 of its depth
// // 011:Receive FIFO reaches 3/4 of its depth
// // 100:Receive FIFO reaches 7/8 of its depth
// // 101:Receive FIFO becomes full
// // Remaining combinations: Reserved
// |USART_CR3_TCBGTIE //bit24:传输完成,中断启用
// |USART_CR3_TXFTIE //bit23:TXFIFO阈值中断启用
// |USART_CR3_WUFIE //bit22:WUF标志位中断使能(停止模式唤醒)
// |USART_CR3_WUS_1 //bit[21:20]:【00|当地址相同置位】【01|Reserve】
// |USART_CR3_WUS_0 //唤醒停止中断标志位选择【WUF在起始位置位】【WUF在RXNE置位】
// |USART_CR3_SCARCNT //bit[19:17]:智能卡自动重复计数
// |USART_CR3_DEP //bit15:【0|DE信号高电平有效】【1|DE信号低电平有效】Driver Enable -> DE
// |USART_CR3_DEM //bit14:【0|DE信号禁止】【1|DE信号使能】
// |USART_CR3_DDRE //bit13:接收错误时禁止DMA【0|接收错误后仅置位标志位,不禁止DMA】【1|接收错误后禁止DMA】
// |USART_CR3_OVRDIS //bit12:【0|溢出后,置位ORE】【1|溢出后不置位ORE】
// |USART_CR3_ONEBIT //bit11:【0|3bit采样法】【1|1bit采样法】
// |USART_CR3_CTSIE //bit10:CTS中断使能
// |USART_CR3_CTSE //bit09:CTS使能
// |USART_CR3_RTSE //bit08:RTS使能
// |USART_CR3_DMAT //bit07:DMA使能发送
|USART_CR3_DMAR //bit06:DMA使能接收
// |USART_CR3_SCEN //bit05:智能卡模式使能【1|使能】
// |USART_CR3_NACK //bit04:【0|就校验错误NACK不传输】【1|奇偶校验错误NACK传输】
// |USART_CR3_HDSEL //bit03:单线半双工模式选择【1|半双工模式】
// |USART_CR3_IRLP //bit02:【1|IrDA低功耗模式】
// |USART_CR3_IREN //bit01:【1|IrDA模式使能】
// |USART_CR3_EIE //bit00:错误中断使能标志位
;
USART1->GTPR = 0x0000
// |(0 << 8) //bit[15:8]:Guard Time 守护时间 GT[7:0]
// |0x00 //bit[7:0]:分频值(page946) PSC[7:0]
;
USART1->RTOR = 0x00000000
// |(0 << 24) //bit[31:24]:块长度
// |0x000000 //bit[23:0]:接收超时值
;
USART1->RQR = (USART1->RQR & 0xffffffe0)
// |USART_RQR_TXFRQ //bit04:发送数据刷新请求,置位TXE
// |USART_RQR_RXFRQ //bit03:接收数据刷新请求,清除RXNE
// |USART_RQR_MMRQ //bit02:静音模式刷新请求,置位RWU,同时USART进入静音模式
// |USART_RQR_SBKRQ //bit01:截止请求,置位SBKF
// |USART_RQR_ABRRQ //bit00:自动模特率请求,置位ABRF标志位
;
// USART1->ISR =
// USART_ISR_REACK //bit22:接收标志
// |USART_ISR_TEACK //bit21:发送标志
// |USART_ISR_WUF //bit20:停止模式唤醒标志
// |USART_ISR_RWU //bit19:静音模式唤醒标志
// |USART_ISR_SBKF //bit18:发送截止标志
// |USART_ISR_CMF //bit17:ADD字符匹配标志
// |USART_ISR_BUSY //bit16:忙标志
// |USART_ISR_ABRF //bit15:自动波特率错误标志
// |USART_ISR_ABRE //bit14:自动波特率状态错误标志
// |USART_ISR_EOBF //bit12:块结束标志
// |USART_ISR_RTOF //bit11:接收超时标志
// |USART_ISR_CTS //bit10:CTS标志
// |USART_ISR_CTSIF //bit09:CTS中断标志
// |USART_ISR_LBD //bit08:LIN截止侦查标志
// |USART_ISR_TXE //bit07:发送数据寄存器空标志
// |USART_ISR_TC //bit06:发送完成标志
// |USART_ISR_RXNE //bit05:读数据寄存器不空标志
// |USART_ISR_IDLE //bit04:总线空标志
// |USART_ISR_ORE //bit03:溢出标志
// |USART_ISR_NE //bit02:起始位检测标志
// |USART_ISR_FE //bit01:帧错误标志
// |USART_ISR_PE //bit00:校验错误标志
USART1->ICR = (USART1->ICR & 0xffede4a0)
// |USART_ICR_WUCF //bit20:写1清除标志位
// |USART_ICR_CMCF //bit17:写1清除标志位
// |USART_ICR_EOBCF //bit12:写1清除标志位
// |USART_ICR_RTOCF //bit11:写1清除标志位
// |USART_ICR_CTSCF //bit09:写1清除标志位
// |USART_ICR_LBDCF //bit08:写1清除标志位
// |USART_ICR_TCCF //bit06:写1清除标志位
// |USART_ICR_ORECF //bit04:写1清除标志位
// |USART_ICR_IDLECF //bit03:写1清除标志位
// |USART_ICR_NCF //bit02:写1清除标志位
// |USART_ICR_FECF //bit01:写1清除标志位
// |USART_ICR_PECF //bit00:写1清除标志位
;
// USART1->RDR
// USART1->TDR
//步骤6 置位CR1寄存器的RE标志位,RX开始寻找起始帧。
USART1_EnableReceive();
}
4.2.2 RX_Buffer变量声明
c
#define RS232_RXBUFFER_SIZE 128
typedef struct RS232_Var_Str
{
char RX_Buffer[RS232_RXBUFFER_SIZE];
}RS232_Var_Struct;
extern RS232_Var_Struct RS232_Var;
4.2.3 DMA初始化
这里我使用DMA2的CH2,对应DMA_MUX是CH9
(DMA1占用8个DMA_MUX通道,DMA2占用8个DMA_MUX通道,DMA_MUX通道是从0开始,因此排到DMA2的CH2就是DMA_MUX的CH9)
DMA通道配置步骤
根据以下顺序配置DMA通道:
- 配置外设寄存器地址在 DMA_CPARx寄存器
- 配置内存地址在DMA_CMARx寄存器
- 配置传输数据数量在DMA_CNDTRx寄存器
- 在DMA_CCRx寄存器中配置以下参数:
- 通道优先级
- 数据传输方向
- 循环模式
- 外设地址 和 内存地址 增长模式
- 外设 和 内存 数据格式大小
- 中断使能一半 和/或 完全传输 和/或传输错误
c
void DMA_Function_Init(void)
{
DMA2_Channel2->CCR &= ~DMA_CCR_EN; //步骤0:禁止使能DMA,准备配置
DMA2_Channel2->CPAR = (uint32_t)(&USART1->RDR); //步骤1:配置外设地址
DMA2_Channel2->CMAR = (uint32_t)(&RS232_Var.RX_Buffer[0]); //步骤2:配置内存地址
DMA2_Channel2->CNDTR = RS232_RXBUFFER_SIZE; //步骤3:传输数据数量 这里配置128个,也就是Rx_Buffer的大小
//步骤4: 在DMA_CCRx寄存器中配置需要的参数
DMA2_Channel2->CCR = 0x00000000
// |DMA_CCR_MEM2MEM // Bit14: memory-to-memory mode 【1: enabled】
|(0 << DMA_CCR_PL_Pos) // Bit[13:12]: 优先级配置。【00: low 01: medium 10: high 11: very high】
|(0 << DMA_CCR_MSIZE_Pos) // Bit[11:10]: 内存地址对应大小 size【00: 8 bits 01: 16 bits 10: 32 bits 11: reserved】
|(0 << DMA_CCR_PSIZE_Pos) // Bit[09:08]: 外设地址对应大小 size【00: 8 bits 01: 16 bits 10: 32 bits 11: reserved】
|DMA_CCR_MINC // Bit07: 内存指针自加【1: enabled】(由于想法是USART接收到1字节,就存到Rxbuffer中,因此需要对内存地址自加)
// |DMA_CCR_PINC // Bit06: 外设指针自加【1: enabled】
|DMA_CCR_CIRC // Bit05: 循环模式【1: enabled】使能后当传输数量达到,CNDTR变成0后,自动重装CNDTR寄存器,重新使能DMA
// |DMA_CCR_DIR // Bit04: 数据传输方向 【0:外设->内存】【1:内存->外设】
// |DMA_CCR_TEIE // Bit03: transfer error interrupt enable
// |DMA_CCR_HTIE // Bit02: half transfer interrupt enable
// |DMA_CCR_TCIE // Bit01: transfer complete interrupt enable(就是CNDTR变成0后,如果置位该位会触发中断)
// |DMA_CCR_EN // Bit00: DMA使能
;
//步骤x:配置DMA源矩阵,DMA2的通道2对应DMA_MUX的通道9(DMA1占用8个DMA_MUX通道,DMA2占用8个DMA_MUX通道,DMA_MUX通道是从0开始,因此排到DMA2的CH2就是DMA_MUX的CH9)
//对于MUX通道的介绍在下面
DMAMUX1_Channel9->CCR = (24 << DMAMUX_CxCR_DMAREQ_ID_Pos) | (0 << DMAMUX_CxCR_NBREQ_Pos) | (0 << DMAMUX_CxCR_SYNC_ID_Pos);
DMA2_Channel2->CCR |= DMA_CCR_EN; //步骤5:使能DMA
}
至此,USART1接收的数据就可以通过DMA存储到 RS232_Var.RX_Buffer 变量中去了。