一 前言
本篇文章并不会系统的从零开始讲起,适合大家对USART有一定的学习,再看本篇文章会有一定的收获,祝大家在本文中,吸收到新的知识。
二 通信方式
1)按数据传输的方式分(这就是“串行 vs 并行”)
(1)串行通信
串行通信:是指数据一位一位地按顺序传输,比如我们常见的 UART、USART、SPI、I2C 等都是串行通信。这种方式的优点是连线少,成本低,尤其适合远距离传输和资源受限的场合。
特点:速度较慢,硬件连接简单,数据线只有一根,
(2)并行通信
并行通信:是同时传输多个位,比如计算机内部的总线、老式的并口打印机接口等。这种方式的数据吞吐量大,但需要更多的连线和同步机制,通常用于短距离和高速数据传输。
特点:速度较快,需要更多的数据线
不知道大家有没有这样一个疑问:
大家再看串行通信这里的举例,USART、UART他不是连接了四根线,其中两根数据线吗,分别是Rx、Tx吗,你怎么就能说他们俩是串行通信呢?
答:大家想的确实没错,但是在实际的硬件连接中,像 UART 或 USART 这样的串行通信确实会有四根线:VCC(电源)、GND(地线)、TX(发送)、RX(接收)。其中 TX 和 RX 是用于数据传输的两根线。
我之前说的“串行通信只有一根数据线”其实是指数据在一根线上按位串行传输,而不是像并行通信那样同时在多条线上并行传输多个位。TX 和 RX 是串行通信中用于发送和接收的两根数据线,数据是按位依次传输的。所以,串行通信的“串”是指数据在一条数据线上按顺序传输,而并不是指有多少根物理线。
2)按通信方向分(这就是“单工/半双工/全双工”)
类型 | 特点 | 举例 |
---|---|---|
单工 | 只能单向通信 | 收音机(只能接收) |
半双工 | 双向通信但不能同时进行 | 对讲机(你说我听,不能同时) |
全双工 | 双向通信可同时进行 | 电话、UART串口 |
3)按时钟控制方式分(这就是“同步 vs 异步”)
类型 | 特点 | 举例 |
---|---|---|
同步通信 | 依靠共享时钟信号,时序精确 | SPI、USART 同步模式 |
异步通信 | 没有时钟线,靠起始位/停止位来同步 | UART、USART 异步模式 |
在这里就要引出我们本文的主角了,USART
三 USART
大家有没有经常搞混USART和UART呢,我简单的给大家找一下相关的数据和分类,
UART 是一种“异步串行通信协议”;
USART 是 STM32 中的“通用同步/异步串口外设”,它能实现 UART,也能实现更多。
UART能干的,USART就给他干完了,所以USART我们更常用。我们平常说的UART说的应该就是USART下的UART模式,他叫异步串行通信。
下两张表是他们所对应的模式
USART:
模式名称 | 描述 |
---|---|
UART 模式 | 异步串行通信,最常用的方式,用 TX/RX 就行 |
同步模式 | 增加了一个时钟引脚(CK),用于同步通信(类似 SPI) |
单线半双工模式 | 只有一根数据线,TX 和 RX 复用(比如红外通信) |
智能卡模式 | 主要用来对接智能卡协议(ISO 7816),包含特定帧结构和校验要求 |
IrDA 模式 | 红外通信协议,STM32 USART 支持内建调制和解调 |
LIN 模式 | 本地互连网络(Local Interconnect Network),汽车中常用的低速通信协议 |
DMX 模式(高级型号) | 灯光控制系统协议(部分高级芯片才有) |
UART:
功能 | 有/无 |
---|---|
异步通信(TX/RX) | ✅ |
同步通信(带时钟) | ❌ |
IrDA / LIN / Smartcard | ❌ |
大家观看上面这两张表,大家也能发现,USART要比UART的功能更加的强大,而我们更常用USART下的UART模式,也就是我们最经常接触的有Rx、Tx引脚的串口器件,USB转TTL电平的模块,如图:
在这里要特别说明一点:如果用异步的通信就一定会包含起始位、停止位、校验位、数据位,如果想要保证异步通信的准确性,就需要这些去判断,而不像同步通信,同步通信靠的是标准的时钟信号CLK
四 基础概念
1)波特率和比特率
比特率Bit Rate:每秒传输了多少个 0 或 1,传输了几位二进制的数
波特率 Baud Rate:传输的是码元,码元是用来承载比特的容器
情况 | 每个码元代表多少比特 | 举例说明 |
---|---|---|
简单二进制通信 | 1 bit | 一个码元只有两种状态(0 或 1) |
QPSK(四相移键控) | 2 bits | 一个码元表示 00、01、10、11 |
这里说的码元,并不是固定死的,他是人为可以规定的,在今天的串口和未来几天关于蓝牙HC-05的讲解,都是将一个码元表示成 1 个比特,也就是说,最近我写的博客中,都是比特率=波特率。
2)通信接口
前文提到的USB转TTL电平模块,要简单说一下,USB是电脑的接口,TTL是单片机引脚的电平,但是电脑和单片机之间是没法进行交流的。
🧑💻【电脑】 —(USB)→ ❓ →(TX/RX)—【单片机】
中间得来个“翻译官”——这就是我们说的 USB转TTL模块!
关于通信接口,还有两个分别是RS232\RS485,这里我并没有研究太深,大家感兴趣的,自己研究就行。
3)复用功能、重映射
说实话这两个我是经常搞混,我给大家进行一个总结,希望大家不要重走我的苦
复用功能;也总说成复用引脚,他就是可以实现同一个引脚可以干很多事情,给大家举个例子,大家可以翻开“stm32f10x中文参考手册”的第118页、120页,在PA9这个引脚上,不仅可以是定时器1通道2还可以是USART1_Tx
如果出现了这个复用引脚,那么我们在配置GPIO结构体的时候,就要选择AF,例如:GPIO_Mode_AF_PP。大家能明白为什么要有复用推挽复用开漏的功能了吧?
接下来讲重映射:
重映射是将外设的某个功能换个引脚来使用。
你原本以为串口只能走 PA9/PA10,结果它说:“不,我还能走 PB6/PB7,只要你设置一下重映射就行!”
这样不就能解决引脚冲突,让资源分配更灵活了嘛。
REMAP=0是默认,REMAP=1是重映射到对应的引脚。一旦要有重映射我们就要在代码中配置一个复用时钟函数,如下图:
重映射中还有部分重映射、完全重映射,给大家总结好表格,大家自行阅读。
类型 | 说明 |
---|---|
默认映射 | 比如 USART1 默认映射在 PA9 / PA10 |
部分重映射 | 比如 TIM2 可以从 PA0/PA1 映射成 PA15/PB3(只改部分) |
完全重映射 | 所有引脚全部换一组,比如 TIM3 从 PA6/PA7/PC6 映射成 PC8/PC9/PB0 |
五 代码讲解
以上关于知识点就到此为止了,代码相关的知识便会很简单,usart.c代码
关于USART中UART模式的硬件代码配置如下,我是利用了中断,大家如果不想用中断也可以,只需将相应的代码删除即可~
PA9--Tx发送,在这里的发送可就用到了第四部分的复用功能哦,AF_PP,翻阅前文即可
PA10--Rx接收
usart_structure.USART_BaudRate = 115200;
usart_structure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart_structure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
usart_structure.USART_Parity = USART_Parity_No;
usart_structure.USART_StopBits = USART_StopBits_1;
usart_structure.USART_WordLength = USART_WordLength_8b;
波特率、校验位、模式等等,这里就是异步通信的标准了,需要把他们都配齐才可以。
为什么这里中断是RXNE呢?首先我们需要了解一下整个过程:
在这里说一下数据是怎么写进去的,首先发送一个数据,也就是库函数中的void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);这个意思就是将数据发送给数据寄存器TDR中,一旦数据写TDR中,TXE的标志位就会被清零,表示“发送数据寄存器”正在用。随后TDR中的数据转移到移位寄存器中(shift register),此时,TDR空了,也就代表着TXE重新置位,告诉数据可以发下一个数据给TDR了。
此时来到移位寄存器,移位寄存器要发送数据,这里发送是通过Tx引脚一位一位 的发送,包括了整个字节,起始位、数据位、校验位、停止位,等待发送完毕即可
常用三个东西,TXE、TC、RXNE
发送数据,需要用这个,TDR空,则用TXE(TXE=1)
判断数据整个过程(字符串)是否发送完成,则用TC
当你的设备需要从外部接收数据(RDR),中断处理,则用RXNE
#include "usart.h"
#include "stm32f10x.h" // Device header
#include "stdio.h"
//普通uart
void my_usart_Config(void)
{
GPIO_InitTypeDef GPIO_structure;
USART_InitTypeDef usart_structure;
NVIC_InitTypeDef NVIC_structure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
//PA9--Tx,发送
GPIO_structure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_structure.GPIO_Pin = GPIO_Pin_9;
GPIO_structure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOA, &GPIO_structure );
//PA10--Rx,接收
GPIO_structure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_structure.GPIO_Pin = GPIO_Pin_10;
GPIO_structure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOA, &GPIO_structure );
usart_structure.USART_BaudRate = 9600;
usart_structure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
usart_structure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
usart_structure.USART_Parity = USART_Parity_No;
usart_structure.USART_StopBits = USART_StopBits_1;
usart_structure.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &usart_structure);
USART_Cmd( USART1,ENABLE);
USART_ITConfig( USART1,USART_IT_RXNE,ENABLE); //RXNE,接收数据寄存器不空
NVIC_structure.NVIC_IRQChannel = USART1_IRQn;
NVIC_structure.NVIC_IRQChannelCmd = ENABLE;
NVIC_structure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_structure.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_structure);
}
//发送寄存器TDR,移位寄存器shift register
//发送一个字节ascii码的值
void My_Usart_Send_Byte(USART_TypeDef* USARTx, uint16_t Data)
{
USART_SendData( USARTx, Data);//写到TDR中
while( USART_GetFlagStatus( USARTx, USART_FLAG_TXE) == RESET);
//txe,发送数据的寄存器空了,移位寄存器已经空了
}
//发送字符串
void My_Usart_Send_String(USART_TypeDef* USARTx, unsigned char *str)
{
uint16_t i =0;
do
{
My_Usart_Send_Byte( USARTx, *(str + i));
i++;
}while(*(str + i) != '\0');
while( USART_GetFlagStatus( USARTx, USART_FLAG_TC) == RESET);
//tc,数据已经彻底全部发送完成了
}
//发送汉字
int putc(int ch, FILE *p)
{
USART_SendData( USART1, (u8)ch);
while( USART_GetFlagStatus( USART1, USART_FLAG_TXE) == RESET);
return ch;
}