STM32F10x 的USART 支持DMA 方式,并且在DMA完成后可以产生中断。这对于需要接收或发送大量数据的应用情景是很有帮助的。
在普通的8位或16位单片机中很少有包含DMA控制器的,所以可能许多嵌入式程序员对DMA方式并不熟悉。简单的说,直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。由于无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。
STM32F10x 上具有两个DMA控制器,共有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。
按照STM32参考手册上的说法:“DMA控制器和Cortex™-M3核心共享系统数据总线,执行直接存储器数据传输。当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求会暂停CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。”所以我们不必担心DMA控制器霸占总线资源。CPU总是可以得到一般的总线时间的。
下面我们以USART2 的数据发送为例来介绍DMA。首先由STM32 参考手册的图22可知。USART2 的发送功能可以使用DMA1 的第7 个通道。
利用的DMA 传输的设置工作大体可以分为6步:
1. 在DMA1_CPAR7寄存器中设置外设寄存器的地址。发生外设数据传输请求时,这个地址将是数据传输的源或目标。
2. 在DMA1_CMAR7寄存器中设置数据存储器的地址。发生外设数据传输请求时,传输的数据将从这个地址读出或写入这个地址。
3. 在DMA1_CNDTR7寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减。
4. 在DMA1_CCR7寄存器的PL[1:0]位中设置通道的优先级。
5. 在DMA1_CCR7寄存器中设置数据传输的方向、循环模式、外设和存储器的增量模式、外设和存储器的数据宽度、传输一半产生中断或传输完成产生中断。
6. 设置DMA1_CCR7寄存器的ENABLE位,启动该通道。
第1步对应的代码为:
DMA1_Channel7->CPAR = (uint32_t) &(USART2->DR);
第2步对应的代码如下,其中p_str 是一个指针,指向要传输的数据的首地址:
DMA1_Channel7->CMAR = (uint32_t) p_str;第 3 步对应的代码如下, cnt 为要传输的数据量,串口数据是以字节为传输单位的,所以这里 cnt 就是要传输数据的字节数。
DMA1_Channel7->CNDTR = cnt;第 4 步对应的代码如下, DMA 通道的优先级分为 4 级,分别是: DMA_Priority_VeryHigh 、 DMA_Priority_High 、 DMA_Priority_Medium 、 DMA_Priority_Low 。这里设为最低。
DMA1_Channel7->CCR |= DMA_Priority_Low;第 5 步对应的代码如下 :
DMA1_Channel7->CCR |= DMA_DIR_PeripheralDST | DMA_Mode_Normal | DMA_PeripheralInc_Disable | DMA_MemoryInc_Enable | DMA_PeripheralDataSize_Byte | DMA_MemoryDataSize_Byte | DMA_M2M_Disable;第 6 步对应的代码如下 :
DMA1_Channel7->CCR |= DMA_CCR1_EN;
实际上在这6步之前应该还有2步操作。首先设置DMA之前,要打开DMA的时钟:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);其次,也要设置 USART 使其支持 DMA 方式:
USARTx->CR3 |= USART_DMAReq_Tx;
或者用下面的函数:
USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
一旦启动了DMA通道,它既可响应连到该通道上的外设的DMA请求。 完成一次DMA传输后如何开启下一次传输呢,这个问题困扰了我好几天,最后在STM32参考手册上发现如下的一句话:
当通道配置为非循环模式时,传输结束后(即传输计数变为0)将不再产生DMA操作。要开始新的DMA传输,需要在关闭DMA通道的情况下,在DMA_CNDTRx寄存器中重新写入传输数目。
下面先给一个简单的示例程序:
void USART2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO, ENABLE); /* Configure USART Tx as alternate function push-pull */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOD, &GPIO_InitStructure); /* Configure USART Rx as input floating */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOD, &GPIO_InitStructure); GPIO_PinRemapConfig(GPIO_Remap_USART2, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure); USART_Cmd(USART2, ENABLE); } void UART2_TX_DMA_Init(uint8_t *p_str, uint16_t cnt) { // DMA_InitTypeDef DMA_InitStructure; // DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(USART2->DR); // DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) str; // DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // DMA_InitStructure.DMA_BufferSize = 14; // DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // DMA_InitStructure.DMA_Priority = DMA_Priority_Low; // DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // DMA_Init(DMA1_Channel7, &DMA_InitStructure); DMA1_Channel7->CPAR = (uint32_t) &(USART2->DR); DMA1_Channel7->CMAR = (uint32_t) p_str; DMA1_Channel7->CNDTR = cnt; DMA1_Channel7->CCR = DMA_DIR_PeripheralDST | DMA_Priority_Low | DMA_Mode_Normal | DMA_PeripheralInc_Disable | DMA_MemoryInc_Enable | DMA_PeripheralDataSize_Byte | DMA_MemoryDataSize_Byte | DMA_M2M_Disable; } uint8_t str[] = "Hello World!!!"; void TaskStart(void *pdata) { SysTick_Config(SystemCoreClock/10); USART2_Init(); UART2_TX_DMA_Init(str, 14); for(;;) { LED_Spark(); //DMA_Cmd(DMA1_Channel7, DISABLE); DMA1_Channel7->CCR &= (uint16_t)(~DMA_CCR1_EN); //DMA_Init(DMA1_Channel7, &DMA_InitStructure); DMA1_Channel7->CNDTR = 14; //DMA_Cmd(DMA1_Channel7, ENABLE); DMA1_Channel7->CCR |= DMA_CCR1_EN; //USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE); OSTimeDly(10); } } int main(void) { SystemInit(); LED_Init(); OSInit(); OSTaskCreate(TaskStart, (void *)0, &(TaskStartStk[TASK_STK_SIZE-1]), 1); OSStart(); for(;;) { } }
下面再说说如何在DMA传输完成之后产生中断。当传输一半的数据后,半传输标志(HTIF)被置1,当设置了允许半传输中断位(HTIE)时,将产生一个中断请求。在数据传输结束后,传输完成标志(TCIF)被置1,当设置了允许传输完成中断位(TCIE)时,将产生一个中断请求。
DMA 的CCR 寄存器中有1位TCIE (Transfer complete interrupt enable)
该位由软件设置和清除。
0:禁止TC中断
1:允许TC中断
所以为了使用DMA中断,我们需要下面的代码:
DMA1_Channel7->CCR |= DMA_IT_TC; //Transfer complete interrupt enable另外能否响应中断,还要 NVIC 说了算,所以下面的代码也不能少:
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
下面还是给个简单的示例程序,示例程序中还用到了uCOS的信号量,中断处理函数通过信号量通知Task 完成了DMA传输:
#include "stm32f10x.h" #include "uart.h" #include "led.h" #include "COMMRTOS.H" #include "ucos_ii.h" #define TASK_STK_SIZE 128 OS_STK TaskStartStk[TASK_STK_SIZE]; void USART2_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO, ENABLE); /* Configure USART Tx as alternate function push-pull */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOD, &GPIO_InitStructure); /* Configure USART Rx as input floating */ GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOD, &GPIO_InitStructure); GPIO_PinRemapConfig(GPIO_Remap_USART2, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure); USART_Cmd(USART2, ENABLE); NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void UART2_TX_DMA_Init(uint8_t *p_str, uint16_t cnt) { // DMA_InitTypeDef DMA_InitStructure; // DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(USART2->DR); // DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) str; // DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // DMA_InitStructure.DMA_BufferSize = 14; // DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // DMA_InitStructure.DMA_Priority = DMA_Priority_Low; // DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // DMA_Init(DMA1_Channel7, &DMA_InitStructure); DMA1_Channel7->CPAR = (uint32_t) &(USART2->DR); DMA1_Channel7->CMAR = (uint32_t) p_str; DMA1_Channel7->CNDTR = cnt; DMA1_Channel7->CCR = DMA_DIR_PeripheralDST | DMA_Priority_Low | DMA_Mode_Normal | DMA_PeripheralInc_Disable | DMA_MemoryInc_Enable | DMA_PeripheralDataSize_Byte | DMA_MemoryDataSize_Byte | DMA_M2M_Disable; } OS_EVENT *UART2_DMA_TX_Sem; uint8_t str[] = "Hello World!!!"; void TaskStart(void *pdata) { unsigned char err; SysTick_Config(SystemCoreClock/10); USART2_Init(); USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE); UART2_TX_DMA_Init(str); UART2_DMA_TX_Sem = OSSemCreate(1); for(;;) { LED_Spark(); OSSemPend(UART2_DMA_TX_Sem, 0, &err); //DMA_Cmd(DMA1_Channel7, DISABLE); DMA1_Channel7->CCR &= (uint16_t)(~DMA_CCR1_EN); //DMA_Init(DMA1_Channel7, &DMA_InitStructure); DMA1_Channel7->CNDTR = 14; //DMA_Cmd(DMA1_Channel7, ENABLE); DMA1_Channel7->CCR |= DMA_CCR1_EN; //USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE); OSTimeDly(10); } } int main(void) { SystemInit(); LED_Init(); OSInit(); OSTaskCreate(TaskStart, (void *)0, &(TaskStartStk[TASK_STK_SIZE-1]), 1); OSStart(); for(;;) { } } void DMA1_Channel7_IRQHandler(void) { OS_CPU_SR cpu_sr; OS_ENTER_CRITICAL(); /* Tell uC/OS-II that we are starting an ISR */ OSIntNesting++; OS_EXIT_CRITICAL(); OSSemPost(UART2_DMA_TX_Sem); //UART_PutChar(USART2, '+'); DMA1->IFCR = DMA1_FLAG_TC7; OSIntExit(); }