串口实验演示
045.6.1 串口实验演示
#include "reg52.h"
#include <intrins.h>
typedef unsigned char uchar;
typedef unsigned int uint;
sbit LED1 = P1^0;
sbit LED2 = P1^1;
sbit LED3 = P1^2;
sbit LED4 = P1^3;
sbit BEEP = P1^6;
sbit JDQ1 = P2^0;
uchar recv;
void Delay_xms(uint xms) //@11.0592MHz
{
uchar data i, j;
while(xms)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
//串口发送一个字节
void UART_Send_Byte(uchar send_byte)
{
SBUF = send_byte;
while(!TI);
TI=0;
}
//串口发送字符串
void UART_Send_Str(uchar *send_str)
{
while(*send_str != '\0') //判断字符串结束符,如果识别到‘\0',则跳出while,结束发送
{
UART_Send_Byte(*send_str++); //本质是一个字节一个字节的发送,
//发送完一个字节,地址+1,等待发送下一个字节,直到检测到字符串结束符为止
}
}
void main()
{
SCON = 0x50; //串口配成工作方式1
PCON &= 0x7F; //波特率不加倍
TMOD &= 0x0f;
TMOD |= 0x20; //定时器1,模式二,自动重装初值,串口按照一定速率发数据,需要用的定时器
TH1 = 0xFD;
TL1 = 0xFD; //@11.0592MH时钟,9600比特率定时器初值
TR1 = 1; //定时器开始运行
ES = 1; //串口中断打开
EA=1; //总中断打开
while(1)
{
UART_Send_Str("I am wfeng!\r\n");
Delay_xms(1000);
}
}
/*****************
串口中断处理函数,接收数据
*****************/
void UART_Routine(void) interrupt 4
{
if(1 == RI) //判断接收标志位是否为1
{
RI = 0; //RI接收标志位清0
recv = SBUF; //接收数据
switch (recv)
{
case 0x1:
LED1 = 0;
break;
case 0x2:
LED2 = 0;
break;
case 0x3:
LED3 = 0;
break;
case 0x4:
LED4 = 0;
break;
case 0x5:
P1 |= 0x0F;
break;
case 0x6:
BEEP = 0;
break;
case 0x7:
BEEP = 1;
break;
case 0x8:
JDQ1 = 0;
break;
case 0x9:
JDQ1 = 1;
break;
default:
break;
}
}
}
一、串口实验演示
本实验先建立一个直观认识:电脑与51单片机之间可以进行双向串行通信。单片机每隔约1秒向电脑发送一行字符串;电脑也可以发送命令字节,让单片机控制LED、蜂鸣器和继电器。后续分析会把电脑端的USB通信、CH340转换电路和单片机内部UART联系起来。
串口通信的完整数据通路如下:
单片机发送:程序写SBUF → P3.1/TXD → CH340的RXD → USB D+/D- → 电脑串口助手
单片机接收:电脑串口助手 → USB D+/D- → CH340的TXD → P3.0/RXD → SBUF → 串口中断
这里的CH340不是简单的“换一个插头”,而是协议与电平转换芯片:电脑一侧使用USB差分信号D+、D-,单片机一侧使用TTL电平UART信号TXD、RXD。TTL串口也不同于传统DB9接口的RS-232电平,二者不能直接相连。
1.1 实验一接线方式
实验一需要一根USB数据线和一个继电器模块。开发板上虽然有两个Type-C接口,但功能并不相同:
| 接口 | 原理图中的名称 | 连接的主要电路 | 能否进行串口通信 |
|---|---|---|---|
| 带CH340串口芯片的一侧 | 供电/下载USB | USB供电、USB数据线、CH340N自动下载电路 | 可以,同时可以供电和下载程序 |
| 另一侧 | 供电USB | VBUS、滤波电容和电源网络 | 不可以,只能供电 |
实验时应把数据线插入靠近CH340芯片、能够下载程序的接口。若插入仅供电接口,开发板虽然会亮灯并运行程序,但电脑不会枚举出COM端口,串口助手也无法通信。
继电器模块的控制侧按下面的方法连接:
| 继电器模块引脚 | 开发板连接点 | 作用 |
|---|---|---|
VCC | 5V | 给继电器模块供电 |
GND | GND | 建立共同电位基准 |
IN | P2.0 | 接收单片机输出的控制电平 |
开发板5V ───────── VCC 继电器模块
开发板GND ───────── GND
P2.0 ───────── IN
必须连接GND。控制信号电压是相对于地定义的;只有IN而没有公共地,继电器模块无法可靠判断高、低电平。本实验程序中的JDQ1=P2.0,发送0x08时执行JDQ1=0,发送0x09时执行JDQ1=1。视频所用模块为低电平触发,因此IN=0时继电器吸合,IN=1时释放;其他继电器模块可能带高/低电平触发选择,使用时应以模块丝印和说明书为准。
继电器模块可以把控制侧和负载侧分开理解:
低压控制侧 触点侧
P2.0 → IN → 驱动管/光耦 → 继电器线圈 ║ COM ─┬─ NO(常开)
5V ────────────────┘ ║ └─ NC(常闭)
GND ───────────────── ║
- 线圈未吸合时,
COM与NC导通,COM与NO断开。 - 线圈吸合后,
COM与NO导通,COM与NC断开。 COM/NO/NC属于负载触点,不应和VCC/GND/IN混为一组。若以后控制市电负载,还必须考虑触点额定电压、电流、绝缘距离和人身安全;本实验只观察指示灯和吸合声即可。
板载CH340串口电路可以拆成五部分理解:
电脑USB
│ D+、D-
▼
Type-C数据接口
│
▼
CH340N USB转TTL
├─ TXD ───────────────→ P3.0/RXD(单片机接收)
├─ RXD ←── R1 300Ω ─── P3.1/TXD(单片机发送)
├─ RTS#、C7、R2、Q2 ── 自动下载辅助控制网络
└─ V3、C5 100nF ────── 内部电源去耦
CH340N负责USB协议与TTL UART之间的转换。R1=300Ω串联在串口信号线上,可限制瞬态电流并减小两个器件端口直接冲突时的冲击。D2=1N4148参与接收/自动下载信号的单向隔离,避免有关控制信号互相倒灌。RTS#、C7、R2、Q2组成自动下载辅助网络,将电脑端控制线的变化整形成板级控制动作,使下载软件可以配合芯片进入下载流程。它们不承担正常字符串数据的编码工作。C5=100nF是CH340内部电源节点的高频去耦电容,用来吸收快速开关产生的小幅电压波动。
开发板上的串口指示灯采用低电平灌电流方式:
+5V ── 限流电阻 ── LED ── TX或RX信号线
│
信号线为低电平时:+5V → 电阻 → LED → 芯片端口 → GND,LED点亮
信号线为高电平时:两端电位接近,LED熄灭
串口空闲状态通常为高电平,传输时会出现低电平脉冲,所以TX/RX灯会随数据收发闪烁。程序每隔约1秒发送一次"I am wfeng!\r\n",因此能看到发送指示灯约每秒闪一下。指示灯只能说明线路上出现过活动,不能代替对具体数据内容和波特率的检查。
1.1.1 写代码
实验程序位于教学资料的第二部分51单片机教学篇/代码/6.1串口通信_实验演示目录。烧写步骤如下:
- 打开STC烧写工具,确认芯片型号与开发板一致。
- 在串口列表中选择开发板对应的端口,例如
COM4。COM编号由电脑实际分配,不应照搬示例。 - 打开工程
Objects目录中生成的HEX程序文件。 - 点击“下载/编程”,再按工具提示让单片机重新上电或复位。
- 下载时观察板载串口收发灯;数据传输期间对应指示灯会闪烁。
程序运行后,while(1)反复执行:
UART_Send_Str("I am wfeng!\r\n") → 延时约1000ms → 再次发送
字符串由13个字节组成,其中\r是回车,\n是换行。UART_Send_Str()逐字节调用UART_Send_Byte();后者把数据写入SBUF,等待发送完成标志TI=1,随后用软件将TI清零。
本实验采用UART模式1和常见的8N1格式,一帧包含:
空闲高电平 ─ 起始位0 ─ D0 ─ D1 ─ D2 ─ D3 ─ D4 ─ D5 ─ D6 ─ D7 ─ 停止位1
8位数据,低位先发送
8N1表示8个数据位、无校验位、1个停止位。每个数据字节实际占用10个串行位,所以在没有帧间空隙的理想情况下,9600 bit/s最多传输:
$ \frac{9600\ \text{bit/s}}{10\ \text{bit/byte}}=960\ \text{byte/s} $
“9600波特”在这里等于每秒传输9600个二进制码元,不等于每秒传输9600个字符。
1.1.2 串口助手需要准备的软件
串口助手可从教学资料的“51单片机工具”目录复制到桌面并直接运行。电脑端参数必须与单片机一致:
| 设置项 | 本实验选择 | 含义 |
|---|---|---|
| 串口号 | 实际枚举出的COM端口 | 可通过设备管理器或拔插设备确认 |
| 波特率 | 9600 | 每秒9600个码元 |
| 校验位 | NONE | 无校验位 |
| 数据位 | 8 | 每帧8位有效数据 |
| 停止位 | 1 | 每帧末尾1位停止位 |
| 流控制 | NONE | 不使用RTS/CTS硬件流控 |
| 接收显示 | ASCII | 将收到的字节按字符显示 |
| 发送格式 | HEX | 输入1时发送一个字节0x01 |
设置完成后点击“打开”。程序会每隔约1秒发送I am wfeng!并换行,串口助手的接收区可以清空,但清空只影响屏幕显示,不会停止单片机继续发送。
发送格式选择HEX是本实验能正确控制外设的关键。HEX发送框中的1通常会被补成字节0x01,恰好匹配程序中的case 0x1。如果选择ASCII后发送字符1,线路上传输的是字符编码0x31,不会命中case 0x01。若希望使用ASCII命令,代码应写成case '1':或case 0x31:。
命令与动作的对应关系如下:
| HEX命令字节 | 程序动作 | 实验现象 |
|---|---|---|
01 | LED1=0 | LED1点亮 |
02 | LED2=0 | LED2点亮 |
03 | LED3=0 | LED3点亮 |
04 | LED4=0 | LED4点亮 |
05 | P1|=0x0F | P1.0~P1.3置1,四个LED熄灭 |
06 | BEEP=0 | 蜂鸣器响 |
07 | BEEP=1 | 蜂鸣器停止 |
08 | JDQ1=0 | 低电平触发继电器吸合 |
09 | JDQ1=1 | 继电器释放 |
LED“写0点亮”由硬件连接决定,而不是C语言中0代表“关闭”:
+5V ── LED ── 10kΩ ── P1.x
P1.x=0:端口向地灌电流,LED点亮
P1.x=1:LED两端压差很小,LED熄灭
蜂鸣器电路同样是低电平有效。P1.6经R3=1kΩ连接PNP三极管S9012的基极;P1.6=0时,基极电位被拉低,三极管导通,+5V经过三极管和蜂鸣器流向GND,于是蜂鸣器发声。单片机端口只负责提供小基极电流,蜂鸣器的主要工作电流由三极管承担,避免端口直接驱动较大负载。
1.2 实验二接线方式
实验二使用独立的CH340 USB转TTL模块,目的是说明外接模块能够完成与板载CH340相同的USB-UART转换。接线必须交叉并共地:
| USB转TTL模块 | 51单片机 | 原因 |
|---|---|---|
TXD | P3.0/RXD | 模块发送端接单片机接收端 |
RXD | P3.1/TXD | 模块接收端接单片机发送端 |
GND | GND | 两端共用电平参考 |
电脑 USB转TTL模块 51单片机
USB ────────────────→ CH340 TXD ─────────────────────→ P3.0/RXD
USB ←──────────────── CH340 RXD ←───────────────────── P3.1/TXD
电脑地 ─────────────── 模块GND ──────────────────────── 单片机GND
TXD和RXD按信号方向命名:每一端的发送端必须接另一端的接收端,因此是TX→RX、RX←TX。如果误接成TX→TX,两个输出端可能同时驱动一条线,不但无法通信,还可能产生冲突电流。
将模块USB端插入电脑后,系统会出现另一个COM端口,例如COM5。可以新建一个串口助手窗口,仍然设置为9600、8、NONE、1、NONE,接收选ASCII、发送选HEX。这样既能收到单片机周期发送的字符串,也能发送01~09控制开发板外设。
板载CH340和外接CH340的功能对应关系为:
板载方案:板载数据USB口 + 板载CH340N + P3.0/P3.1
外接方案:模块USB插头 + 模块CH340 + 杜邦线接P3.0/P3.1/GND
如果开发板已由自身USB接口供电,外接模块通常只连接TXD、RXD、GND,不要再连接模块的5V/VCC,以免两个USB电源互相反灌。视频所用模块上的黄色跳帽按接线说明取下;不同型号模块的跳帽可能用于选择3.3V/5V、供电或收发方式,实际操作必须先看模块丝印,不能只凭颜色判断。
当板载CH340与外接模块同时接在P3.0/P3.1上时,单片机的TXD输出可以被两个接收器同时观察,所以两个串口助手都能收到同一字符串。但两个CH340的TXD都属于输出端,不应同时向P3.0驱动相反电平。实验时只从一个串口助手发送,正式应用则优先只保留一个发送源,避免总线争用。
1.2.1 代码分析
程序从main()开始,先配置串口、定时器1和中断,再进入主循环发送字符串。各条初始化语句的含义如下:
| 语句 | 作用 |
|---|---|
SCON=0x50 | 串口工作于模式1,允许接收 |
PCON&=0x7F | 清除SMOD,波特率不加倍 |
TMOD&=0x0F | 清除定时器1的高四位配置,保留定时器0配置 |
TMOD|=0x20 | 定时器1选择模式2、内部时钟、无门控 |
TH1=TL1=0xFD | 设置定时器1自动重装值,为9600波特提供时钟 |
TR1=1 | 启动定时器1 |
ES=1 | 允许串口中断 |
EA=1 | 打开CPU总中断开关 |
SCON=0x50=0101 0000B,各位可以逐位展开:
| 位 | 名称 | 本实验值 | 含义 |
|---|---|---|---|
| B7 | SM0/FE | 0 | 在通常配置下与SM1共同选择串口模式;STC扩展功能启用时也可作帧错误标志FE |
| B6 | SM1 | 1 | 与B7组合为01,选择模式1:8位UART、波特率可变 |
| B5 | SM2 | 0 | 关闭多机通信筛选,正常接收模式1的数据帧 |
| B4 | REN | 1 | 允许串口接收 |
| B3 | TB8 | 0 | 模式2/3的第9个发送位;模式1不用 |
| B2 | RB8 | 0 | 模式2/3的第9个接收位;模式1中可反映停止位 |
| B1 | TI | 0 | 发送完成标志,由硬件置1、软件清0 |
| B0 | RI | 0 | 接收完成标志,由硬件置1、软件清0 |
定时器1的TMOD高四位依次为GATE1、C/T1、M1、M0。写入0x20后的高四位为0010B:GATE1=0表示只由TR1控制运行,C/T1=0表示使用内部时钟,M1M0=10表示8位自动重装模式。TH1保存重装值,TL1从该值开始向上计数,溢出后硬件自动把TH1重新装入TL1。
在11.0592MHz、12T、SMOD=0条件下:
$ f_{T1_overflow}=\frac{11,059,200}{12\times(256-0xFD)} =\frac{11,059,200}{12\times3} =307,200\ \text{Hz} $
模式1的波特率为定时器1溢出率的$1/32$:
$ baud=\frac{307,200}{32}=9600\ \text{bit/s} $
这说明0xFD不是经验数字,而是由晶振、12T时钟、定时器1模式2和SMOD=0共同计算出的结果。如果这块STC89C52被切换成6T模式,仍机械使用0xFD就无法得到9600波特;换用1T的8051兼容芯片时也必须按对应数据手册重新计算,否则收发两端会因波特率不匹配而出现乱码或完全无法通信。
SBUF的读写地址相同,但芯片内部包含彼此独立的发送缓冲和接收缓冲:
写SBUF → 发送缓冲/移位寄存器 → P3.1/TXD
P3.0/RXD → 接收移位寄存器/接收缓冲 → 读SBUF
发送流程是SBUF=send_byte → 等待TI=1 → TI=0。字符串函数持续移动指针,遇到C字符串结束符\0后停止,因此字符串中的\r\n会被发送,而编译器自动附加的\0不会被发送。
接收采用串口中断。8051串口的发送完成TI和接收完成RI共用中断号4,入口地址为0023H;只要二者之一置1且EA=1、ES=1,CPU就会进入UART_Routine()。本程序在中断函数中判断RI,清零后读取SBUF,再用switch执行外设命令。发送完成的TI则由UART_Send_Byte()轮询并清零。两个标志都必须由软件清除,否则会反复提出中断请求。
从接收到执行命令的过程可概括为:
串口助手发送HEX字节
│
▼
P3.0/RXD接收完整8N1帧
│
▼
硬件把数据装入SBUF,并置RI=1
│
▼
CPU进入串口中断:RI=0,recv=SBUF
│
▼
switch(recv)匹配0x01~0x09
│
├─ 控制P1.0~P1.3:LED
├─ 控制P1.6:蜂鸣器
└─ 控制P2.0:继电器
排查通信故障时,应按信号链从外到内检查:确认使用数据USB口和正确COM号,再检查两端参数是否同为9600、8N1、无流控,然后确认TX/RX交叉、GND共地,最后核对发送格式究竟是HEX还是ASCII。这样比反复修改程序更容易定位问题。