今天我们来回顾串口通信实验。
实验目的
通过串口通信实验,学会实验串口来接收和发送数据。
实验内容
在实验开始前先讲一讲串口的复用。我对复用的理解就是,跟前面按键、跑马灯实验不同,前面的IO口我们只是把它们当作正常的IO口输入输出来使用,而串口通信使用的是stm32内部的外设,这时候我们把普通的IO口配置成对应外设功能的IO口的步骤叫做复用。对于复用功能的 IO,我们首先要使能 GPIO 时钟,然后使能复用功能时钟。
首先,第一步就是使能串口的时钟和GPIO的时钟。这使用到了RCC_APB2PeriphClockCmd()这个函数,在上一期我们已经讲过。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|
RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
然后第二步就是串口复位,这一步怎么说呢,其实也可以不用的,但是用了可以保证串口是复位了的,复位总比不复位的好。复位的函数很简单,就是USART_DeInit()函数,参数只有一个,只需要输入相应的串口,实验这里的是串口1,所以就是USART1。
USART_DeInit(USART1); //复位串口1
第三步就是GPIO端口模式的配置,方法同前面的按键、蜂鸣器、跑马灯这些实验一样,在这里就不过多的赘述了。具体代码如下:
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //USART1_TX PA9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //USART1_RX PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA10
第四步就是串口参数的初始化了,串口初始化使用的是USART_Init()函数,配置过程和GPIO初始化函数类似,都是需要定义一个结构体再对结构体的每个变量进行赋值来进行相关的配置。不只是GPIO和串口的初始化,所有的初始化基本都是如此。我们先来看串口初始化函数用到的结构体USART_InitTypeDef。
typedef struct
{
uint32_t USART_BaudRate;
uint16_t USART_WordLength;
uint16_t USART_StopBits;
uint16_t USART_Parity;
uint16_t USART_Mode;
} USART_InitTypeDef;
第一个成员变量是波特率设置;第二个成员变量是字长设置;第三个成员变量是停止位设置;第四个成员变量是奇偶校验位设置;第五个成员变量是串口模式设置。在实验里面我们的设置如下。
USART_InitStructure.USART_BaudRate = bound; //波特率设置,这里的bound由参数传进来
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); //初始化串口
第五步,开启中断并且初始化NVIC,这一步是要开启中断才需要,如果不需要开启接收中断就不需要进行这一步。NVIC初始化就是中断优先级的初始化,使用的是NVIC_Init()函数,使用方法同前面所用过的初始化函数一样。先看优先级初始化的结构体NVIC_InitTypeDef。
typedef struct
{
uint8_t NVIC_IRQChannel;
uint8_t NVIC_IRQChannelPreemptionPriority;
uint8_t NVIC_IRQChannelSubPriority;
FunctionalState NVIC_IRQChannelCmd;
} NVIC_InitTypeDef;
第一个成员变量是中断通道选择;第二个成员变量是中断通道抢占优先级设置;第三个成员变量是中断通道响应优先级(子优先级)设置;第四个成员变量是通道是否使能。在我们的实验中的设置如下。
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //串口中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3; //抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //中断优先级初始化
配置好中断优先级的同时,我们还要在主函数里面设置中断优先级分组,使用的函数是NVIC_PriorityGroupConfig(),中断优先级分组一共4组,看下面的图了解一下。这里我们随便选择中断优先级分组2,因为在这个实验里面中断优先级不影响实验,等到后面需要真正设置中断优先级来管理多个中断的时候再细讲。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
开启中断的函数是USART_ITConfig(), 该函数共三个参数,第一个是选择串口,第二个是选择中断的类型,第三个是选择中断的状态。其中第二个参数的中断类型有如下已经定义在头文件stm32f10x_usart.h里面。
#define USART_IT_PE ((uint16_t)0x0028)
#define USART_IT_TXE ((uint16_t)0x0727)
#define USART_IT_TC ((uint16_t)0x0626)
#define USART_IT_RXNE ((uint16_t)0x0525)
#define USART_IT_IDLE ((uint16_t)0x0424)
#define USART_IT_LBD ((uint16_t)0x0846)
#define USART_IT_CTS ((uint16_t)0x096A)
#define USART_IT_ERR ((uint16_t)0x0060)
#define USART_IT_ORE ((uint16_t)0x0360)
#define USART_IT_NE ((uint16_t)0x0260)
#define USART_IT_FE ((uint16_t)0x0160)
这里我们使能的是接收中断,所以选择的是USART_IT_RXNE。
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启中断
最后,就是使能串口了,使用的是串口使能函数USART_Cmd(),第一个参数是串口,第二个参数是串口的状态。
USART_Cmd(USART1, ENABLE); //使能串口
到这里串口的配置基本上是完成了。
那么接下来就是串口怎么接收和发送数据的问题了,串口接收数据我们使用的函数是USART_ReceiveData()函数,这个函数的使用也很简单,只有一个参数,就是选择我们要接收数据的串口就行了,这里我们使用的是串口1,所以是USART1。注意这个函数是有返回值的,返回值是我们接收到的数据,所以需要创建一个变量来存储接收到的数据。而发送数据的函数是USART_SendData(),它需要两个参数,第一个是选择发送数据的串口,第二个是要发送的数据。
Res = USART_ReceiveData(USART1);
USART_SendData(USART1,Res);
但是,如果前面我们使能了串口接收中断,那么在后面我们还有写中断服务函数,把接收数据的函数放在中断服务函数里面,就可以实现串口接收数据时产生中断,在中断里把接收到的数据取出来。所谓中断服务函数,其实就是产生中断后我们要执行的东西。串口1的中断服务函数是USART1_IRQHandler()。在中断服务函数里面我们可以用USART_GetITStatus()函数来判断接收中断是否发生。如果不使用中断,我们还可以用USART_GetFlagStatus()函数来判断是否接收到数据。
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
Res = USART_ReceiveData(USART1);
USART_SendData(USART1,Res);
}
}
我这里是简单的在中断里接收数据后直接发送出来。我这里只讲使用方法,更具体的更严谨的根据协议来通信需要判断接收到的数据是否是0x0d 0x0a结尾的,可以参考正点原子的例程。