简要说一下实验目的:上位机给单片机发送数据,单片机使用串口接收中断接收。在接收中断中,串口向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\n");
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);