串口通信

什么是串口

串口是一个广义的概念,指的是用于串行通信的接口。在计算机和嵌入式设备上,串口通信是指数据一位一位地按顺序传输,而不是像并行接口那样同时传输多个位。串口通信是许多嵌入式系统和外部设备(如传感器、模块、其他计算机等)之间进行通信的常用方式之一。

通过物理连接,将串口(如UART串行接口)的引脚与设备的USB端口或其他通信接口相连,以实现设备之间的数据传输或通信。

UART

UART (通用异步收发传输器 Universal Asynchronous Receiver/Transmitter),是一种具体实现全双工异步串行通信硬件或电路(它本身也定义了一套简单的串口通信协议)。

全双工: 全双工指的是通信的双方可以同时进行数据的发送和接收,彼此互不干扰。(也就是说需要至少两条线进行通信)

异步: 数据的发送方和接收方不需要共享一个全局的时钟信号。发送方可以在任意时间发送数据,接收方通过特殊的起始位停止位来判断数据的开始和结束。(不需要专门的时钟信号线)

串行通信: 数据通过一条数据线依次一位一位的发送。

UART 通过定义起始位、数据位、校验位和停止位等传输格式,来实现数据的串行发送和接收;具有数据传输速度稳定、可靠性高、适用范围广等优点。在嵌入式系统中,串口常用于与外部设备进行通讯,如传感器、液晶显示屏、WiFi 模块、蓝牙模块等。

硬件电路

引脚连接

UART 通道有两条数据线。每个设备上都有一个 RX 引脚和一个 TX 引脚(RX 用于接收,TX 用于发送)。每个设备的 RX 引脚都连接到另一个设备的 TX 引脚。请注意,没有共享时钟线!这是通用异步接收方发送方的“异步”方面。

UARTyingjianlianjie

通信协议

UART 通信的组成

在 UART中,传输模式为数据包(字符)形式。数据包由起始位、数据帧、奇偶校验位和停止位组成。

UARTtongxinxieyi

数据的收发过程

UARTshujushoufaguocheng

TTL

TTL(Transistor-Transistor Logic)是一种使用双极型晶体管(BJT)来实现逻辑电路的逻辑电平标准。其广泛应用于单片机通信中,特别是在串行通信接口(如UART、SPI、I2C)等场景下。

严格来说,TTL不是一种通信协议,而是逻辑电平的定义标准,描述的是电气属性

TTL的电平标准定义了“高”电平和“低”电平的电压范围,通常与电源电压(Vcc)有关。传统 TTL 逻辑电平以 5V 电源供电,常见的电平范围如下:

  • 逻辑 1(高电平):约为 2.0V 到 5V
  • 逻辑 0(低电平):约为 0V 到 0.8V

在串口通讯中 TTL 电平用于定义信号的电压范围:TX(发送引脚)、RX(接收引脚)

TX(Transmit):发送端通过 TX 引脚发送数据。当发送一个逻辑高电平(1)时,TX引脚输出一个接近 5V 的电压;当发送一个逻辑低电平(0)时,TX 引脚输出接近 0V 的电压。

RX(Receive):在接收端,RX 引脚检测输入电压是否在TTL电平的范围内。如果接收到的电压在高电平范围(通常是 2V 以上),则解码为逻辑 1;如果电压在低电平范围(通常是 0.8V 以下),则解码为逻辑 0。

UART 与 TTL

既然已经有了 UART 或者 TTL 中的一个协议,为什么还需要另外一个?

UART 负责数据的传输方式,即如何按位发送和接收数据。而 TTL 则规定了实际传输中电信号的高低电平。

它们的关系类似于计算机网络中的 TCP/IP 协议和以太网物理层的关系。UART 是负责逻辑层面的数据传输协议,而 TTL 则是负责电信号层面的物理传输标准。没有 UART 协议,TTL 电平的信号就没有办法以串行数据的方式组织和传输。没有 TTL 电平或其他类似的物理层标准,UART 定义的信号则没有适当的电压来表示和传输。

CH340N

CH340N 是一款常用的 USB 转 UART(串口)的桥接芯片,用于将 UART 串口数据转换为 USB 接口数据,进而实现串口设备与 USB 设备之间的通信。

CH340N 芯片主要用于将串行 UART 接口转换为 USB 接口,并且在系统中可以虚拟为标准的串口设备(COM 口)。这种功能使得我们可以在计算机没有串口的情况下,通过 USB 进行串行通信,方便调试、数据传输等应用。

串口转换芯片的转换电路图

chuankouzuanUSB

引脚图:

chuankouzuanUSByuanlitu

  • D+ D-对应的 USB 口,和 PC 主机连接
  • P3.1 P3.0 对应的芯片引脚
  • 采用 CH340 将串口和 USB 之间进行转换

STC8H

以下是 STC8H 的芯片引脚介绍图

STC8H8K64Uyinjiaotu

其中有 4 组 Uart 通讯口:

UARTtongxinkou

实现

通过串口接受收据并原样返回

  1. 新建项目。新建main.c文件

  2. 导入函数库。拷贝以下函数库文件到项目目录:

  3. Config.h Type_def.h

  4. GPIO.h``GPIO.c

  5. Delay.h``Delay.c

  6. UART.h``UART.c``UART_Isr.c

  7. NVIC.c NVIC.h

  8. Switch.h

配置代码

GPIO
1
2
3
4
5
6
7
8
9
10
11
12
/******************* IO配置函数 *******************/
void GPIO_config(void) {

GPIO_InitTypeDef GPIO_Init; //结构定义

//指定IO的工作模式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
GPIO_Init.Mode = GPIO_PullUp;
//指定要初始化的IO引脚, Px0, Px1
GPIO_Init.Pin = GPIO_Pin_0 | GPIO_Pin_1;

GPIO_Inilize(GPIO_P3,&GPIO_Init); //初始P3端口
}
UART
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void UART_config(void) {

COMx_InitDefine UART_Init; //结构定义

//工作模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
// SCON = (SCON & 0x3f) | COMx->UART_Mode; SCON 串口 1 的控制寄存器
UART_Init.UART_Mode = UART_8bit_BRTx;
//选择波特率发生器, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
UART_Init.UART_BRT_Use = BRT_Timer1;
UART_Init.UART_BaudRate = 115200ul; //波特率, 一般 110 ~ 115200
// 能够通过 UART 的 RX 引脚接收从其他设备发送过来的数据。
UART_Init.UART_RxEnable = ENABLE; //接收允许, ENABLE或DISABLE
UART_Init.BaudRateDouble = DISABLE; //波特率加倍, ENABLE或DISABLE

//初始化串口: UART1,UART2,UART3,UART4
UART_Configuration(UART1, &UART_Init);

//UART1 中断初始化
//中断使能, ENABLE/DISABLE; 优先级(低到高) Priority_0,Priority_1,Priority_2,Priority_3
NVIC_UART1_Init(ENABLE,Priority_1);

//指定为UART1通信的TX(发送)和RX(接收)引脚到底是那一对
//UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17,UART1_SW_P43_P44
UART1_SW(UART1_SW_P30_P31);
}

在单片机开发中,配置了UART(UART1_SW(UART1_SW_P30_P31);)后,通常还需要配置GPIO(GPIO_Pin_0 | GPIO_Pin_1)。这是因为UART本身只是一种通信协议和硬件外设模块,而 GPIO 管脚的配置则决定了UART功能的实际物理引脚映射和特性。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void on_uart1_recv() {
u8 i;
// RX_Cnt收到的数据个数(字节u8 - unsigned char)
// 将收到的数据, 按字节逐个循环
// u8 RX1_Buffer[120]
for(i=0; i<COM1.RX_Cnt; i++) {
u8 dat = RX1_Buffer[i]; // 1 1 1 1 0 0 0 0 -> 0xF0
TX1_write2buff(dat); //收到的数据原样返回
}
}

/**
开启串口调试,接收数据,把收到的数据原样返回
**/
void main() {
// 初始化GPIO、UART
GPIO_config();
UART_config();

// 开启中断(全局)必须要写!
EA = 1;

// 写一个字节
TX1_write2buff(0x23);
// 通过PrintString1输出字符串
PrintString1("STC8H8K64U UART1 Test Programme!\r\n"); //UART1发送一个字符串
// 通过printf输出字符串
printf("STC8H8K64U UART1 Test Programme!\r\n"); //UART1发送一个字符串

while(1) {
// 超时计数
// 一旦收到了一个字节数据,RX_TimeOut会初始化一个值(例如:5)
if((COM1.RX_TimeOut > 0) && (COM1.RX_TimeOut == 0)) {
if(COM1.RX_Cnt > 0) {
// 收到数据了,on_uart1_recv();
on_uart1_recv();
}
// 处理完数据,将数据个数清零
COM1.RX_Cnt = 0;
}
// 注意这里delay代码的位置,属于while
delay_ms(10);
}
}

UART_Init.UART_BRT_Use = BRT_Timer1; 这一句的作用是指定用哪个定时器来生成波特率,即通过哪个定时器为串口通信提供时钟信号。

在单片机中,多个定时器通常可以执行不同的任务,比如控制 PWM 信号、生成定时中断或产生串口波特率。如果某个定时器被用于其他任务,就需要选择另一个定时器来生成波特率。

中断

查看数据手册 UART 通信涉及中断,所以需要配置中断(开启中断);

调试

使用 STC-ISP 调试工具进行调试。切换好串口助手,选择正确的串口,设置和代码中相同的波特率。

通过发送区进行数据发送,通过接收区观察接收内容。

问题

一、为什么配置或者说使用 UART 时要指定定时器或者波特率发生器?

在使用 UART(通用异步收发器)进行串行通信时,定时器或者波特率发生器的配置是必不可少的,因为串行通信是异步通信,没有时钟信号来同步发送端和接收端的数据传输。因此,波特率发生器和定时器的作用是保证双方能够在相同的时间窗口内正确地发送和接收数据。

异步通信(Asynchronous Communication)是一种数据传输方式,其中发送端和接收端之间没有共享的时钟信号,即没有全局时钟来同步数据的发送和接收。这种方式允许设备在不同的时间开始传输数据,但仍然能够通过某种机制正确解码接收到的数据。

二、为什么要指定波特率?

UART通信是异步的,这意味着通信双方没有共享的时钟信号。在没有时钟同步的情况下,双方必须依靠波特率来控制数据传输的速率。如果发送端和接收端的波特率不一致,数据将无法正确解码,导致通信错误。因此,指定波特率为了确保数据能够在预期的速率下传输和接收

三、URAT 中使用了 定时器,但我们却没有配置定时器却能使用为什么?

因为他内部自己配置了对于定时器的一系列操作;

1
2
3
4
5
6
7
8
9
10
11
12
13
if(COMx->UART_BRT_Use == BRT_Timer1) {       
TR1 = 0;
AUXR &= ~0x01; //S1 BRT Use Timer1;
TMOD &= ~(1<<6); //Timer1 set As Timer
TMOD &= ~0x30; //Timer1_16bitAutoReload;
AUXR |= (1<<6); //Timer1 set as 1T mode
TH1 = (u8)(j>>8);
TL1 = (u8)j;
ET1 = 0; //禁止中断
TMOD &= ~0x40; //定时
INTCLKO &= ~0x02; //不输出时钟
TR1 = 1;
}

四、多机通信指的是什么?

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)串口通信中的多机通信,指的是一个 UART 设备(通常称为主设备或主机)同时与多个其他 UART 设备(称为从设备或从机)进行通信的模式。这种通信模式在需要集中控制多个设备或进行数据广播的场景中非常有用。

五、波特率发生器怎么选:

看数据手册:比如 UART4 可以选择定时器 2、定时器 4,UART4 只能使用定时器 2 等。

这里的波特率发生器就是定时器,但我们只需选择不需要配置,因为它默认会给我们进行配置 ;

六、为什么 MCU 可以发送汉字而不可以接收汉字

如果 PC 端读取到一个字节的高位在有效范围,接下来的字节会被解释为同一字符的一部分,而不是独立的字节。相反,如果接收到的字节在 ASCII 范围内(0x00 到 0x7F),接收方将其视为普通字符。