串口接收/发送有三种模式:中断、轮询、DMA,轮询方式并不推荐,也不经常使用,这里主要看的是中断和DMA方式。
中断:
串口触发中断两种方式:RXNE、IDLE,一种是接收到一个数据接中断一次,一种是等待数据发送完了,产生一个桢中断。
第一种方式明显不好,原因有两个:1.接收一个数据就中断一次去处理数据,数据多的话,程序老是被被打断,这样有可能会产生不好的效果。推荐还是一样采用桢中断;2.容易发生数据丢包,数据收发不完整(出现几率小,但还是尽量避免)。
第一种很多资料都有,就说下第二种,接收一帧数据。(将有一大波代码出现)
初始化串口之类的都大同小异,这里主要是在初始化的最后加上两句话
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); //一个字节中断一次
USART_ITConfig(USART3, USART_IT_IDLE, ENABLE); //一帧结束产生一个中断
void Myuart_Init(unsigned int baudrate)
{
GPIO_InitTypeDef GPIO_InitStructure; //创建引脚初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; //创建中断初始化结构体
USART_InitTypeDef USART_InitStructure; //创建串口初始化结构体
//使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE); //注意这里的RCC_APB2Periph_AFIO,有时候串口发送/接收失败可能是由于这个
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);
//初始化引脚PB10为发送引脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//TX
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//初始化引脚PB11为接收引脚
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11;//RX
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//初始化中断结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //先选择中断分组(总有四种模式)
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; //中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //中断优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_Init(&NVIC_InitStructure);
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(USART3,&USART_InitStructure);
USART_Cmd(USART3, ENABLE); //使能串口
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); //一个字节中断一次
USART_ITConfig(USART3, USART_IT_IDLE, ENABLE); //一帧结束产生一个中断
}
接下来是串口中断服务函数,注意中断函数名字别写错了(不清楚的可以弹到stm32f10x.h中查询,老版本库好像是misc.h)
void USART3_IRQHandler(void)
{
unsigned char clear;
if(USART_GetITStatus(USART3,USART_IT_RXNE)!= RESET) //接收一个字节中断
{
recy_buffer[RxCounter++]=USART3->DR; //从寄存器中读取数据
}
else if(USART_GetFlagStatus(USART3,USART_FLAG_IDLE)!= RESET) //桢中断
{
clear=USART3->SR;
clear=USART3->DR; //清除IDLE位
// USART_ClearFlag(USART1,USART_FLAG_IDLE);//有些人会加上这句话,但其实追踪进去跟上面其实是相同的,可有可无的存在
Rx_flag=1; //标志完成
}
}
中断服务函数中,主要是收集数据,以及判断数据是否接收完成,清除标志位的方式RXNE与IDLE是一样的,只不过IDLE要多一步读取SR寄存器,RXNE清除的方式是读取USART_DR的内容,而IDLE的清除方式是先读SR再读DR方式进行清除。
到这里,串口接收完成了,在函数中的while中,通过判断全局标志位是否为0就可以,记得在接收完数据要将标志位置0。
完整代码:
#define BUF_SIZE 1024
int Rx_flag=0; //全局变量,作为接收完成的标志
int RxCounter=0; //接收数据量
unsigned char recy_buffer[BUF_SIZE]; //接收数据数组
void Myuart_Init(unsigned int baudrate)
{
GPIO_InitTypeDef GPIO_InitStructure; //创建引脚初始化结构体
NVIC_InitTypeDef NVIC_InitStructure; //创建中断初始化结构体
USART_InitTypeDef USART_InitStructure; //创建串口初始化结构体
//使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE); //注意这里的RCC_APB2Periph_AFIO,有时候串口发送/接收失败可能是由于这个
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);
//初始化引脚PB10为发送引脚
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_10;//TX
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//初始化引脚PB11为接收引脚
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11;//RX
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB,&GPIO_InitStructure);
//初始化中断结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); //先选择中断分组(总有四种模式)
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; //中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //中断优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_Init(&NVIC_InitStructure);
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(USART3,&USART_InitStructure);
USART_Cmd(USART3, ENABLE); //使能串口
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); //一个字节中断一次
USART_ITConfig(USART3, USART_IT_IDLE, ENABLE); //一帧结束产生一个中断
}
void USART3_IRQHandler(void)
{
unsigned char clear;
if(USART_GetITStatus(USART3,USART_IT_RXNE)!= RESET)
{
recy_buffer[RxCounter++]=USART3->DR; //从寄存器中读取数据
}
else if(USART_GetFlagStatus(USART3,USART_FLAG_IDLE)!= RESET)
{
clear=USART3->SR;
clear=USART3->DR; //清除IDLE位
Rx_flag=1; //标志完成
}
}
int main()
{
while(1)
{
if(Rx_flag ==1)
{
printf(“ %s ”, recy_buffer);
Rx_flag =0;
RxCounter =0;
}
}
}
已经有人发现其实上面的方法也是每次读取单个字节的时候产生中断的时候都需要CPU参与啊(这里CPU参与的时间较少),所以才有了DMA方式收发数据,不用通过CPU控制,直接由DMA控制器来进行数据的收发,而且可以接收不定长的数据。DMA接收串口初始化是一样的只不过,要多加一个DMA初始化。
步骤:
主函数while运行之前
- 让串口接收一帧数据
- 打开DMA接收;
- 中断服务函数判断一帧数据是否结束,如果接收完成,标记。清除状态位,停止DMA接收,计算接收数据量,最后重新打开DMA接收。
配置:
串口初始化:
代码:
void USART1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA |RCC_APB2Periph_AFIO, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200 ;
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8个数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //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);
USART_Cmd(USART1, ENABLE);
USART_ClearFlag(USART1, USART_FLAG_TC); //清发送完成标志
USART_ITConfig(USART1, USART_IT_RXNE , ENABLE); //使能接收中断
USART_ITConfig(USART1, USART_IT_IDLE , ENABLE); //使能空闲中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组2
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //USART1接收中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //次占优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_DMAConfiguration(); //DMA配置 注意这里接收并不需要注册中断
// NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;
// NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
// NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
// NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// NVIC_Init(&NVIC_InitStructure);
}
DMA配置初始化:
代码
void USART_DMAConfiguration(void)
{
DMA_InitTypeDef DMA_InitStructure;
/* DMA clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA1
/* DMA1 Channel4 (triggered by USART1 Tx event) Config */
DMA_DeInit(DMA1_Channel4);
DMA_InitStructure.DMA_PeripheralBaseAddr = 0x40013804;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART1_SEND_DATA;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = RX_LEN;
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_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel4, &DMA_InitStructure);
DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);
DMA_ITConfig(DMA1_Channel4, DMA_IT_TE, ENABLE);
/* Enable USART1 DMA TX request */
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
DMA_Cmd(DMA1_Channel4, ENABLE);
/* DMA1 Channel5 (triggered by USART1 Rx event) Config */
DMA_DeInit(DMA1_Channel5);
DMA_InitStructure.DMA_PeripheralBaseAddr = 0x40013804; //DMA外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART1_RECEIVE_DATA; // DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 目标源
DMA_InitStructure.DMA_BufferSize = RX_LEN; // 数据缓存大小
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_Circular; // 采集模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//存储器到存储器模式
DMA_Init(DMA1_Channel5, &DMA_InitStructure);
/* Enable USART1 DMA RX request */
USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); //启DMA接收
DMA_Cmd(DMA1_Channel5, ENABLE);
}
这里代码中每一句解释得较详细了,当配置成功后串口接收到的数据,将存放在 数组中,而你需要做的就是在中断中告诉CPU,串口数据已经收发完成,可以进行相关处理,或者在中断中计算此次收发数据量。
中断处理:
代码
void USART1_IRQHandler(void)
{
u8 clear;
uint16_t tmp;
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
while(rx_flag); //数据未发送完成不可接收数据
rx_flag = 1;
DMA_Cmd(DMA1_Channel5, DISABLE);//关闭DMA,防止处理其间有数据
tmp = DMA_GetCurrDataCounter(DMA1_Channel5); //读取剩余字节
count = RX_LEN - tmp; //总长度减掉剩余就等于传输
DMA1_Channel5->CNDTR = RX_LEN;
//读SR后读DR清除Idle
clear = USART1->SR; //清除标志位
clear = USART1->DR;
USART_ClearITPendingBit(USART1, USART_IT_IDLE);
DMA_Cmd(DMA1_Channel5, ENABLE); //重新开启DMA接收
}
}
接下来就是在主函数中的处理
while(1)
{
if(rx_flag)
{
printf("%s",rx_data);
memset(rx_data,0,tmp_count);
rx_flag = 0;
tmp_count = 0;
}
}
使用stm32cubemx开发串口,进行DMA方式进行接收配置是差不多的,前提是要去找到了解操作dma的相关API。串口配置、DMA配置那些都是在工具中完成,道理相同,没什么好讲。
在UART.h先定义一些结构体
在串口配置函数中加入使能空闲中断语句,以及打开DMA接收
在UART.c中编写中断接收函数
发送函数
希望串口一帧发送完成后再发送下一桢,这样才能保证串口不会卡死,那么就需要使用回调函数,在stm32中串口函数是弱定义类型,故我们可以对它进行重写。
接下来就是在中断文件(stm32f10x_it.c)中的串口一中断函数加入中断接收函数
在主函数中只需要判断标志位是否为1即可,别忘了每一次都需要将标志位置0,否则将产生无限死循环,输出同一个内容。
工程链接:https://pan.baidu.com/s/1P9B3P4QhQGHBhXcENtUl0g
提取码:q9ul