Proteus仿真双机通信:从串口中断到PWM波形还原

发布时间:2026/9/17 2:30:50
Proteus仿真双机通信:从串口中断到PWM波形还原 简介这是一份基于51单片机与Proteus仿真的双机通信设计资料包含仿真图、源代码与讲解视频面向单片机初、中级学习者及课程设计人群解决双机数据交互、串口通信、中断与PWM输出等典型问题。资源共38个文件压缩包11.68MB主要包含Keil工程文件、Proteus仿真工程、C源码与hex固件、电路截图与说明文档另有exe格式的讲解视频方便对照源码与仿真环境逐步复现。设计实现了单片机1发送正弦波采样值通过按键选择频率单片机2接收后用1602LCD显示字节数并回送应答同时以PWM方式经有源RC滤波输出波形虚拟示波器可观测结果单片机1收到应答后数码管提示通信成功。目前已有185人学习浏览资料结构清晰文件夹按仿真工程、源程序、文档与视频分类适合作为课程设计、毕业设计或竞赛培训的参考方案也可基于现有代码扩展更多自定义通信功能。1. 双机通信的Proteus仿真从串口中断到波形还原做单片机通信实验最头疼的就是手边没有第二块板子。用Proteus仿真双机通信好处是波形、时序、收发状态全都在同一屏里看得到。这套设计里单片机1通过串口把正弦波采样值发给单片机2单片机2用1602LCD显示收到的字节数同时在P1.0用PWM方式还原波形再接RC滤波给虚拟示波器显示。整条链路覆盖了UART收发、定时器T0/T1的两种不同用法、查表法生成波形、PWM占空比控制以及双机握手协议。这套设计的核心不在于“点灯”而在于两个单片机之间如何约定通信格式、如何用中断保证实时性、如何把数字采样值变成模拟波形。适合正在学51单片机串口通信、或者准备课程设计的人群。下面按发送端、接收端、仿真验证三个层面拆开讲。2. 串口通信基础与T1波特率发生器配置2.1 为什么选工作模式1而不是模式0或模式251单片机的串口有四种工作模式。模式0是同步移位寄存器只能做8位数据的移位输入输出不能用于双机异步通信。模式2和模式3都是9位UART多出来的第9位可以做多机通信的地址帧标志但对这个项目来说并不需要。模式1是8位UART一帧10位1位起始位、8位数据位、1位停止位帧格式最干净波特率由T1的溢出率决定换算逻辑最直观。这个设计要求“T1作为波特率发生器使用工作模式1”所以SCON寄存器要配置成0101 0000也就是SM00、SM11、REN1。REN必须置1否则接收端根本收不到数据。T1工作在模式2也就是8位自动重装。之所以不用模式116位计数是因为串口通信的波特率要求定时器溢出周期尽可能精确稳定而模式1溢出后需要软件重新赋初值赋值指令会引入几个机器周期的误差。模式2由硬件自动重装TH1到TL1溢出周期完全确定。2.2 波特率计算公式与初值表模式1的波特率计算公式波特率 (2^SMOD / 32) × (T1溢出率) T1溢出率 系统时钟频率 / (12 × (256 - TH1))合并简化后波特率 (2^SMOD / 32) × (晶振频率 / (12 × (256 - TH1)))系统时钟频率这里指晶振频率除以12后的机器周期频率。SMOD是PCON寄存器的第7位默认值为0。SMOD0时波特率不分频倍增SMOD1时波特率翻倍。常用晶振和波特率的TH1初值对照如下晶振频率目标波特率SMODTH1初值实际波特率误差11.0592MHz960000xFD96000%11.0592MHz480000xFA48000%12MHz960010xF989287.5%12MHz480000xF348070.15%注意12MHz晶振算9600波特率的误差。12MHz无法整除出精确的9600误差会到7.5%这个误差在短帧数据传输时问题不大但连续大量收发时可能偶尔触发校验错误。Proteus仿真里如果波形出现偶发乱码先把波特率换成4800试一下往往就能定位到误差问题。2.3 串口初始化代码// UART初始化模式18位数据波特率960011.0592MHz晶振 void UART_Init(void) { SCON 0x50; // 0101 0000模式1REN1允许接收 TMOD 0x0F; // 只修改T1相关位保留T0配置 TMOD | 0x20; // T1工作模式28位自动重装 TH1 0xFD; // 波特率9600初值11.0592MHz TL1 0xFD; // 重装值 PCON 0x00; // SMOD0波特率不倍增 ES 1; // 使能串口中断 EA 1; // 开总中断 TR1 1; // 启动T1 }这段初始化代码的顺序是有讲究的。先配置SCON确定串口工作方式再配置TMOD确定T1的工作模式然后设TH1和TL1的初值。如果先把TR1置1再赋值TH1定时器会先按一个不确定的初值跑几个机器周期这期间如果恰好有串口中断请求帧头就丢了。ES和EA最后开是因为串口中断标志RI和TI在上电时不可控等寄存器全部配置完成后再开中断可以避免误入中断服务函数。3. 发送端实现正弦波采样表、按键选频与应答显示3.1 正弦波采样值怎么生成发送端要发送“一个周期的正弦波采样值”。实际实现是预先算好一张正弦波采样值表存到数组中然后在主循环里循环发送。注意这里的关键点是用查表法生成正弦波而不是实时计算sin函数。51单片机做浮点sin运算要消耗几十个机器周期实时算根本跟不上串口发送速率的节奏而且代码体积和运行时间都不划算。// 正弦波16点采样表取值0~255正好对应PWM占空比 // 计算公式value 127 127 * sin(2 * PI * i / 16) unsigned char code sin_table[16] { 127, 176, 221, 249, 255, 249, 221, 176, 127, 78, 33, 5, 0, 5, 33, 78 };为什么采样值范围取0到255因为接收端要用PWM方式输出波形PWM的占空比寄存器通常就是8位采样值直接映射到占空比不需要再额外做量程变换。如果采样值范围取0到100也可以但后面每次赋值都要先乘2.55再做整数变换多了一步运算。0到255直接赋值最简单。采样点数量选16而不是32或64是因为这个项目里采样值需要通过串口逐个字节发送采样点越多完整波形周期越长按键切换频率时的响应就越迟钝。16点配合按键切换延时波形视觉上足够平滑数据量也适中。3.2 按键选频用T0计数中断设计要求“通过按键选择频率该按键使用定时器T0计数方式中断实现”。这里用的不是查询按键电平而是把按键接到T0的外部计数引脚P3.4让T0工作在计数模式。每按一次按键相当于在P3.4引脚上产生一个下降沿T0计数寄存器加1。当计数值溢出时触发T0中断在中断服务函数里修改频率参数。void Timer0_Init(void) { TMOD 0xF0; // 只修改T0相关位保留T1配置 TMOD | 0x05; // T0工作模式116位计数器外部计数 TH0 0xFF; // 初值设为FFFB接近溢出 TL0 0xFB; // 每计5个脉冲溢出一次 ET0 1; // 开启T0中断 EA 1; // 开总中断 TR0 1; // 启动T0 } // 频率档位表按键切换时依次循环 unsigned char code freq_table[4] {1, 2, 4, 8}; unsigned char freq_index 0; void Timer0_ISR(void) interrupt 1 { TH0 0xFF; // 重装初值保证下次仍旧5个脉冲溢出 TL0 0xFB; freq_index; // 切换频率档位 if (freq_index 4) freq_index 0; }为什么要用“计数模式”而不是“定时器中断按键扫描”因为按键扫描需要主循环不断轮询引脚电平一旦主循环里有串口发送的延时等待按键响应就不及时。计数模式完全由硬件完成脉冲计数不需要CPU干预按5次触发一次中断抖动也不会导致误触发。这个思路在处理低速外部事件时非常实用。频率切换后的实际发送间隔由主循环里的延时函数决定。比如基础延时是2ms频率档位为1时每个采样点间隔2ms完整周期32ms约31Hz档位为4时每个采样点间隔0.5ms完整周期8ms约125Hz。具体数值可按需要调整。3.3 发送主循环与应答数码管// 数码管段码表共阴极0~F unsigned char code seg_table[10] { 0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F }; void main(void) { unsigned char i; UART_Init(); Timer0_Init(); while (1) { for (i 0; i 16; i) { SBUF sin_table[i]; // 发送一个采样值 while (TI 0); // 等待发送完成 TI 0; // 清发送中断标志 delay_ms(freq_table[freq_index]); // 根据当前频率延时 } if (ack_flag 1) { P2 seg_table[1]; // 数码管显示1表示应答成功 ack_flag 0; } } }TI标志位是发送完成标志。发送一个字符前要先确认上次发送已完成不能直接把SBUF连续赋值。while (TI 0)是查询方式也可以用中断方式在串口中断服务函数里置位标志但查询方式在这个场景下更简洁因为发送端的主循环本身就是按周期发送不需要同时处理其他并发任务。应答信号的接收是在串口中断里完成的。void UART_ISR(void) interrupt 4 { unsigned char rx_data; if (RI) { rx_data SBUF; // 读取数据 RI 0; // 清接收标志 if (rx_data 0xAA) { ack_flag 1; // 设置应答标志主循环里显示 } } }串口中断服务函数里只做最简单的置标志操作不直接操作数码管、不调用延时函数。中断服务函数越短越好这是51单片机中断编程的第一原则。4. 接收端实现1602LCD显示计数与PWM波形输出4.1 接收端完整代码结构与串口中断接收端的核心任务是三件事接收串口数据、统计字节数、在LCD上实时显示收到数据后回送应答信号用定时器T0在P1.0引脚上输出PWM波形波形占空比跟随收到的采样值变化。unsigned char rx_counter 0; // 收到的字节计数 unsigned char pwm_duty 127; // 当前PWM占空比 void UART_ISR(void) interrupt 4 { if (RI) { pwm_duty SBUF; // 读取采样值同时作为PWM占空比 RI 0; rx_counter; // 字节计数加1 LCD_Show_Counter(rx_counter); // 显示接收到的字节数 SBUF 0xAA; // 回送应答信号 while (TI 0); TI 0; } }这里有一个细节回送应答信号放在串口中断服务函数里意味着每收到一个字节就要回复一个应答。这会产生频繁的双向通信但在这个场景下是合理的因为发送端是按固定周期发数据的接收端回复的0xAA是固定握手信号。LCD_Show_Counter函数需要把二进制计数值转换成十进制数字一位一位显示到LCD上void LCD_Show_Counter(unsigned int cnt) { unsigned char temp[3]; temp[0] cnt / 100; // 百位 temp[1] (cnt % 100) / 10; // 十位 temp[2] cnt % 10; // 个位 LCD_Write_Char(0x80, temp[0] 0); // 第一行第一位 LCD_Write_Char(0x81, temp[1] 0); LCD_Write_Char(0x82, temp[2] 0); }4.2 1602LCD四线驱动与关键命令1602LCD有两种连接方式8线并口和4线并口。8线方式一次传输8位数据占用P0口全部引脚4线方式分两次传输高4位和低4位只占4个引脚。这个设计里LCD要和P1.0的PWM输出、T0计数等外设共存引脚资源紧张用4线驱动更合理。sbit RS P2^0; // 寄存器选择0指令1数据 sbit RW P2^1; // 读写选择0写1读 sbit EN P2^2; // 使能信号 // 1602写命令 void LCD_Write_Cmd(unsigned char cmd) { P0 cmd 0xF0; // 先传高4位 RS 0; RW 0; EN 1; delay_short(); EN 0; P0 (cmd 4) 0xF0; // 再传低4位 EN 1; delay_short(); EN 0; delay_long(); } // 1602写数据 void LCD_Write_Data(unsigned char dat) { P0 dat 0xF0; RS 1; RW 0; EN 1; delay_short(); EN 0; P0 (dat 4) 0xF0; EN 1; delay_short(); EN 0; delay_long(); }使用4线模式时初始化命令序列和8线模式不同。LCD在上电后默认是8线模式必须先通过三次0x03指令切换到4线模式然后再发送功能设置命令0x284线2行5x8点阵。void LCD_Init(void) { delay_ms(15); LCD_Write_Cmd(0x03); // 第一次发0x03 delay_ms(5); LCD_Write_Cmd(0x03); // 第二次 delay_ms(5); LCD_Write_Cmd(0x03); // 第三次 delay_ms(1); LCD_Write_Cmd(0x02); // 正式切换到4线模式 LCD_Write_Cmd(0x28); // 功能设置4线、2行、5x8 LCD_Write_Cmd(0x08); // 关闭显示 LCD_Write_Cmd(0x01); // 清屏 LCD_Write_Cmd(0x06); // 光标右移 LCD_Write_Cmd(0x0C); // 开启显示不显示光标 }每次调用LCD_Write_Cmd(0x03)时实际上只用了P0口的高4位P0.4到P0.7与LCD的DB4到DB7连接。连续三次发0x03是让LCD无论如何都能对齐到8位模式的指令接收状态这是切换4线模式的标准做法少一次都不行。4.3 PWM方式输出正弦波与RC滤波器参数计算接收端P1.0输出PWM波形。用定时器T0中断控制PWM周期和占空比void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; // T0工作模式116位定时器 TH0 0xFF; TL0 0x9C; // 100us定时初值11.0592MHz ET0 1; EA 1; TR0 1; } void Timer0_ISR(void) interrupt 1 { static unsigned char pwm_count 0; TH0 0xFF; TL0 0x9C; pwm_count; if (pwm_count pwm_duty) P1_0 1; // 占空比时间内输出高电平 else P1_0 0; if (pwm_count 255) pwm_count 0; }PWM的周期是255 × 100us 25.5ms约39Hz的PWM载波频率。PWM的频率越高滤波后的波形越平滑。如果想提高载波频率可以把100us定时改为50us同时把采样值映射范围从255改为对应值但注意中断频率越高CPU在中断里花的时间占比就越大会挤压主循环处理LCD刷新的时间。RC低通滤波器的截止频率fc 1 / (2 × π × R × C)设计目标是让载波频率被衰减而正弦波信号频率通过。PWM载波39Hz正弦波信号最高125Hz左右这两个频率比较接近一级RC滤波效果有限。常见做法是取R10kΩC1uF此时fc约15.9Hz对39Hz载波有约8dB的衰减波形上能看到比较明显的纹波。想更平滑就用两阶RC滤波第一阶R14.7kΩC110uF 第二阶R210kΩC21uF两阶滤波后纹波明显减小示波器上看到的正弦波形更干净。注意Proteus里虚拟示波器直接接在RC滤波器输出端不要再接入其他负载否则影响截止频率。4.4 两边T0的不同配置对比这个设计把T0在两端用出了完全不同的效果。发送端的T0是计数器模式方式1外部脉冲计数接收端的T0是定时器模式方式1内部时钟计时。两者初值和中断服务函数完全不一样放在同一篇文章里对比看非常有助于理解定时器/计数器模式切换的本质区别。// 发送端T0计数器按键输入 TMOD | 0x05; // 0000 0101T0方式1计数模式 // 接收端T0定时器PWM输出 TMOD | 0x01; // 0000 0001T0方式1定时模式C/TCounter/Timer位决定了T0是对外部脉冲计数还是对内部机器周期计数。一个引脚P3.4一个外部信号T0的输入源完全不同。这个区别在Proteus仿真里看得特别清楚发送端按按键时能看到TH0/TL0在硬件层面变化接收端则是TH0/TL0按固定节奏自增。5. 双机通信的Proteus仿真接线与波形验证技巧5.1 电路连接要点Proteus中搭建这个电路关键连接关系如下连接项发送端连接接收端UART TXDP3.1—— 交叉连接 ——P3.0 (RXD)UART RXDP3.0—— 交叉连接 ——P3.1 (TXD)按键输入P3.4——接外部按健至GNDPWM输出——P1.0接RC滤波器示波器—RC滤波器输出虚拟示波器CH1两个单片机的TXD、RXD必须交叉连接。同名端相连是仿真里最容易犯的错误。GND必须共地Proteus仿真里虽然不接GND也能跑但真实硬件上不共地会导致通信失败。按键接P3.4到GND之间按下时产生下降沿脉冲T0计数一次。TTL电平的UART信号在Proteus里不需要MAX232做电平转换因为两个单片机直接互联逻辑电平一致。如果接PC串口才需要MAX232。5.2 COMPIM模块的用法说明Proteus中如果想把这个仿真接到真实串口调试工具上调试可以使用COMPIM组件。双击COMPIM打开属性面板Physical port选择实际串口号如COM1Baud rate设为9600Data bits为8Parity为NONEStop bits为1Flow control选none。COMPIM的TXD连接发送端P3.1RXD连接接收端P3.0同样交叉。COMPIM的用处在于可以把Proteus仿真里的串口数据转发到真实串口方便用PC上的串口助手观察数据流。仿真里跑通了再下载到真实单片机通信协议不需要改动。5.3 常见现象与排查方式现象一示波器没有波形但LCD在计数。说明接收端收到数据并正常处理问题出在P1.0的PWM输出或RC滤波电路。检查P1.0在Proteus里有没有加电压探针RC滤波器的电阻电容值有没有接反。现象二LCD显示乱码或者完全空白。先看发送端初始化是否正确P0口上有没有加上拉电阻排RESPACK-8。51单片机P0口是开漏输出没上拉的话高电平驱动能力不足LCD收不到有效数据。现象三数码管一直没有显示“1”。说明接收端的应答信号没到发送端或者发送端串口中断没收到。检查两个单片机之间P3.0和P3.1是否交叉正确接收端的SBUF 0xAA是否确实执行。现象四波形存在明显台阶感。正弦波采样点只有16个RC滤波后波形本身就是阶梯状的这是正常现象。加大采样表到32点会改善不少但会占更多存储空间发送周期也会变长。5.4 双机通信的扩展验证方式在Proteus仿真跑通之后可以再做两件事验证通信可靠性。第一人为改一个错误波特率比如把发送端TH1从0xFD改成0xFA此时发送端实际波特率约为4800接收端仍按9600接收可以看到LCD上的字节计数开始跳动收到的数据不再是有效正弦波值PWM输出波形变成乱码。这个实验能直观看到波特率不匹配的后果。第二在接收端串口中断里加一个校验逻辑检查收到的采样值是否在合理范围0到255超出则丢弃。真实硬件通信中干扰可能让某个字节出错丢弃非法数据能避免PWM输出毛刺。Proteus仿真里串口几乎不会出错但真实线缆和电源噪声会让这个问题变得很现实。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询