通讯
按数据传送方式,通讯可分为串行通讯与并行通讯;按数据通讯方向,通讯可分为全双工、半双工和单工通讯;按通讯的数据同步方式,通讯可分为同步和异步。衡量通讯性能的一个非常重要的参数就是通讯速率,以比特率表示,每秒传输的二进制数。波特率表示每秒传输多少个码元,用时间间隔相同的符号表示一个码元。二者区别如下
如常见的通讯传输中,用0V表示数字0,5V表示数字1,那么一个码元可以表示两种状态0和1,所以一个码元等于一个二进制比特位, 此时波特率的大小与比特率一致;如果在通讯传输中,有0V、2V、4V以及6V分别表示二进制数00、01、10、11, 那么每个码元可以表示四种状态,即两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率的一半。
串行通信一般是以帧格式传输数据,每帧都包含起始位(固定为低电平)、数据位、停止位(固定为高电平),还可能有校验位。
一、USART
USART(全双工通用同步/异步串行收发模块)是一个串行通信设备,可灵活地与外部设备进行全双工数据交换。一般在STM32硬件设计时都会预留一个USART通信接口连接电脑,以确保程序运行是否正确。C8T6有三个USART接口,其中USART1位于APB2总线,最大频率为72MHz;其余两个位于APB1总线,最大频率为36MHz。USART结构框图如下
功能引脚
- TX
发送数据输出引脚 - RX
接收数据输入引脚 - SW_RX
数据接收引脚,只用于单线和智能卡模式,属于内部引脚 - nRTS
(Request To Send)发送请求,n表示低电平有效。若是低电平,表示USART准备好接收数据,只适用于硬件流控制。一般不用 - nCTS
(Clear To Send)清除发送,n表示低电平有效。若是高电平,在当前数据传输结束时阻断下一次的数据发送,只适用于硬件流控制。一般不用 - SCLK
时钟输出引脚,仅适用于同步模式。
USART基本结构如下
简单的双向串口通信有两根通信线(TX与RX),设备之间交叉连接。接线图如下
一个数据帧是否设置校验位可选造成8或9位。如果使用了奇校验,则9位数据中会出现奇数个1;如果使用了偶校验,则9位数据中会出现偶数个1。帧的停止位可自行配置为1、2、0.5、1.5个停止位。如图所示
-
发送器
发送器根据是否设置校验位(即USART_CR1的M位)发送8位或9位的数据字,USART_CR1寄存器的发送使能位TE置1时,启动数据发送,发送移位寄存器的数据会在TX引脚输出,低位在前、高位在后,就意味着接收端先接收低位数据,后接收高位数据。使能位TE被激活后将发送一个空闲帧,然后就可以往USART_DR寄存器写入要发送的数据,在写入最后一个数据后,需要等待USART_SR的TC位为1,表示数据传输完成,如果USART_CR1寄存器的TCIE位置1,将产生中断。 -
接收器
前提保证和发送器相同的波特率,并且要求每次采样的位置正好处于每一位的正中间,同时还要对噪声有一定的判断能力。根据USART_CR1的M位接收8或9位数据字,在辨认出一个特殊的采样序列(检测下降沿)后,就可认为侦测到一个起始位,若该序列不完整,那么接收端将退出起始位侦测并回到空闲状态等待下降沿。字符帧起始位的检测如图
由图可知,在检测到下降沿后,对信号线进行采样,为避免噪声对采样的干扰,在下降沿之后的第3、5、7位进行第一次采样和8、9、10位进行第两次采样,都要求两批采样中至少有2个0。数据的最低有效位首先被接收,最后接收最高有效位。根据NE状态判断数据有效性。 -
分数波特率
波特率指数据信号对载波的调制速率,用单位时间内载波调制状态改变次数来表示。Tx / Rx 波特率 = = fCK/(16* USARTDIV ),fCK是外设的时钟即72MHz,USARTDIV是一个存放在波特率寄存器(USART_BRR)的一个无符号定点数,其中DIV_Mantissa[11:0]位定义USARTDIV的整数部分,DIV_Fraction[3:0]位定义USARTDIV的小数部分。 -
数据模式
- HEX模式:以原始数据的形式显示
- 字符模式:以原始数据编码后的形式显示
- MicroLIB
MicroLIB是Keil为嵌入式平台优化的一个精简库,经过深度优化,代码大小有明显优势。
需要对printf
进行重定向,将printf
打印的东西输出到串口。在串口模块.c
文件加上头文件#include "stdio.h"
,
二、实验案例
- 单字节串口发送
使用USART1的TX引脚向电脑发送数据,编程步骤如下
- 开启USART时钟和GPIO引脚时钟
- 配置GPIO引脚
- 配置USART
- 使能USART
- 功能函数编写
void Serial_Init(void)
{
//开启USART1时钟和GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//配置PA9引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//采用复用推完输出
//一般RX配置是浮空输入或上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置USART1
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;//波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制,这里不选用
USART_InitStructure.USART_Mode = USART_Mode_Tx;//作输出
USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,这里不选用
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长
USART_Init(USART1, &USART_InitStructure);
//开启USART1
USART_Cmd(USART1, ENABLE);
}
//发送单个字节数据函数编写
void Serial_SendByte(uint8_t Byte)
{ //调用发送函数
USART_SendData(USART1, Byte);//数据写入TDR,此处还等TDR的数据转移到移位寄存器
//此处作等待标志位之用,即发送数据寄存器空标志位,标志位会自动置0
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) != SET);
}
- 单字节串口接收
对于串口接收来说,可以使用查询和中断两种方法。
- 使用查询
查询不需要配置中断,在主函数中不断判断RXNE(读数据寄存器非空)标志位。当RDR移位寄存器中的数据被转移到USART_DR寄存器中,该位被硬件置位;若USART_CR1寄存器中的RXNEIE为1,则产生中断,对USART_DR的读操作可将该位清零,意思就是说读DR操作可自动清零标志位。此方法只适用于实现简单的功能
代码如下
//使用查询
void Serial_Init(void)
{
//PA9是USART1_TX,PA10是USART1_RX
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//配置PA9引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置PA10引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置USART1
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
}
//数据接收函数
uint8_t RxData;
//不断判断RXNE标志位
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
{
RxData = USART_ReceiveData(USART1);
OLED_ShowHexNum(2, 1, RxData, 5);
}
- 使用中断
使用中断,可以使得程序更加灵活,代码如下
void Serial_Init(void)
{
//PA9是USART1_TX,PA10是USART1_RX
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//配置PA9引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置PA10引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//配置USART1
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStructure);
//开启RXNE标志位到NVIC的输出
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //选择接收数据寄存器非空中断
//配置USART中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //选择第二组中断
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);
}
uint8_t Serial_RxData;//读取的数据
uint8_t Serial_RxFlag;//读取标志位
//实现读后自动清除的功能
uint8_t Serial_GetRxFlag(void)
{
if (Serial_RxFlag == 1)
{
Serial_RxFlag = 0;
return 1;
}
return 0;
}
//读DR会自动清零
uint8_t Serial_GetRxData(void)
{
return Serial_RxData;//返回接收到的数据
}
//中断函数
void USART1_IRQHandler(void)
{ //先判断标志位
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART1); //存储接收的数据
Serial_RxFlag = 1; //通过扫描标志位接收数据
//如果读取了DR可以自动清除标志位
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
//读数据函数如下
if(Serial_GetRxFlag() == 1)//始终是对标志位进行扫描判断
{
RxData = Serial_GetRxData();
OLED_ShowHexNum(2, 1, RxData, 5);
}