Good Good Study,Day Day Up!!!
今天写一下关于串口通信的内容,实现的是串口助手向单片机发送信息,并显示到LCD上。
明确几点:
来看原理图:
可以看到有两个串口,那么我们再来分别看一下这两个串口在电路图中是怎么连接的:
我们可以看到串口1的通信引脚是接在RS232的接口上的,这种接口一般不是很常用,我们经常使用的接口应该都是USB接口,所以我们不用串口1来进行通信。再来看一下串口2:
串口2连接USB接口,所以我们利用串口2(USART2)来进行串口通信,那么我们就用到PA2(数据发送引脚)和PA3(数据接收引脚)这两个引脚。
下面我们来看一下USART的配置流程:使能时钟+配置相关引脚->配置时钟相关参数->时钟中断配置->时钟使能+中断使能
我们来按照步骤一步一步配置:
//使能GPIOA和USART2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA ,ENABLE);
//RXD-PA3 设置为浮空模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//TXD-PA2 设置为推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART2的配置
USART_InitStructure.USART_BaudRate = 9600;//设置波特率为9600
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(USART2, &USART_InitStructure);//初始化USART2
做几点说明:
//USART2的中断优先级配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;//选择USART2通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//响应优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能通道
NVIC_Init(&NVIC_InitStructure);//调用初始化函数
USART_Cmd(USART2, ENABLE);//开启USART2
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启USART2接收中断
这里要注意一点:这里只能使能串口接收中断!!!发送中断在数据发送前使能,数据发送完毕后就要关闭!!!
总的串口初始化程序:
void USART2_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
//开启GPIOA和USART2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA ,ENABLE);
//USART2的中断优先级配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;//选择USART2通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//响应优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能通道
NVIC_Init(&NVIC_InitStructure);//调用初始化函数
//RXD-PA3 设置为浮空模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//TXD-PA2 设置为推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART2的配置
USART_InitStructure.USART_BaudRate = 9600;//设置波特率为9600
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(USART2, &USART_InitStructure);//初始化USART2
USART_Cmd(USART2, ENABLE);//开启USART2
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启USART2接收中断
}
初始化串口之后,我们还要写一个串口发送数据的函数:
void USART2_SendString(u8 *str)
{
u8 index = 0;
while(str[index++] != 0)
{
USART_SendData(USART2, str[index-1]);//发送一个字节的数据,注意这里是index-1!,如果是index则不能显示第一个字符!!!
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == 0);//数据发送结束(TXE为1)则跳出循环
index++;
}
}
这里涉及到了标志位的问题,我们怎么判断一帧数据发送完了呢?
我们先来了解一下发送数据的流程:内部总线->TDR数据寄存器->发送移位寄存器->TXD引脚输出;这里的发送移位寄存器可以理解为一个数据的中转站,一帧的数据会先由数据寄存器送达中转站,然后再输出,这时我们的TDR寄存器是空的,那么就会产生一个标志位(TXE = 1),表明我们可以发送下一帧的数据,这个TXE就是硬件设置的TXD为空的标志,我们也可以看一下这个引脚的功能:
读数据的流程与发送数据的流程相类似,用到的标志位是RXNE,从下面的图片中我们可以看到当RXNE为1时,表示数据接收到了,那么我们在写接收数据的函数时肯定也是在这种情况下进行操作的;
明白了上面的内容,我们也就能理解下面这行代码的意思了:
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == 0);//数据发送结束(TXE为1)则跳出循环
当TXE为0时,表明这一帧数据没有发送完,那么我们就一直在while循环内执行,直到这一帧数据完全发送到移位寄存器(即TXE = 1);
发送完数据我们肯定还要接收数据啊,接收数据是利用串口中断来接收的,所以我们还要在stm32f10x_it.c文件中编写一个USART中断服务函数:
NOTICE!!!stm32的中断服务函数是有特定的名称的,我们编写的中断服务函数必须与这个名称一致才可以,否则就会报错!!!我们用到的是串口2,所以要编写串口2的中断服务函数:(void USART2_IRQHandler(void))
void USART2_IRQHandler(void)
{
u16 tmp;
if(USART_GetITStatus(USART2, USART_IT_RXNE) == 1)//检测接收中断标志位,RXNE为1表示接收到了数据
{
USART_ClearITPendingBit(USART2, USART_IT_RXNE);//清除中断标志位(即清除RXNE)
tmp = USART_ReceiveData(USART2);//读取串口2的数据
if(tmp == '\n')//‘/n’表示本次读取结束
{
RxdBuf[RxdCnt-1] = 0;//避免\r显示在LCD发生的乱码
RxdCnt = 0;//读取结束清RxdCnt
RxdOver = 1;//本次数据读取结束标志置1
USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);//接收完毕后关闭,防止处理过程发生干扰。
}
else
{
RxdBuf[RxdCnt++] = tmp;//将读取到的数据存到RXDBuf中
}
}
}
主函数:
#include "stm32f10x.h"
#include "lcd.h"
#include "usart2.h"
u32 TimingDelay = 0;
u8 RxdCnt = 0;//用来计数读取到的字节数
u8 RxdOver = 0;//读取数据是否结束:0-未结束,1-结束
u8 RxdBuf[20];//存储读到的数据
void Delay_Ms(u32 nTime);
//Main Body
int main(void)
{
u8 i;
STM3210B_LCD_Init();
LCD_Clear(Blue);
LCD_SetBackColor(Blue);
LCD_SetTextColor(White);
SysTick_Config(SystemCoreClock/1000);
USART2_Init();//串口初始化
while(1)
{
if(RxdOver)
{
RxdOver = 0;
LCD_ClearLine(Line5);//清除LCD的对应行
LCD_DisplayStringLine(Line5, RxdBuf);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//接收的数据处理完毕后打开接收中断
for(i=0; i<20; i++)//清空缓冲区
RxdBuf[i] = 0;
}
}
}
//
void Delay_Ms(u32 nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
这段代码实现的功能是:利用串口助手向单片机发送数据,数据可显示到LCD上,关于LCD的部分,我是直接用蓝桥嵌入式里面的液晶显示例程来建工程的,emmm,LCD的部分我暂时还没有学到。