一、串口通信协议
1、物理层
串口通信的物理层有很多标准及变种
1、RS-232标准
2、USB转串口
3、原生串口到串口
使用RS-232标准的串口设备间常见的通讯结构如下:
两通讯设备的“DB9接口”之间通过串口信号线建立连接,串口信号线使用“RS-232标准”传输数据信号。由于RS-232电平标准的信号不能直接被控制器直接识别,所以该信号会经过“电平转换芯片”转换成控制器能够识别的“TTL标准”电平信号,才能实现通讯。
(1)电平标准
根据通讯使用的电平标准不同,串口通讯可分为TTL标准及RS-232标准。
常见的电子电路中常使用TTL电平标准,理想状态下,使用5V表示二进制逻辑1,0V表示二进制逻辑0;而RS-232电平标准为了增加串口通讯的远距离传输以及抗干扰能力(对抗衰减),使用-15V表示逻辑1,+15V表示逻辑0。下图为分别用两种不同的电平标准表示同一信号。
因为控制器一般使用TTL电平标准,所以常常会使用MAX3232芯片对TTL及RS-232电平的信 号进行互相转换。
(2)RS-232信号线
接线口以针式引出信号线的称为公头,以孔式引出信号线的称为母头。
DB9接口中的公头及母头的各个引脚的标准信号线接法如下:
DB9信号线说明
在目前的其它工业控制使用的串口通讯中,一般只使用RXD、TXD以及GND三条信号线,直 接传输数据信号,而RTS、CTS、DSR、DTR及DCD信号都被裁剪掉了。
2、协议层
串口通讯的数据包由发送端的TXD接口传输到接收端的RXD接口。在串口通信的协议层中,规定了数据包的内容,由起始位,主体数据、校验位以及停止位组成,通信双方的数据包格式要约定一致方能正常通信。下图为串口数据包基本组成。
(1)波特率
在串口异步通信中由于没有时钟信号,所以两个设备之间需要约定好波特率,即每个码元的长度,常见的波特率为4800、9600、115200等。
(2)起始和停止信号
串口通信的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑0的数据位表示,而数据包的停止信号可由0.5、1、1.5或2个逻辑1的数据位表示,只要双方约定一致即可。
起始位固定为低电平
停止位固定为高电平
(3)有效数据
在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度 常被约定为5、6、7或8位长。
(4)数据校验
在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可在传输过程中加上校验位解决这个问题。校验方法有奇校验、偶校验、0校验、1校验以及无校验。
奇校验要求有效数据和校验位中“1”的个数为奇数;
偶校验要求帧数据和校验位中“1”的个数为偶数;
0 校验是不管有效数据中的内容是什么,校验位总为“0”,1校验是校验位总为“1”。
二、USART
USART是通用同步异步收发器的简称,它是一个串行通信设备,UART是在USART的基础上裁剪了同步通信功能,只有异步通信。USART支持同步单向通信和半双工单线通信;USART支持使用DMA,可实现高速数据通信。
USART 在STM32 应用最多莫过于“打印”程序信息,一般在硬件设计时都会预留一个USART 通信接口连接电脑,用于在调试程序是可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、如果出错哪具体哪里出错等等。
1、USART功能
(1)引脚功能
TX:发送数据输出引脚;
RX:接收数据输入引脚;
SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚;
nRTS:请求以发送(Request To Send),n表示低电平有效;该引脚只适用于硬件流控制。
如果使能RTS流控制
低电平:USART接收器准备好接收新数据
高电平:接收寄存器已满
nCTS:清除以发送(Clear To Send),n表示低电平有效;该引脚只适用于硬件流控制。
如果使能CTS流控制
低电平:USART发送器可以发送数据
高电平:发送完当前数据后停止发送
SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。
STM32F103C8T6系统控制器有三个USART和两个UART,其中USART1和时钟来源于APB2总线时钟,其最大频率为72MHz,其他四个的时钟来源于APB1总线时钟,其最大频率为36MHz。 UART只是异步传输功能,所以没有SCLK、nCTS和nRTS功能引脚。
(2)数据寄存器
USART数据寄存器(USART_DR)只有低9位有效,并且第9位数据是否有效取决于USART控制寄存器1(USART_CR1)的M位设置,当M位为0时表示8位数据字长,当M位为1时表示9位数据字长。
USART_DR 包含了已发送的数据或者接收到的数据。USART_DR实际是包含了两个寄存器,一 个专门用于发送的可写TDR,一个专门用于接收的可读RDR。
TDR和RDR都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的,发送时把 TDR 内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收时把接收到的 每一位顺序保存在接收移位寄存器内然后才转移到RDR。
(3)控制器
USART 有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。使用 USART 之前需要向USART_CR1寄存器的UE位置1使能USART,UE位用来开启供给给串口的时钟。
发送或者接收数据字长可选8位或9位,由USART_CR1的M位控制。
(4)发送器
当USART_CR1寄存器的发送使能位TE置1时,启动数据发送,低位在前,高位在后;
一个数据帧包括:起始位(一个周期的低电平)+数据帧(8位或9位数据)+停止位(一定时间周期的高电平);
停止位时间长短由USART_CR2的STOP位控制,可选0.5、1、1.5和2个停止位。
(5)接收器
如果将USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在RX线开始搜索 起始位。在确定到起始位后就根据RX线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到RDR内,并把USART_SR寄存器的RXNE位置1,同时如果 USART_CR2 寄存器的RXNEIE置1的话可以产生中断。
(6)波特率
波特率指数据信号对载波的调制速度,它用单位时间内载波调制状态改变次数表示,单位为波特,波特率越大,传输速率越快。
USART的发送器和接收器使用相同的波特率。计算公式如下:
fCK为USART时钟
USARTDIV为一个存放在波特率寄存器(USART_BRR)的一个无符号定点数,其中DIV_Mantissa[11:0]位定义USARTDIV的整数部分,DIV_Fraction[3:0]位定义 USARTDIV 的小数部分。
我们知道USART1使用APB2总线时钟,最高可达72MHz,其他USART的最高频率为36MHz。 我们选取USART1作为实例讲解,即fPLCK=72MHz。为得到115200bps的波特率,此时:
115200 = 72000000/16∗USARTDIV
解得USARTDIV=39.0625,可得DIV_Fraction=0.0625*16=1=0x01,DIV_Mantissa=39=0x27,即 应该设置USART_BRR的值为0x271。
2、校验控制
STM32F103 系列控制器USART支持奇偶校验。当使用校验位时,串口传输的长度将是8位的数 据帧加上1位的校验位总共9位,此时USART_CR1寄存器的M位需要设置为1,即9数据位。 将USART_CR1 寄存器的PCE位置1就可以启动奇偶校验控制,奇偶校验由硬件自动完成。启动了奇偶校验控制之后,在发送数据帧时会自动添加校验位,接收数据时自动验证校验位。接收 数据时如果出现奇偶校验位验证失败,会见USART_SR寄存器的PE位置1,并可以产生奇偶校验中断。 使能了奇偶校验控制后,每个字符帧的格式将变成:起始位+数据帧+校验位+停止位。
3、中断控制
USART有多个中断请求事件
三、USART初始化结构体详解
1、USART初始化结构体
标准库函数对每个外设都建立了一个初始化结构体,比如USART_InitTypeDef,结构体成员用于 设置外设工作参数,并由外设初始化配置函数,比如USART_Init()调用,这些设定参数将会设置 外设相应的寄存器,达到配置外设工作环境的目的。
typedef struct
{
uint32_t USART_BaudRate; /*波特率设置,一般设置为2400、9600、19200、115200,标
准库函数会根据设定值计算得到USAETDIV值,从而设置
USART_BRR寄存器值*/
uint16_t USART_WordLength; /*设置数据帧字长,可选8位或9位,若无校验位使用8位,如果使
用校验则一般设置为9位*/
uint16_t USART_StopBits; /*停止位设置,可选0.5、1、1.5和2个停止位*/
uint16_t USART_Parity; /*奇偶校验控制选择*/
uint16_t USART_Mode; /*USART模式选择*/
uint16_t USART_HardwareFlowControl; /*硬件流控制选择,只有在硬件流控制模式才有效,可选有使能RTS、使能CTS、同时使能RTS和CTS、不使能硬件流。*/
} USART_InitTypeDef;
2、USART时钟初始化结构体
当使用同步模式时需要配置SCLK引脚输出脉冲的属性,标准库使用一个时钟初始化结构体 USART_ClockInitTypeDef 来设置,该结构体内容也只有在同步模式才需要设置。
typedef struct
{
uint16_t USART_Clock; /*时钟使能控制*/
uint16_t USART_CPOL; /*时钟极性*/
uint16_t USART_CPHA; /*时钟相位*/
uint16_t USART_LastBit; /*最尾位时钟脉冲*/
} USART_ClockInitTypeDef;
四、USART1收发通信实验
1、硬件
在利用USART实现开发板与电脑通信,需要用到一个USB转串口的IC,选择CH340G芯片实现这个功能。
2、软件
(1)编程要点
1)使能GPIO时钟和USART时钟;
2)初始化GPIO,并将GPIO复用到USART上;
3)配置USART参数;
4)配置中断控制器并使能USART中断;
5)使能USART;
6)在USART接收中断服务函数中实现数据接收和发送。
(2)代码分析
1)串口发送
bsp_usart.h
#ifndef _BSP_USART_H
#define _BSP_USART_H
#include "stm32f10x.h"
void USART_Config(void);
void Usart_SendByte(uint8_t tx_value);
void Usart_SendArray(uint8_t *Array,uint16_t Length);
void Usart_SendString(char *String);
uint32_t Tx_Pow(uint32_t X,uint32_t Y);
void Usart_SendNumber(uint32_t Number,uint32_t Length);
#endif
bsp_usart.c
void USART_Config(void)
{
//打开串口GPIO外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//打开串口外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
/*1、初始化串口GPIO*/
GPIO_InitTypeDef GPIO_InitStructure;
//将USART_Tx的GPIO配置为推挽复用输出模式
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);
/*2、配置串口的各种参数*/
USART_InitTypeDef USART_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_Tx | USART_Mode_Rx;; //串口模式
USART_Init(USART1, &USART_InitStructure);
//使能串口
USART_Cmd(USART1, ENABLE);
}
//发送一个字符
void USART_SendByte(uint8_t tx_value)
{
//注意串口数据寄存器只有低八位有效
USART_SendData(USART1, tx_value);
//等待串口状态寄存器的TXE(发送数据寄存器空)置‘1’
//0:数据还未被转移到移位寄存器
//1:数据已经被转移到移位寄存器
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
}
/*发送一个数组*/
void Usart_SendArray(uint8_t *Array,uint16_t Length)
{
uint16_t i;
for(i=0;i<Length;i++)
{
Usart_SendByte(Array[i]);
}
}
/*发送一个字符串*/
void Usart_SendString(char *String)
{
uint16_t i;
for(i=0;String[i] != '\0';i++)
{
Usart_SendByte(String[i]);
}
}
uint32_t Tx_Pow(uint32_t X,uint32_t Y)
{
uint32_t value = 1;
while(Y--)
{
value *= X;
}
return value;
}
/*发送一个十进制数*/
void Usart_SendNumber(uint32_t Number,uint32_t Length)
{
uint16_t i;
for(i=0;i<Length;i++)
{
Usart_SendByte(Number/Tx_Pow(10,Length - i - 1)%10+'0'); //需要以字符的形式显示,所以需要加上偏移“0x30”。
}
}
2)串口接收
bsp_usart.h
#ifndef _BSP_USART_H
#define _BSP_USART_H
#include "stm32f10x.h"
void USART_Config(void);
void Usart_SendByte(uint8_t tx_value);
void Usart_SendArray(uint8_t *Array,uint16_t Length);
void Usart_SendString(char *String);
uint32_t Tx_Pow(uint32_t X,uint32_t Y);
uint8_t USART_GetRxData(void);
void Usart_SendNumber(uint32_t Number,uint32_t Length);
uint8_t USART_GetRxFlag(void);
void USART1_IRQHandler(void);
#endif
bsp_usart.c
#include "bsp_usart.h"
uint8_t Rx_Data;
uint8_t Rx_Flag;
void USART_Config(void)
{
//打开串口GPIO外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//打开串口外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
/*1、初始化串口GPIO*/
GPIO_InitTypeDef GPIO_InitStructure;
//将USART_Tx的GPIO配置为推挽复用输出模式
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);
//将USART_Rx的GPIO配置为浮空输入模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/*2、配置串口的各种参数*/
USART_InitTypeDef USART_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_Tx | USART_Mode_Rx;;
USART_Init(USART1, &USART_InitStructure);
/*3、串口中断向量控制器参数配置*/
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/*4、使能串口接收中断*/
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
/*5、使能串口*/
USART_Cmd(USART1, ENABLE);
}
uint8_t USART_GetRxFlag(void)
{
if(Rx_Flag == 1){
Rx_Flag = 0;
return 1;
}
else{
return 0;
}
}
uint8_t USART_GetRxData(void)
{
return Rx_Data;
}
void USART1_IRQHandler(void)
{
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET)
{
Rx_Data = USART_ReceiveData( USART1 );
Rx_Flag = 1;
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //也可以不用,可以硬件清
除标志位
}
}
main.c
#include "stm32f10x.h" // Device header
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_systick.h"
#include "bsp_usart.h"
extern void delay_m(uint32_t num);
uint8_t KEYNum;
uint8_t USART_Data;
int main(void)
{
LED_Init();
KEY_Init();
USART_Config();
while(1)
{
if(USART_GetRxFlag() == 1)
{
Usart_SendByte(USART_GetRxData());
}
}
}
五、重定向prinft和scanf函数
int fputc(int ch,FILE *f)
{
//发送一个字节数据到串口
USART_SendData(DEBUG_USARTx,(uint8_t)ch);
//等待发送完毕
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
return (ch);
}
int fgetc(FILE *f)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(USART1);
}
在C语言标准库中,fputc函数是printf函数内部的一个函数,功能是将字符ch写入到文件指针f所指向文件的当前写指针位置,简单理解就是将字符写入到特定文件中。
注意
需要包含#include <stdio.h>文件
需要勾选“UseMicroLIB”,MicoroLIB是缺省C库的备选库,它对标准C库进行了 高度优化使代码更少,占用更少资源。