51单片机模拟IIC从机实战:手把手教你用两块STC89C52实现双向通信(附完整代码)

张开发
2026/4/14 21:04:33 15 分钟阅读

分享文章

51单片机模拟IIC从机实战:手把手教你用两块STC89C52实现双向通信(附完整代码)
51单片机模拟IIC从机实战双机通信系统设计与实现在嵌入式开发领域IIC总线因其简洁的两线制结构和多主机支持特性成为设备间通信的常见选择。当手头只有基础51单片机如STC89C52却需要验证IIC通信协议时完全可以通过软件模拟实现双机交互。本文将详细演示如何用两块STC89C52搭建IIC通信系统其中一块作为主机另一块模拟从机功能实现双向数据交换。1. 硬件准备与电路连接1.1 所需材料清单STC89C52单片机 x2杜邦线若干USB转TTL下载器 x25V电源适配器或USB供电线面包板可选用于固定连接关键连接要点主从机的P3.4(SCL)相互连接主从机的P3.5(SDA)相互连接两片单片机共地GND相连注意实际连接时建议加入4.7KΩ上拉电阻到VCC确保信号稳定性。若使用开发板通常已内置上拉电阻。1.2 引脚定义与寄存器配置对于STC89C52我们使用P3.4和P3.5作为IIC引脚sbit SCL P3^4; // 时钟线 sbit SDA P3^5; // 数据线2. IIC协议核心原理剖析2.1 基础通信时序IIC通信包含几个关键时序节点信号类型时序特征持续时间(典型值)起始信号SCL高电平时SDA下降沿4.7μs停止信号SCL高电平时SDA上升沿4.0μs数据有效SCL高电平期间保持SDA稳定4.7μs数据变化SCL低电平期间允许SDA变化4.7μs2.2 从机地址分配在模拟从机时需要定义设备地址。标准IIC地址为7位我们采用0xA0作为从机写地址0xA1作为读地址#define SLAVE_ADDR_W 0xA0 #define SLAVE_ADDR_R 0xA13. 从机端软件实现3.1 起始/停止信号检测从机需要持续监测总线状态bit I2C_CheckStart(void) { if(SCL !SDA) { delay_us(5); if(SCL !SDA) return 1; } return 0; } bit I2C_CheckStop(void) { if(SCL SDA) { delay_us(5); if(SCL SDA) return 1; } return 0; }3.2 数据接收与发送从机响应主机的关键函数unsigned char I2C_SlaveReceive() { unsigned char i, dat 0; SDA 1; // 释放总线 for(i0; i8; i) { while(!SCL); // 等待时钟高电平 dat 1; if(SDA) dat | 0x01; while(SCL); // 等待时钟低电平 } return dat; } void I2C_SlaveSend(unsigned char dat) { unsigned char i; for(i0; i8; i) { SDA (dat 0x80) ? 1 : 0; dat 1; while(!SCL); // 等待时钟高电平 while(SCL); // 等待时钟低电平 } SDA 1; // 释放总线 }4. 主机端控制逻辑4.1 基本信号生成主机需要主动产生IIC时序信号void I2C_Start() { SDA 1; SCL 1; delay_us(5); SDA 0; delay_us(5); SCL 0; } void I2C_Stop() { SDA 0; SCL 1; delay_us(5); SDA 1; delay_us(5); }4.2 完整读写流程主机向从机写入数据的典型流程发送起始信号发送从机地址写标志等待从机应答发送寄存器地址发送数据字节发送停止信号bit I2C_WriteByte(unsigned char addr, unsigned char dat) { I2C_Start(); I2C_SendByte(SLAVE_ADDR_W); if(!I2C_CheckAck()) { I2C_Stop(); return 0; } I2C_SendByte(addr); if(!I2C_CheckAck()) { I2C_Stop(); return 0; } I2C_SendByte(dat); if(!I2C_CheckAck()) { I2C_Stop(); return 0; } I2C_Stop(); return 1; }5. 系统调试与优化5.1 常见问题排查无应答信号检查地址匹配、上拉电阻、电源电压数据错误调整延时参数确保时序满足规范总线冲突确认从机在非通信时段释放SDA线5.2 性能优化建议使用定时器产生精确延时替代delay_us()添加CRC校验提高通信可靠性实现超时机制避免死锁bit I2C_CheckAck() { unsigned int timeout 1000; SDA 1; // 释放总线 SCL 1; while(SDA timeout--) delay_us(1); SCL 0; return (timeout 0); }6. 完整应用示例实现一个简单的温度监控系统主机定期读取从机采集的数据从机程序片段void main() { unsigned char temp_data 0; while(1) { if(I2C_CheckStart()) { if(I2C_SlaveReceive() SLAVE_ADDR_W) { I2C_SendAck(); // 处理写操作... } else if(I2C_SlaveReceive() SLAVE_ADDR_R) { I2C_SendAck(); I2C_SlaveSend(temp_data); // 模拟温度变化 } while(!I2C_CheckStop()); // 等待停止信号 } // 其他任务... } }主机程序片段void main() { unsigned char recv_data; while(1) { if(I2C_ReadByte(0x00, recv_data)) { printf(Current Temp: %d\n, recv_data); } delay_ms(1000); } }

更多文章