- 存储器 ----> 存储器
- 存储器 ----> 外设
- 外设 ----> 存储器
一、DMA请求
STM32F103有2个 DMA 控制器,DMA1有7个通道,DMA2有5个通道。
如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA 请求,DMA 收到请求信号之后,控制器会给外设一个应答信号,当外设应答且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。请求是有优先级的,DMA控制器会根据优先级的高低来处理请求。
DMA1 请求列表
示例:DMA1的通道4可以用来处理USART1_TX 和 TIM4_CH2等
DMA2 请求列表
二、仲裁器
三、DMA传输方式
- DMA_Mode_Normal(正常模式): 一次DMA数据传输完后,停止传送 ,即只传输一次
- DMA_Mode_Circular(循环传输模式):当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式
四、中断
五、存储器 ----> 存储器 case
将数组srcBuf的数据复制到desBuff数组中。并通过串口打印显示。
主要代码如下:
#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
};
// 目标数组
uint32_t desBuf[BUF_SIZE];
int main(void)
{
int i;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
// 启动DMA传输一直等到DMA_FLAG_TC2 = 1
HAL_DMA_Start(&hdma_memtomem_dma1_channel2,(uint32_t)srcBuf, (uint32_t)desBuf, sizeof(uint32_t) * BUF_SIZE);
// 这里配置的是通道2,要取得通道2的TC状态
while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel2, DMA_FLAG_TC2) == RESET);
// 通过串口打印数组内容
for (i = 0; i < BUF_SIZE; i++)
printf("Buf[%d] = %X\r\n", i, desBuf[i]);
while (1) {
}
}
5.1、MX_DMA_Init
代码如下,就是配置了一些DMA的初始化
void MX_DMA_Init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
hdma_memtomem_dma1_channel2.Instance = DMA1_Channel2;
hdma_memtomem_dma1_channel2.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem_dma1_channel2.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem_dma1_channel2.Init.MemInc = DMA_MINC_ENABLE;
hdma_memtomem_dma1_channel2.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_memtomem_dma1_channel2.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_memtomem_dma1_channel2.Init.Mode = DMA_NORMAL;
hdma_memtomem_dma1_channel2.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_memtomem_dma1_channel2) != HAL_OK)
{
Error_Handler();
}
}
DMA1_Channel2: DMA1 通道2的外设地址,
Direction: 传输方向
- DMA_MEMORY_TO_MEMORY
- DMA_PERIPH_TO_MEMORY
- DMA_MEMORY_TO_PERIPH
这里设置的是DMA_MEMORY_TO_PERIPH,操作的是寄存器CCR2的位14
PeriphInc:外设是否自增,操作的是CCR2的位6
这里外设是存储器DMA_PINC_ENABLE
MemInc:外设是否自增,操作的是CCR2的位7
PeriphDataAlignment:自增时的单位,操作的是CCR2的位8、9 位
MemDataAlignment: 自增时的单位,操作的是CCR2的位10、11 位,图同上
Mode:是否循环执行DMA,操作的是CCR2的位5
Priority:优先级操作的是CCR2的位12、13
- DMA_PRIORITY_LOW
- DMA_PRIORITY_MEDIUM
- DMA_PRIORITY_HIGH
- DMA_PRIORITY_VERY_HIGH
5.2、HAL_DMA_Init
代码精简大致如下:
uint32_t tmp = 0U;
if(hdma == NULL)
{
return HAL_ERROR;
}
hdma->ChannelIndex = (((uint32_t)hdma->Instance - (uint32_t)DMA1_Channel1) / ((uint32_t)DMA1_Channel2 - (uint32_t)DMA1_Channel1)) << 2;
hdma->DmaBaseAddress = DMA1;
hdma->State = HAL_DMA_STATE_BUSY;
tmp = hdma->Instance->CCR;
tmp &= ((uint32_t)~(DMA_CCR_PL | DMA_CCR_MSIZE | DMA_CCR_PSIZE | \
DMA_CCR_MINC | DMA_CCR_PINC | DMA_CCR_CIRC | \
DMA_CCR_DIR));
tmp |= hdma->Init.Direction |
hdma->Init.PeriphInc | hdma->Init.MemInc |
hdma->Init.PeriphDataAlignment | hdma->Init.MemDataAlignment |
hdma->Init.Mode | hdma->Init.Priority;
hdma->Instance->CCR = tmp;
hdma->ErrorCode = HAL_DMA_ERROR_NONE;
hdma->State = HAL_DMA_STATE_READY;
hdma->Lock = HAL_UNLOCKED;
return HAL_OK;
其中channgelindex的计算是如下
hdma->ChannelIndex = (((uint32_t)hdma->Instance - (uint32_t)DMA1_Channel1) / ((uint32_t)DMA1_Channel2 - (uint32_t)DMA1_Channel1)) << 2;
本案例用的是通道2,所以Instance = DMA1_Channel2, 最终结果是1 << 2, 即4 = 0x100,这里用位表示的通道,而不是0 1 2 3 等表示。
5.3、HAL_DMA_Start
精简源码如下:
HAL_StatusTypeDef HAL_DMA_Start(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
HAL_StatusTypeDef status = HAL_OK;
__HAL_LOCK(hdma);
if(HAL_DMA_STATE_READY == hdma->State)
{
hdma->State = HAL_DMA_STATE_BUSY;
hdma->ErrorCode = HAL_DMA_ERROR_NONE;
__HAL_DMA_DISABLE(hdma);
DMA_SetConfig(hdma, SrcAddress, DstAddress, DataLength);
__HAL_DMA_ENABLE(hdma);
}
else
{
__HAL_UNLOCK(hdma);
status = HAL_BUSY;
}
return status;
}
这里的操作主要是配置dma的状态包括State,ErrorCode等,最主要的是DMA_SetConfig 寄存器的配置。
State:类型是HAL_DMA_StateTypeDef,有四种状态
- HAL_DMA_STATE_RESET
- HAL_DMA_STATE_READY
- HAL_DMA_STATE_BUSY
- HAL_DMA_STATE_TIMEOUT
ErrorCode:是各个宏,有5个宏
- HAL_DMA_ERROR_NONE:没有错误
- HAL_DMA_ERROR_TE: 发送数据错误
- HAL_DMA_ERROR_NO_XFER:无持续的传输
- HAL_DMA_ERROR_TIMEOUT:超时错误
- HAL_DMA_ERROR_NOT_SUPPORTED:不支持的模式
5.4、DMA_SetConfig
精简源码如下:
static void DMA_SetConfig(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << hdma->ChannelIndex);
hdma->Instance->CNDTR = DataLength;
if((hdma->Init.Direction) == DMA_MEMORY_TO_PERIPH)
{
hdma->Instance->CPAR = DstAddress;
hdma->Instance->CMAR = SrcAddress;
}
else
{
hdma->Instance->CPAR = SrcAddress;
hdma->Instance->CMAR = DstAddress;
}
}
hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << hdma->ChannelIndex);
1、(DMA_ISR_GIF1 << hdma->ChannelIndex) 开启是的通道二的全局中断
2、把值传给IFCR寄存器,设置CGIF2为1,意义清除DMA_ISR对应的GIF,TEIF,HTIF,TCIF标志
if((hdma->Init.Direction) == DMA_MEMORY_TO_PERIPH)
{
hdma->Instance->CPAR = DstAddress;
hdma->Instance->CMAR = SrcAddress;
}
else
{
hdma->Instance->CPAR = SrcAddress;
hdma->Instance->CMAR = DstAddress;
1、配置CPAR(外设地址寄存器),CMAR(内存地址寄存器)
2、根据Direction(这里是CCR2寄存器的DIR位)的方向来配置,
如果是内存到外设,CMAR就是源头,CPAR就是目的
如果是其它,那么 CMAR就是目的,CPAR就是源头
5.5、__HAL_DMA_GET_FLAG
while(__HAL_DMA_GET_FLAG(&hdma_memtomem_dma1_channel2, DMA_FLAG_TC2) == RESET);
#define __HAL_DMA_GET_FLAG(__HANDLE__, __FLAG__) (DMA1->ISR & (__FLAG__))
这里获取的是TCIF2,如果数据未传输完成TCIF2 = 0, 否则等于1
获取ISR寄存器中通道2中的TCIF2
六、存储器 ----> 外设 case
将内存中的数据通过串口DMA打印
int main(void)
{
int i;
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
for (i = 0; i < BUF_SIZE; i++)
sendBuf[i] = 'A';
// 将数据通过串口DMA发送
HAL_UART_Transmit_DMA(&huart1, sendBuf, BUF_SIZE);
while (1)
{
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
HAL_Delay(100);
}
}
HAL_UART_Transmit:普通串口打印方法
HAL_UART_Transmit_DMA:DMA串口打印方法
七、外设 ----> 存储器 case
精简源码如下:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
__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);
}
}
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart1);
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
}
}
#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__) ((((__INTERRUPT__) >> 28U) == UART_CR1_REG_INDEX)? ((__HANDLE__)->Instance->CR1 |= ((__INTERRUPT__) & UART_IT_MASK)): \
(((__INTERRUPT__) >> 28U) == UART_CR2_REG_INDEX)? ((__HANDLE__)->Instance->CR2 |= ((__INTERRUPT__) & UART_IT_MASK)): \
((__HANDLE__)->Instance->CR3 |= ((__INTERRUPT__) & UART_IT_MASK)))
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 使能IDLE空闲中断
__HAL_UART_ENABLE_IT是一个宏,里面有个 >> 28 ,是因为中断标识包括UART_IT_IDLE
在定义的时候<< 28了,所以在使用的时候需要 >> 28,第28 29位是CR1,CR2,CR3,再和UART_IT_MASK进行操作就得到了CRx 上的某一个中断标识位
另外USART_CRx寄存器中的16-31位是保留位,
HAL_UART_Receive_IT:普通串口接收中断
HAL_UART_Receive_DMA: DMA串口接收中断
__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->SR & (__FLAG__)) == (__FLAG__))
得到串口SR寄存器中的IDLE位(只读)
HAL_UART_DMAStop(&huart1); // 停止DMA传输,防止干扰
/*****处理数据先关闭DMA,处理完成之后再打开***/HAL_UART_Receive_DMA(&huart1, rcvBuf, BUF_SIZE);//开启DMA
__HAL_UART_CLEAR_IDLEFLAG的最终定义如下
#define __HAL_UART_CLEAR_PEFLAG(__HANDLE__) \
do{ \
__IO uint32_t tmpreg = 0x00U; \
tmpreg = (__HANDLE__)->Instance->SR; \
tmpreg = (__HANDLE__)->Instance->DR; \
UNUSED(tmpreg); \
} while(0U)
这个宏的意思是清楚标志位,只看到它依次读了SR,DR寄存器,并没有清除??? 是不是很奇怪,这时就需要手册了。
手册寄存器USART_SR中对IDLE位有这样一个描述
先读SR,再读DR 就可以清除标识位。