简要说一下实验目的:上位机给单片机发送数据,单片机使用串口接收中断接收。在接收中断中,串口向DMA控制器发送请求,把内存中的数据发送到串口的DR寄存器(发送到上位机)
1.串口的基本配置配置略过,需要注意的是打开串口的接收中断,编写接收中断函数
串口接收中断的NVIC配置
1 /* 配置USART为中断源 */ 2 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; 3 /* 抢断优先级*/ 4 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; 5 /* 子优先级 */ 6 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 7 /* 使能中断 */ 8 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 9 /* 初始化配置NVIC */ 10 NVIC_Init(&NVIC_InitStructure); 11 USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
串口中断函数
1 void USART1_IRQHandler(void) 2 { 3 if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)//串口接收中断 4 { 5 USART_ClearITPendingBit(DEBUG_USARTx,USART_IT_RXNE); 6 printf("usart "); 7 8 DMA_Cmd(DMA1_Channel4,ENABLE);//串口向dma发送请求 9 DMA_ClearFlag(DMA1_FLAG_GL4); 10 11 //DMA1_Channel4->CNDTR = SENDBUFF_SIZE; 12 13 USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE); 14 } 15 }
2.DMA 配置
1 void USARTx_DMA_Config(void) 2 { 3 DMA_InitTypeDef DMA_InitStructure; 4 NVIC_InitTypeDef NVIC_InitStructure; /* Configure one bit for preemption priority */ 5 6 // 开启DMA时钟 7 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); 8 // 设置DMA源地址:串口数据寄存器地址*/ 9 DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS; 10 // 内存地址(要传输的变量的指针) 11 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; 12 // 方向:从内存到外设 13 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; 14 // 传输大小 15 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; 16 // 外设地址不增 17 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 18 // 内存地址自增 19 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 20 // 外设数据单位 21 DMA_InitStructure.DMA_PeripheralDataSize = 22 DMA_PeripheralDataSize_Byte; 23 // 内存数据单位 24 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 25 // DMA模式,一次或者循环模式 26 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; 27 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 28 // 优先级:中 29 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 30 // 禁止内存到内存的传输 31 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 32 // 配置DMA通道 33 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); 34 35 36 //DMA中断的NVIC 37 // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 38 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn; 39 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 40 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 41 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 42 NVIC_Init(&NVIC_InitStructure); 43 DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); //配置DMA发送完成后产生中断 44 DMA_ITConfig(DMA1_Channel4,DMA_IT_HT,ENABLE); //配置DMA发送完成后产生中断 45 // USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); 46 //DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); 47 // 使能DMA 48 DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE); 49 }
DMA中断函数
1 void DMA1_Channel4_IRQHandler(void) 2 { 3 if(DMA_GetITStatus(DMA1_IT_TC4)==SET) 4 { 5 LED1(1); 6 printf("d"); 7 DMA_ClearITPendingBit(DMA1_IT_TC4); //清除全部中断标志 8 DMA_ClearFlag(DMA1_FLAG_GL4); 9 DMA_Cmd(DMA1_Channel4,DISABLE); 10 // DMA_Cmd(DMA1_Channel4,DISABLE); 11 } 12 if(DMA_GetITStatus(DMA1_IT_HT4)==SET) 13 { 14 LED1(1); 15 printf("h"); 16 DMA_ClearITPendingBit(DMA1_IT_HT4); //清除全部中断标志 17 } 18 }
3.结果
串口接收中断中 向上位机发送“usart”,然后发送DMA请求,把内存中的数据“p”发送到串口(外设,由上位机接收)。DMA半传输完成中断中向上位机发送“h”,DMA全部完成传输中断中向上位机发送“d”。
下图为实验结果
经过多次调试,发现了几个问题
问题1:如果DMA发送了N个数据,上位机接收过程中总会少2个数据。缺少的位置是h和d的位置。
问题2:在串口接收中断中 使用USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE); 串口向DMA发送请求 发送数据时,只会在第一次进入接收中断里发生DMA传输。第二次往后都只会进串口接收中断,不会有DMA传输。DMA设置为normal模式,按照网上的说法,清中断DMA_ClearITPendingBit(DMA1_IT_TC4),关DMA,以及在第二次传输前设置数据大小//DMA_SetCurrDataCounter(DMA1_Channel4,SENDBUFF_SIZE); (或者DMA1_Channel4->CNDTR = SENDBUFF_SIZE;)以及修改串口中断和DMA中断的NVIC优先级都不会有第二次传输。尝试了这些方法都不能正常启动DMA第二次传输。暂时先记录一下,解决了再来写。###:解决了,只用修改一下DMA_Mode即可。DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 之前是DMA_InitStructure.DMA_Mode = DMA_Mode_Normal。感谢一位热心网友。
其他问题和总结(自己总结的,理解的不到位的请大佬指正):
1.打开了串口接收和发送完成中断。在串口接收中断中 使用串口发送数据,有时候会出现接收到一个字符,多次进入接收中断,查找资料,可能存在中断嵌套,后来只在串口接收中断中接收,添加了一个按键,用按键去输出接收到的数据。这个没有问题。(在使用printf时,一定要勾选microlib(MDK))
2.关于串口接收中断和发送中断的问题。经过查找启动文件,发现在NVIC设置中断时只能设置串口中断(不区分时接收还是发送完成)NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;区分接收还是发送是在void USART1_IRQHandler(void) 中字节去判断是哪个标志位。那可能串口的接收中断和发送完成中断的优先级是一样的。
3.关于DMA的中断问题。DMA不是不需要cpu参与吗?为什么还要设置中断。DMA的传输数据过程是不需要cpu的,但是DMA有3个中断:半传输完成DMA1_IT_HT4、全部传输完成DMA1_IT_TC4、传输错误DMA1_IT_TE4。当DMA在传输数据完成一半、全部、出错时,需要向cpu发送中断。一样的,这三个中断也需要设置NVIC。和其他中断是一样的(比如:定时器中断、串口中断)。
打开DMA中的函数:
DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); //配置DMA发送完成后产生中断 DMA_ITConfig(DMA1_Channel4,DMA_IT_HT,ENABLE); //配置DMA发送完成后产生中断
4.DMA1有7个通道,DMA2有5个,每个通道都是固定的和某个外设连接,比如要使用存储器到外设,外设到存储器模式,像上面的串口。要使用串口外设,该怎么选DMA通道,要查中文参考手册。存储器到存储器之间通道不固定,自选任选。
5. 关于设置顺序,每个外设都有对应的通道,那到底是先设置外设呢,还是先设置通道。先设置通道,当需要开启DMA时,用对应外设去发请求。设置通道的函数在DMA库中,发送请求的函数在各个外设的库中。也就是说当外设发送DMA请求时,该外设对应的DMA通道一定是配置好的。
__STM32F10x_DMA_H头文件中:413行的函数 参数是 哪个通道的初始化和 通道初始化结构体
IIC头文件中:537行的函数 参数是 哪个外设的DMA请求和ENABLE
串口头文件中:372行的函数 参数是 哪个外设的DMA请求和ENABLE
6.每个通道都包括多个中断比如:半传输完成DMA1_IT_HT4、全部传输完成DMA1_IT_TC4、传输错误DMA1_IT_TE4。那这3个中断的优先级也不能单独配置。因为每个通道的中函数名是void DMA1_Channel4_IRQHandler(void)这种类型的。从名字上能看出通道上的中断是针对通道的,不针对具体中断。就和串口中断一样,是接收中断还是发送上完成中断,自己在中断函数中判断去。
7.第6条是同一通道中的不同中断,那DMA1有7个通道,不同通道中的中断能设置不同优先级吗?DMA也是一个外设,如果DMA1的7个通道(7个外设)同时要请求DMA,那么DMA先处理哪个通道呢?通过观看通道Init函数,发现DMA_InitStructure结构体中有个变量DMA_InitStructure.DMA_Priority是设置优先级的,优先级可选项有4种:DMA_Priority_VeryHigh、DMA_Priority_High、DMA_Priority_Medium、DMA_Priority_Low。那么就是DMA中的不同通道是可以设置不同的优先级的。如果两个通道优先级一样。那通道号越小优先级越高(通道0>通道1)
8.在使用DMA时只要初始化DMA_InitStructure结构体中的所有变量和NVIC即可
1 DMA_InitTypeDef DMA_InitStructure; 2 NVIC_InitTypeDef NVIC_InitStructure; /* Configure one bit for preemption priority */ 3 4 // 开启DMA时钟 5 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); 6 // 设置DMA源地址:串口数据寄存器地址*/ 7 DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS; 8 // 内存地址(要传输的变量的指针) 9 DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff; 10 // 方向:从内存到外设 11 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; 12 // 传输大小 13 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; 14 // 外设地址不增 15 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 16 // 内存地址自增 17 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 18 // 外设数据单位 19 DMA_InitStructure.DMA_PeripheralDataSize = 20 DMA_PeripheralDataSize_Byte; 21 // 内存数据单位 22 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; 23 // DMA模式,一次或者循环模式 24 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; 25 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 26 // 优先级:中 27 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; 28 // 禁止内存到内存的传输 29 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 30 // 配置DMA通道 31 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); 32 33 34 35 // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); 36 NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn; 37 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; 38 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; 39 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 40 NVIC_Init(&NVIC_InitStructure); 41 DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); //配置DMA发送完成后产生中断 42 DMA_ITConfig(DMA1_Channel4,DMA_IT_HT,ENABLE); //配置DMA发送完成后产生中断 43 // USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); 44 //DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE); 45 // 使能DMA 46 DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);