串口通讯(Serial Communication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,其通讯协议可分层为协议层和物理层。物理层规定通信协议中具有机械、电子功能的特性,从而确保原始数据在物理媒体的传播;协议层主要规定通讯逻辑,统一双方的数据打包、解包标准。通俗的讲物理层规定我们用嘴巴还是肢体交流,协议层规定我们用中文还是英文交流。下面分析一下串口通讯协议的物理层和协议层。
1.通讯结构
串口通讯的物理层的主要标准是RS-232标准,其规定了信号的用途、通讯接口及信号的电平标准,其通讯结构如下:
在设备内部信号是以TTL电平标准传输的,设备之间是通过RS-232电平标准传输的,而且TTL电平需要经过电平转换芯片才能转化为RS-232电平,RS-232电平转TTL电平也是如此。
2.电平标准
根据使用的电平标准不同,串口通讯可分为 RS-232标准 及TTL标准,具体标准如下:
在电子电路中常使用TTL的电平标准,但其抗干扰能力较弱,为了增加串口的通讯距离及抗干扰能力,使用RS-232电平标准在设备之间传输信息,经常使用MA3232芯片对TTL电平及RS-232电平进行相互转换。
3.数据传输方式:
A同步:
传输以数据块为核心,在一个数据块内,字符间无间隔,接受发送同步,有sclk时钟,双方sclk连在一起,提供同步
特点:效率高,无间隔
B异步:
以字符为传输单位,每发一个字符,都得发送一个起始位,(告诉对方我开始发了)结束发送停止位。(我发完了)
特点:效率低,间隔任意
4.串口数据包组成
起始位、数据位(8位或者9位)、奇偶校验位(第9位)、起始停止位(1,15,2位)、波特率设置
5.速率类型:
比特:每秒传输的二进制位
波特:每秒传输的码源个数(串口常用)
注:这俩本质上其实是差不多的
6.通信类型
串行:一个一个传输 如:fsmc
特点:占用资源多,速度慢,看干扰强
并行:多个一起传输 如:spi usart
特点:占用资源少,速度快。抗干扰能力弱,距离近
7.通信方式
单工:数据传输只支持数据在一个方向上传输;如:打印机
半双工:允许数据在两个方向上传输。但是,在某一时刻,只允许数据在一个方向上传输,它实际上是一种切换方向的单工通信;它不需要独立的接收端和发送端,两者可以合并一起使用一个端口。如:对讲机,spi
全双工:允许数据同时在两个方向上传输。因此,全双工通信是两个单工通信方式的结合,需要独立的接收端和发送端。如:spi,usart
8.概念补充
1.数据包
串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备得RXD接口,在协议层中规定了数据包的内容,具体包括起始位、主体数据(8位或9位)、校验位以及停止位,通讯的双方必须将数据包的格式约定一致才能正常收发数据。
2.波特率
由于异步通信中没有时钟信号,所以接收双方要约定好波特率,即每秒传输的码元个数,以便对信号进行解码,常见的波特率有4800、9600、115200等。STM32中波特率的设置通过串口初始化结构体来实现。
3.起始和停止信号
数据包的首尾分别是起始位和停止位,数据包的起始信号由一个逻辑0的数据位表示,停止位信号可由0.5、1、1.5、2个逻辑1的数据位表示,双方需约定一致。STM32中起始和停止信号的设置也是通过串口初始化结构体来实现。
4.有效数据
有效数据规定了主题数据的长度,一般为8或9位,其在STM32中也是通过串口初始化结构体来实现的。
5.数据校验
在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验(odd)、偶校验(even)、 0 校验(space)、 1 校验(mark)以及无(noparity)。这些也都可以在串口初始化结构体中实现的。
USART(通用同步异步收发器)是一个串行通信设备,可以灵活地与外部设备进行全双工数据交换。有别于 USART 还有一个UART,它是在 USART 基础上裁剪掉了同步通信功能,只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。USART 在 STM32 应用最多莫过于“打印”程序信息,一般在硬件设计时都会预留一USART 通信接口连接电脑,用于在调试程序是可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、如果出错哪具体哪里出错等等。
void USART_Init //初始化函数
(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_Cmd //串口使能函数
(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig //中断配置函数
(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
void USART_SendData //串口发送函数
(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData //串口接收读取函数
(USART_TypeDef* USARTx);
FlagStatus USART_GetFlagStatus //获取相应的串口标志位
(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus //中断状态位获取
(USART_TypeDef* USARTx, uint16_t USART_IT);
1.配置时钟:gpio,串口,引脚复用
2.配置gpioA9,10结构体
3.配置串口结构体
4.初始化,打开串口
5串口发送函数配置
附上参数.c文件代码
#include "stm32f10x.h"
#include "usart.h"
void usart_init(void)
{
GPIO_InitTypeDef gpioinitStructure;//结构体变量定义,结构体定义要在时钟之前
USART_InitTypeDef usartinitStucture;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO, ENABLE );
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//配置gpio,串口,串口复用时钟
//先配置tx输出引脚io(pa9)
gpioinitStructure.GPIO_Pin =GPIO_Pin_9;//选择引脚
gpioinitStructure.GPIO_Speed=GPIO_Speed_50MHz;//选择电平大小,初始状态无电平
gpioinitStructure.GPIO_Mode =GPIO_Mode_AF_PP;//用推挽输出
GPIO_Init(GPIOA,&gpioinitStructure );//端口初始化
//再配置rx输出引脚io(pa10)
gpioinitStructure.GPIO_Pin =GPIO_Pin_10;
gpioinitStructure.GPIO_Speed=GPIO_Speed_50MHz;//选择电平大小,初始状态无电平
gpioinitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&gpioinitStructure );//再次端口初始化,因为过程相同且都是gpio所以不用再定义
//串口结构体的配置
usartinitStucture.USART_BaudRate =115200;//波特率
usartinitStucture.USART_HardwareFlowControl =USART_HardwareFlowControl_None;//无限流
usartinitStucture.USART_Parity =USART_Mode_Rx | USART_Mode_Tx;//输入输出模式
usartinitStucture.USART_WordLength =USART_Parity_No;//无校验位
usartinitStucture.USART_StopBits =USART_StopBits_1;//一个停止位
usartinitStucture.USART_Mode =USART_WordLength_8b;//有效数据长度8bit
USART_Init(USART1,&usartinitStucture);//串口1初始化
USART_Cmd(USART1,ENABLE);//打开串口1
1.配置时钟:gpio,串口,引脚复用
2.串口中断组选择
3.配置gpioA9,10,串口结构体与初始化
4.串口与中断控制器联系配置
5.打开串口
6.串口发送函数配置
.c文件
#include "stm32f10x.h"
#include "usart.h"
void usart_init(void)//串口发送接收和串口中断配置
{
GPIO_InitTypeDef gpioinitStructure;//结构体变量定义,结构体定义要在时钟之前
USART_InitTypeDef usartinitStucture;
NVIC_InitTypeDef NVICinitStucture;
//1.串口中断组的选择
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2);
//2.打开gpio,引脚复用和串口时钟
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO, ENABLE );
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);//配置gpio,串口,串口复用时钟
//3.先配置tx输出引脚io(pa9)
gpioinitStructure.GPIO_Pin =GPIO_Pin_9;//选择引脚
gpioinitStructure.GPIO_Speed=GPIO_Speed_50MHz;//选择电平大小,初始状态无电平
gpioinitStructure.GPIO_Mode =GPIO_Mode_AF_PP;//选择输出方式,用推挽输出
GPIO_Init(GPIOA,&gpioinitStructure );//端口初始化
//4.再配置rx输出引脚io(pa10)
gpioinitStructure.GPIO_Pin =GPIO_Pin_10;
gpioinitStructure.GPIO_Speed=GPIO_Speed_50MHz;//选择电平大小,初始状态无电平
gpioinitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&gpioinitStructure );//再次端口初始化,因为过程相同且都是gpio所以不用再定义
//5.串口结构体的配置
usartinitStucture.USART_BaudRate =115200;//波特率
usartinitStucture.USART_HardwareFlowControl =USART_HardwareFlowControl_None;//无限流
usartinitStucture.USART_Parity =USART_Mode_Rx | USART_Mode_Tx;//输入输出模式
usartinitStucture.USART_WordLength =USART_Parity_No;//无校验位
usartinitStucture.USART_StopBits =USART_StopBits_1;//一个停止位
usartinitStucture.USART_Mode =USART_WordLength_8b;//有效数据长度8bit
USART_Init(USART1,&usartinitStucture);//串口1初始化
//6.串口和中断控制器联系配置
USART_ITConfig(USART1, USART_IT_RXNE,ENABLE );
//7.打开串口1
USART_Cmd(USART1,ENABLE);
//8.串口中断控制器结构体配置与初始化
NVICinitStucture.NVIC_IRQChannel =USART1_IRQn;//中断通道,选择串口中断
NVICinitStucture.NVIC_IRQChannelPreemptionPriority=1;//配置中断优先级
NVICinitStucture.NVIC_IRQChannelCmd =ENABLE;//使能打开
NVICinitStucture.NVIC_IRQChannelSubPriority =1;//配置中断子优先级
NVIC_Init(&NVICinitStucture);//串口结构体初始化
}
void USARTSendByte(USART_TypeDef* USARTx, uint16_t Data)//串口发送字符函数封装
{
USART_SendData(USARTx, Data);//串口1发送字符0
while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE)==RESET);//如果数据寄存器空标志位为空,则已经成功发送数据跳出
}
void USARTSendstr(USART_TypeDef* USARTx,char*str)//串口发送字符串函数封装
{
uint16_t i=0;
do
{
USARTSendByte(USARTx,*(str+i));//串口发送字符函数封装
i++;
}while(*(str+i)!='\0');
while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)==RESET);//如果数据寄存器空标志位为空,则已经成功发送数据跳出
//检测字符串用tc,检测字符用txc
}
int fputc(int ch,FILE *f)//串口更改printf实现向电脑输出,这边只用单个字符的但函数进行多次调用
{
USART_SendData(USART1,(uint8_t) ch);//串口1发送字符
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);//如果数据寄存器空标志位为空,则已经成功发送数据跳出
return ch;
}
int fget(int ch,FILE *f)//串口更改scanf函数实现向电脑接收一个数据
{
while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==RESET);//如果数据寄存器空标志位为空,判断数据是否接受成功
//接受用rxne,输入用txe
return (int)USART_ReceiveData(USART1);
}
main函数中断服务函数