前言:
学习笔记,若有侵犯请告知删改
参 考:[9-1] USART串口协议_哔哩哔哩_bilibili
串口通信原理与实践-优快云博客
江科大学习笔记——USART串口通信_江科大串口通信程序-优快云博客
STM32江科大————USART串口通信_32江科大usart串口-优快云博客
通信接口
【通信目的】:一对或者多设备之间进行数据传输,拓展功能
【通信协议】:制定通信的规则,通信双方按照协议规则进行数据收发
USART:TX(数据发送脚)、RX(数据接收脚)
I2C:SCL(时钟)、SDA(数据)
SPI:SCLK(时钟)、MOSI(主机输出数据脚)数据发送、MISO(主机输入数据脚)数据接收、CS(片选,用于指定通信的对象)
CAN:CAN_H、CAN_L(差分数据脚,用2个引脚表示差分数据)
USB:DP(也叫D+)、DM(D-)也是一对差分数据脚。
【双工模式】
全双工:通信双方能够同时双向通信。通常两根数据线,发送线路和接收线路互不影响(打电话)
半双工:只有一根数据线,I2C、CAN、USB。CAN和USB两根差分线组合成一根线(对讲机)
单工:数据只能从一个设备到另一个设备,不能反着来(广播)。
【时钟特性】(对的时间做对的事)
同步:同一时钟节拍下工作。I2C、SPI有单独的时钟线,所以同步,接收方可以在时钟的指引下进行采样。
异步:在相同的频率下工作,只看起始和结束。USART、CAN、USB没有时钟线,需要双方约定一个采样频率,并还需加帧头帧尾等,进行采样位置的对齐。
【电平特性】
单端:USART、I2C、SPI,引脚的高低电平都是对GND的电压差,所以单端信号通信双方必须要共地,就是把GND接在一起。所以这3个引脚前必须加GND引脚。
差分:没有参考电平线,靠2个差分引脚的电压差来传输信号,是差分信号,在通信的时候,不需要GND。不过USB协议也有一些地方需要单端信号,所以USB还是需要供地的,性能优,距离远,抗干扰强。
【设备通信】
点对点:只有两个设备
多设备:需要有寻址的过程,确定对象是谁
串口通信USART
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器 是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备点对点的互相通信,并且串口通信每次同时只能传输1个二进制位(与并行的区别)。
STM32内部集成了USART硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里。USART自带波特率发生器,最高达4.5Mbits/s 可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2) 可选校验位(无校验/奇校验/偶校验) 支持同步模式(CLK输出)、硬件流控制(防止发送太快导致接收数据丢弃或覆盖)、DMA、智能卡、IrDA、LIN
单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力。常见硬件有蓝牙模块、语言模块、陀螺仪模块。
STM32F103C8T6 中的USART资源包括: USART1、 USART2、 USART3(USART1在APB2上72M,USART2和3在APB1上36M)
STM32江科大————USART串口通信_32江科大usart串口-优快云博客

硬件电路
- 简单双向串口通信有两根通信线(发送端TX和接收端RX) TX与RX要交叉连接
- 当只需单向的数据传输时,可以只接一根通信线(只用收或者发)
- 当电平标准不一致时,需要加电平转换芯片(前面说了,单端、共地参考)
【电平标准】
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
TTL电平:+3.3V(STM32)或+5V(51)表示1,0V表示0
RS232电平:-3~-15V表示1,+3~+15V表示0
RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)
串口参数以及时序
十位和十一位(多个校验位)数据帧
【波特率】
波特率,是串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位。波特率就是9600说明每秒钟可以传输9600个二进制位数据(传输一个二进制位需要的时间是1/9600秒,也就是104us)。串口通信的波特率不能随意设定,一般最常见的波特率是9600或115200(发送和接收波特率必须一致)。
【串口通信的通信单元】
串口通信时,收发是一个周期一个周期进行的,每个周期传输n个二进制位。这一个周期就叫做一个通信单元,一个通信单元由:起始位+数据位+奇偶校验位+停止位组成的。通信单元再通信时双方事需要先约定好
起始位:表示发送方要开始发送一个通信单元,起始位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。(串口通信固定为低电平0)
数据位:是一个通信单元中发送的有效信息位(低位先行),是本次通信真正要发送的有效数据,串口通信一次发送多少位有效数据是可以设定的(可选的有6、7、8、9,一般都是选择8位数据位,因为一般通过串口发送的文字信息都是ASCII码编码,而ASCII码中一个字符刚好编码为8位)
校验位:是用来校验数据位,以防止数据位出错的。常见有无校验(10位数据帧)、奇或偶标志位(11位数据帧,第八位奇0偶1判断 二进制 1 的个数)
停止位:是发送方用来表示本通信单元结束标志的,停止位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。常见的有1位停止位、1.5位停止位、2位停止位等(图中有),一般使用的是1位停止位。(串口通信固定为高电平1, 通信空闲状态也为1)。

USART内部框图

- 发送寄存器和接受寄存器占用同一个地址。
- 进行写操作时,数据写到TDR。进行读操作,数据从RDR读出来。
- 发送寄存器:把一个字节的数据一位一位地移出去。
- 若硬件检测到发送寄存器没有数据移位,TDR地数据就会一位一位移动到发送寄存器。当数据从TDR移动到发送寄存器就会置标志位。TXE为1,说明可以在TDR写入下一个数据。但TXE置1了并不是前面的数据已经从发送寄存器移出去。
- TX从发送移位寄存器移位,RX从接收移位寄存器移位。
- 硬件数据流控,简称流控。nRTS(Request To Send)是请求发送,是输出脚,nCTS(Clear To Send)是清除发送,是输入脚,这里的n都表示低电平有效。nRTS用于告诉别人能不能传输信号,nCTS用于接收nRTS信号。
- 可以触发串口中断实现
数据发送过程:首先写操作过后,数据来到TDR后会一整个字节一次性进入发送移位寄存器,此时会在SR状态寄存器里面置一个标志位TXE(TX Empty发送数据寄存器空),如果该标志位置1了,就可以在TDR立马写入下一个数据,此时数据还在发送移位寄存器里面,数据并未发送出去,然后发送移位寄存器会在绿色发生器控制的控制下,向右移位将数据从TX引脚发送出去,刚好对应串口协议低位先行,比如写操作写入0x55给TDR,此时寄存器里面就是原数据0101 0101,那么原数据向右移位就把最低位1先发送出去了,作为发送数据的最高位(低位先行,高位填充),然后依次发送剩下的010 1010,最终得到发送数据1010 1010,这就是串行发送。
数据接收过程:从RX引脚接收到数据,然后一位一位的读取引脚电平,读取到的第一个电平放在接收移位寄存器最高位,然后接收器控制器会控制接收移位寄存器向右移位,也就是将高位数据移到低位,移位之后再读取第二个电平放在最高位再右移,以此类推移位8次接受一个字节,置位SR状态寄存器RXNE(接收数据寄存器非空)。比如发送0x55,接收时RX引脚接收到的数据是1010 1010,把一个电平0放在最高位,然后移位原数据,再读取第二个电平1再移位,移位8次得到一个字节0101 0101之后,整个数据会一下到RDR里面并在状态寄存器里置标志位RXNE(RX Not Empty)=1,也就是并行接收
注意:发送时候加帧头帧尾和接收时候去帧头帧尾外设自己完成了,我们不用管
【简化结构图】

USART的PCLKx分频得到发送控制器和接收控制器的波特率,写操作可以判断标志位TXE,由发送控制器控制从TDR到移位寄存器到TX,读操作依据判断标志位RXNE,由接收控制器控制从RX到移位寄存器到RDR,最后Cmd开启外设即可。
【波特率发生器】
波特率发生器中,对APBx时钟分频得到对应发送和接收的时钟,fPCLKx(x=1或2),USART1挂载在APB2上是PCLK2的72MHZ时钟,USART2和3在APB1上是PCLK1的36MHZ时钟,USARTDIV是分频系数,可以支持到小数点后四位,确保分频更加精准,分频完成之后还要再除以16(内部使用了16倍波特率的采样时钟)才能得到发生器和接收器时钟频率。
发送器和接收器的波特率由波特率寄存器BRR里的DIV确定
计算公式:波特率 = fPCLK2/1 / (16 * DIV) ,一般定波特率需要求解的数值是DIV

USART数据帧
【字长配置】
通常是9位数据位带校验或者是8位数据位不带校验的方式,为上升沿采样,上升沿对应每一个数据的中间,时钟速率和数据速率一致。
空闲帧就是从头到尾都是高电平,断开帧就是从头到尾都是低电平
串口输出定时翻转TX电平即可,输入时不仅要保证采样频率和波特率一致,还要保证每次输入采样的位置正好在没一位的正中间,这样子采样才是最可靠的,另外输入还要判断噪声干捞,因此复杂的多。
【停止位配置】
通常停止位一位即可
【起始位侦测】

当输入电路侦测到一个数据帧的起始位后(也就是一个下降沿)就会以波特率的频率连续采样一帧数据。同时,从起始位开始采样位置(时钟上升沿)就要对齐到位的正中间,一般只要第一位对齐了后面就都齐了。
输入电路对起始位侦测的采样时钟进行细分,会以波特率的16倍进行采样,使一位数据之间要采样16次(1位的时间里采集16次数据)。
首先要要检测起始位,在空闲状态时候检测的全部都是1,而当检测到0的时候,产生下降沿,如果这个下降沿不是由于噪声产生的,那么后面的就应该是起始位了,这个时候就对起始位进行16次采样,采取每三位(3、5、7)至少两位是0这个才是起始位,否则舍弃这几次采样,再次寻找下一个下降沿再次判断是否是起始位,如果有噪声则会置标志位NE(Noisy Error),数据能够使用但是提醒你谨慎使用。当确定这是起始位之后,接收状态就会由空闲变成接收起始位,这个图中在8,9,10次采样(至少两位是0)才开始变为接收起始位状态,8,9,10三次采样肯定是在起始位正中间的,那么后续数据的16次采样,我们采样8,9,10这三次采样的地方肯定也位于后续数据正中间。
【数据采样】

在一个数据位有16个采样时钟的数据长度,由于起始位侦测已经对齐了采样时钟,所以直接在中间的第8、9、10次连续采样三次,没有噪声影响便全为0或1,或者是受到了噪声的影响两位是0或1,同时置为NE标志,告诉你存在噪声。
数据格式
HEX模式/十六进制模式/二进制模式:以原始数据的形式显示
文本模式/字符模式:以原始数据编码后的形式显示


CH340
CH340内部原理图

硬件接线:
CH340中短接VCC-3.3V
PA9(USART1_TXD)----RXD
PA10(USART1_TXD)--TXD
GND----------------------------GND共地
电脑USB-----------------------CH340USB
连接好后出现出现COM9,若没有要么就是串口烧了识别不出,要么就是没有安装串口驱动(网上有很多教程,江科大视频最前面几个很详细)

蓝牙
编程实现
代码源于江科大STM32!!!库函数位于stm32f10x_usart.h,用什么学什么


串口发送+接收
1. 初始化
1.1开启USART1时钟APB2,开启GPIOA时钟APB2
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
1.2初始化GPIOA PA9输出TXD--复用推挽 PA10输入RXD--浮空或者上拉
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;(IPU)
1.3初始化USART (8位字长无校验、1位停止位、波特率9600)
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; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART1, &USART_InitStructure);
2. 发送一个字节函数
2.1 调用发送数据函数 //将字节数据写入数据寄存器,写入后USART自动生成时序波形
USART_SendData(USART1, Byte);
2.2 等待发送完成 //利用TXE发送标志位空判断,等其置为1(自动清0)发送完成一个字节
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
3. 发送一个数组
用指针传递数组, 设定长度,循环利调用发送一个字节函数发送

4. 发送字符串 (文本模式)
同理使用指针传递字符串,利用字符串末尾停止位为\0的特性判断字符串发送循环完成

5. 发送数字
利用数字长度以及对应位对10取模取出每一位数据,加上0数字的ASCII码‘0’或者0x30作为偏移
5.1创建一个次方函数,用于数字/10的y次方,取出该位

5.2 循环发送

6. 接收方
6.1 方法1:主函数中循环判断标志位RXNE(接收寄存器非空,即当数据从移位寄存器移动到RDR时置为1,对DR读取时硬件自动清零)的状态,然后读DR,使用OLED显示出来。
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
Serial_RxData = USART_ReceiveData(USART1);//读取数据寄存器,存放在接收的数据变量
}
6.2 方法2:串口中断
(1) 首先通过USART_IT_RXNE开启串口接收IT中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断
(2)NVIC中断分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
(3)NVIC配置
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //选择配置NVIC的USART1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure);
(3)调用中断函数 (在启动文件md.h中)

其中两个变量是为了方便调用和写主函数,建议extern作为全局变量或者是定义函数调用,可不清除RXNE标志位。为验证数据接收与发送的是否一致,可以进行数据回传,再发送RxData回去
Printf函数实现(可发文字)
1.打开Micro LIB精简库

2.重定向fputc
#include <stdio.h>
/**
* 函 数:使用printf需要重定向的底层函数
* 参 数:保持原始格式即可,无需变动
* 返 回 值:保持原始格式即可,无需变动
*/
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch); //将printf的底层重定向到串口中自己的发送字节函数
return ch;
}此时可以直接调用printf("\r\nNum2=%d", 222); 输出,但是只能有一个printf。不使用重定向而是使用sprintf打印到字符数组,再用串口发送字符数组,此方法打印到字符数组,之后想怎么处理都可以,可在多处使用
char String[100]; //定义字符数组
sprintf(String, "\r\nNum3=%d", 333);//使用sprintf,把格式化字符串打印到字符数组
Serial_SendString(String); //串口发送字符数组(字符串)
3.封装的prinf函数(抄就完了)
#include <stdarg.h>
/**
* 函 数:自己封装的prinf函数
* 参 数:format 格式化字符串
* 参 数:... 可变的参数列表
* 返 回 值:无
*/
void Serial_Printf(char *format, ...)//C语言可变参数
{
char String[100]; //定义字符数组
va_list arg; //定义可变参数列表数据类型的变量arg
va_start(arg, format); //从format开始,接收参数列表到arg变量
vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中
va_end(arg); //结束变量arg
Serial_SendString(String); //串口发送字符数组(字符串)
}
4.print输出格式乱码问题
(1)串口助手和Keil文字编码格式保持一致(中文推荐GB2312--GBK)
(2)若一定相使用UTF-8则Keil中修改参数

(3)批量转码工具
UltraCodingSwitch.exe进行转码

拓展:串口数据包
作用:把一个个单独的数据给打包起来方便进行多字节的通信
HEX数据包-数据以本身的形式出现

添加包头包尾进行数据判断,可以自由定义,若数据与包头包尾重复,怎么办?
(1)固定包长,对齐包头包尾,就能分清楚数据与包头尾了
(2)数据限幅,只发生非包头包尾的数据
(3)添加包头包尾的数量,减少重复
文本数据包-数据每个字节都要经过一层编码和译码,最终表现出文本形式

由于文本内容数据过多,不用在意包头包尾重复的形式,但是解析效率低
发送较为简单无疑发送HEX就是发送数组,发送文本就约等于发送字符串,接收却难
固定包长 HEX数据包接收

引入状态机的思维,定义三个状态,S=0、1、2进行逻辑编程,在对应的状态干对应的事情
发送时:

Serial_TxPacket可以设置为数组extern出去或者是定义为数组指针变量,这里本人觉得加入形参替代4不就可以实现可变长度了,不过接收部分也要做更多的变化,蛮难。
接收部分:
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0; //定义表示当前状态机状态的静态变量
static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
uint8_t RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
/*使用状态机的思路,依次处理数据包的不同部分*/
/*当前状态为0,接收数据包包头*/
if (RxState == 0)
{
if (RxData == 0xFF) //如果数据确实是包头
{
RxState = 1; //置下一个状态
pRxPacket = 0; //数据包的位置归零
}
}
/*当前状态为1,接收数据包数据*/
else if (RxState == 1)
{
Serial_RxPacket[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
pRxPacket ++; //数据包的位置自增
if (pRxPacket >= 4) //如果收够4个数据
{
RxState = 2; //置下一个状态
}
}
/*当前状态为2,接收数据包包尾*/
else if (RxState == 2)
{
if (RxData == 0xFE) //如果数据确实是包尾部
{
RxState = 0; //状态归0
Serial_RxFlag = 1; //接收数据包标志位置1,成功接收一个数据包
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除标志位
}
}
可变包长 文本数据接收

接收方:
void USART1_IRQHandler(void)
{
static uint8_t RxState = 0; //定义表示当前状态机状态的静态变量
static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
uint8_t RxData = USART_ReceiveData(USART1); //读取数据寄存器,存放在接收的数据变量
/*使用状态机的思路,依次处理数据包的不同部分*/
/*当前状态为0,接收数据包包头*/
if (RxState == 0)
{
if (RxData == '@' && Serial_RxFlag == 0) //如果数据确实是包头,并且上一个数据包已处理完毕
{
RxState = 1; //置下一个状态
pRxPacket = 0; //数据包的位置归零
}
}
/*当前状态为1,接收数据包数据,同时判断是否接收到了第一个包尾*/
else if (RxState == 1)
{
if (RxData == '\r') //如果收到第一个包尾
{
RxState = 2; //置下一个状态
}
else //接收到了正常的数据
{
Serial_RxPacket[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
pRxPacket ++; //数据包的位置自增
}
}
/*当前状态为2,接收数据包第二个包尾*/
else if (RxState == 2)
{
if (RxData == '\n') //如果收到第二个包尾为换行
{
RxState = 0; //状态归0
Serial_RxPacket[pRxPacket] = '\0'; //将收到的字符数据包添加一个字符串结束标志
Serial_RxFlag = 1; //接收数据包标志位置1,成功接收一个数据包
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE); //清除标志位
}
}
若想使用文本模式字符串判断,可以调用#include "string.h"头文件中的strcmp()函数,对比串口接收到的数据与字符串,if(strcmp(Serial_RxPacket, "LED_ON") == 0),从而执行操作。
基于STM32学习USART串口通信及应用
4929

被折叠的 条评论
为什么被折叠?



