二十一、串口DMA接收和中断接收
1. 全局定义
我们先定义一些全局变量来方便后续的使用:
#define USART_MAX_LEN 400
volatile uint16_t usart1_rx_len = 0; //接收帧数据的长度
volatile uint16_t usart1_tx_len = 0; //发送帧数据的长度
volatile uint8_t usart1_recv_end_flag = 0; //帧数据接收完成标志
uint8_t DMA_USART1_RX_BUF[USART_MAX_LEN]={0}; //接收数据缓存
uint8_t DMA_USART1_TX_BUF[USART_MAX_LEN]={0}; //DMA发送缓存
2
3
4
5
6
7
2. 初始化串口
2.1 GPIO配置
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure; // 定义一个结构体变量用于配置GPIO
USART_InitTypeDef USART_InitStructure; // 定义一个结构体变量用于配置USART
USART_DeInit(USART1); // 重置USART1寄存器为默认值,即取消之前的配置
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 使能USART1时钟
// USART1端口配置
GPIO_StructInit(&GPIO_InitStructure); // 将GPIO_InitStructure结构体初始化为默认值
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // 配置引脚9为USART1的TX引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 设置引脚9为复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 设置引脚9的输出速度为50MHz
GPIO_Init(GPIOA, &GPIO_InitStructure); // 将以上配置应用到GPIOA端口上
GPIO_StructInit(&GPIO_InitStructure); // 将GPIO_InitStructure结构体再次初始化为默认值
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; // 配置引脚10为USART1的RX引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置引脚10为浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); // 将以上配置应用到GPIOA端口上
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
代码解释
GPIO_InitTypeDef GPIO_InitStructure;
这行声明了一个名为GPIO_InitStructure的结构体变量,用于配置GPIO端口的参数。USART_InitTypeDef USART_InitStructure;
这行声明了一个名为USART_InitStructure的结构体变量,用于配置USART模块的参数。USART_DeInit(USART1);
这行代码将USART1模块的寄存器重置为默认值,取消之前的配置。RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
这行代码使能了GPIOA端口的时钟。RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
这行代码使能了USART1模块的时钟。
下面的代码块是对USART1端口进行配置:
GPIO_StructInit(&GPIO_InitStructure);
- 这行代码将GPIO_InitStructure结构体初始化为默认值。GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
- 这行代码配置引脚9为USART1的TX引脚。GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
- 这行代码设置引脚9为复用推挽输出。GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- 这行代码设置引脚9的输出速度为50MHz。GPIO_Init(GPIOA, &GPIO_InitStructure);
- 这行代码将以上配置应用到GPIOA端口上。
接着是对引脚10进行配置,同样使用了类似的步骤:
GPIO_StructInit(&GPIO_InitStructure);
- 初始化GPIO_InitStructure结构体为默认值。GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
- 配置引脚10为USART1的RX引脚。GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
- 设置引脚10为浮空输入。GPIO_Init(GPIOA, &GPIO_InitStructure);
- 将以上配置应用到GPIOA端口上。
这些代码主要是在配置STM32微控制器的USART1模块和相关的GPIO端口,以便进行串行通信操作。
2.2 串口配置
设置USART1的波特率、字长(8位数据格式)、停止位(1个停止位)、奇偶校验位(无)、硬件流控(无)以及工作模式(收发模式)。
//USART1 初始化设置
USART_InitStructure.USART_BaudRate = __baud; //波特率设置
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为8位数据格式
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(USART1, &USART_InitStructure); //初始化串口1
2
3
4
5
6
7
8
__baud:是形参,方便更改波特率。
2.3 中断配置
配置USART1的中断优先级,并使能中断。同时,配置DMA2_Stream5(DMA接收)和DMA2_Stream7(DMA发送)的中断和优先级。
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn; //嵌套通道为DMA1_Channel4_IRQn
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级为 2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //响应优先级为 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //通道中断使能
NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel5_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure);
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //开启串口空闲中断
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2.4 DMA接收和发送配置
开启DMA时钟,分别对DMA2_Stream5和DMA2_Stream7进行配置,包括选择通道、设置外设和内存地址、数据传输方向、数据大小、增量模式、传输模式等。最后,使能DMA通道。
/* 配置串口DMA接收*/
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 开启DMA时钟
// RX DMA1 通道5
DMA_InitStructure.DMA_BufferSize = sizeof(DMA_USART1_RX_BUF); // 定义了接收的最大长度
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 串口接收,方向是外设->内存
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 本次是外设到内存,所以关闭内存到内存
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DMA_USART1_RX_BUF;// 内存的基地址,要存储在哪里
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存数据宽度,按照字节存储
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存递增,每次串口收到数据存在内存中,下次收到自动存储在内存的下一个位置
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 正常模式
DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_BASE + 0x04; // 外设的基地址,串口的数据寄存器
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设的数据宽度,按照字节存储,与内存的数据宽度一致
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 接收只有一个数据寄存器 RDR,所以外设地址不递增
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 优先级
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
// TX DMA1 通道4
DMA_InitStructure.DMA_BufferSize = 0; // 发送缓冲区的大小,初始化为0不发送
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 发送是方向是外设到内存,外设作为目的地
DMA_InitStructure.DMA_MemoryBaseAddr =(uint32_t)DMA_USART1_TX_BUF; // 发送内存地址,从哪里发送
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
USART_DMACmd(USART1, USART_DMAReq_Tx | USART_DMAReq_Rx, ENABLE); // 使能DMA串口发送和接受请求
DMA_Cmd(DMA1_Channel5, ENABLE); // 使能接收
DMA_Cmd(DMA1_Channel4, DISABLE); // 禁止发送
DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE); // 使能接收DMA通道中断
USART_Cmd(USART1, ENABLE); //使能串口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
3. 编写串口中断服务函数
当USART1空闲中断发生时,表示接收到一帧数据完成,此时通过DMA获取接收到的数据长度,并将接收到的数据通过DMA发送出去(回显功能)。然后,重新设置DMA接收的数据长度并使能DMA接收。
void USART1_IRQHandler(void) //串口1中断服务程序
{
if(USART_GetITStatus(USART1,USART_IT_IDLE)!=RESET) //空闲中断触发
{
usart1_recv_end_flag = 1; // 接受完成标志位置1
DMA_Cmd(DMA1_Channel5, DISABLE); /* 暂时关闭dma,数据尚未处理 */
usart1_rx_len = USART_MAX_LEN - DMA_GetCurrDataCounter(DMA1_Channel5);/* 获取接收到的数据长度 单位为字节*/
DMA_ClearFlag(DMA1_FLAG_TC5); /* 清DMA标志位 */
DMA_USART1_Send(DMA_USART1_RX_BUF, usart1_rx_len); // 将接收的数据回显
DMA_SetCurrDataCounter(DMA1_Channel5,USART_MAX_LEN); /* 重新赋值计数值,必须大于等于最大可能接收到的数据帧数目 */
DMA_Cmd(DMA1_Channel5, ENABLE); /*打开DMA*/
USART_ReceiveData(USART1); //清除空闲中断标志位(接收函数有清标志位的作用)
}
if(USART_GetFlagStatus(USART1,USART_IT_TC)==RESET) //传输完成中断
{
USART_ClearITPendingBit(USART1, USART_IT_TC); // 清除传输完成中断标志位
USART_ITConfig(USART1,USART_IT_TC,DISABLE);
usart1_rx_len = 0;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
4. 编写DMA接收中断服务函数
// DMA1_Channel5_IRQHandler函数
void DMA1_Channel5_IRQHandler(void)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC5) != RESET) // 等待DMA1_Channel5传输完成
{
DMA_ClearFlag(DMA1_FLAG_TC5); // 清除DMA1_Channel5传输完成标志
DMA_Cmd(DMA1_Channel5, DISABLE); // 关闭使能
USART_ITConfig(USART1, USART_IT_TC, ENABLE); // 打开串口发送完成中断
}
}
2
3
4
5
6
7
8
9
10
函数解释
void DMA1_Channel5_IRQHandler(void)
这是一个无返回值的函数,用于处理DMA1通道5的中断。在STM32中,中断服务函数的命名规则遵循特定的格式,以便系统能够正确识别和调用它们。if(DMA_GetFlagStatus(DMA1_FLAG_TC5) != RESET)
这行代码检查DMA1通道5传输完成标志位是否被置位,即检查数据传输是否完成。{ DMA_ClearFlag(DMA1_FLAG_TC5); }
如果传输已经完成,那么将清除DMA1通道5传输完成标志位,以便为下一次传输做准备。DMA_Cmd(DMA1_Channel5, DISABLE);
这行代码用于关闭DMA1通道5,即禁用DMA1通道5,防止不必要的��复传输。USART_ITConfig(USART1, USART_IT_TC, ENABLE);
最后,这行代码打开了USART1的发送完成中断,允许在数据传输完成时触发相应的中断处理。
5. 发送函数
将要发送的数据复制到DMA_USART1_TX_BUF缓冲区,设置DMA传输的数据长度,并使能DMA1_Channel4开始数据传输。
void DMA_USART1_Send(uint8_t *data, uint16_t size)
{
memcpy(DMA_USART1_TX_BUF, data, size); // 复制数据到DMA发送缓存区
DMA_Cmd(DMA1_Channel4, DISABLE); // 确保DMA可以被设置
DMA_SetCurrDataCounter(DMA1_Channel4, size); // 设置数据传输长度
DMA_Cmd(DMA1_Channel4, ENABLE); // 打开DMA通道,开始发送
}
2
3
4
5
6
7
6. 实验现象
关于这一章节的代码百度网盘下载,在立创·STM32F103C8T6开发板资料/第03章软件资料/代码例程/010串口中断DMA接收二合一。
烧写我们的代码之后,在串口助手中发送对应的数据,将会在串口助手中显示出来。