I2C系列(三):软件模拟I2C读写24C04

一.目标

        PC 端的串口调试软件通过 RS-485 与单片机通信,控制单片机利用软件模拟 I2C 总线对 EEPROM(24C04) 进行任意读写。 

二.RS-485简述

        在工业控制领域,传输距离越长,要求抗干扰能力也越强。由于 RS-232 无法消除共模干扰,且传输距离只有 15m 左右,无法满足要求。
        工业标准组织提出了 RS-485 接口标准。 RS-485 标准采用差分信号传输方式,因此具有很强的抗共模干扰能力。 RS-485 接口芯片 SP485E 的封装及引脚说明如下图 1。

        RS-485 的逻辑电平为:

①当 A 的电位比 B 高 200mV 以上时, 为逻辑 1;

②当 B 的电位比 A 电位高200mA 以上时为逻辑 0,传输距离可达 1200m。由于是差分传输,因此无需公共地,在 RS-485 总线上仅需连接两根线 A 和 B。
       单片机与 RS-485 接口芯片的电路连接图如下图 2。

三.控制命令定义

        定义如下命令:

①c——串口接收数据函数初始化
②s——单片机将接收到的数据发送到串口调试终端显示,以确认单片机是否已正确接收数据
③w——将接收缓冲区 wbuf 中的数据写入 EEPROM 中
④r——将刚才写入 EEPROM 中的数据读出到缓冲区 rbuf 中,并发送到串口调试终端显示

四.C代码

        本代码注重功能实现,以期达到理解I2C协议和24C04读写方法的目的。实际项目还须考虑代码质量,如可读性、可维护性等。

4.1 I2C基础时序模拟

4.1.1 引脚初始化

void i2cinit(void)
{sdaout;/*引脚输出模式*/sclout;sda = 1; /*释放总线*/scl = 1;
}

4.1.2 延时函数

1.SCL 时序控制延时函数

        78K0指令的最短时钟周期为2个,一条NOP指令即为2个时钟周期,若使用内部8MHz时钟,则执行一条NOP指令需0.25us。

void delay(void)
{UCHAR i;for(i = 0;i < NOP_num;i++)NOP();
}

2.长延时函数

void delay_long(UINT a)
{UINT i,j;for(i = 0;i < a;i++)for(j = 0;j < 100;j++);
}

4.1.3 起始信号模拟

        SCL 线为高电平期间, SDA 线由高电平向低电平的变化表示起始信号。

void i2cstart(void)
{sdaout;	sclout;/*1.初始化SDA为高电平:在SCL低电平期间拉高SDA*/scl = 0;delay();sda = 1;delay();/*2.模拟一个起始信号*/scl = 1;delay();sda = 0;delay();
}

4.1.4 停止信号模拟

        SCL 线为高电平期间, SDA 线由低电平向高电平的变化表示终止信号。

void i2cstop(void)
{sdaout;sclout;/*1.初始化SDA为低电平:在SCL低电平期间拉低SDA*/scl=0;delay();sda=0; delay();/*2.模拟一个停止信号*/scl=1;delay();sda=1;delay();
} 

4.1.5 应答信号模拟

        每一个字节必须保证是 8 位长度。数据传送时,先传送最高位(MSB),每一个被传送的
字节后面都必须跟随一位应答位(即一帧共有 9 位)。

         在第 9 个时钟信号的高电平期间:若 SDA为 0,则为应答;若 SDA 为 1,则为非应答。

1.主机发送应答信号

void i2c_ack_write(UCHAR ack)
{sdaout;sclout;/*1.初始化SDA为应答信号/非应答电平:在SLC低电平期间改变SDA*/scl=0; /*sda 变化前,先将 scl 置 0,一个时钟周期的开始*/delay();if(ack == 1)sda = 1;/*1,不应答从机,通知从机释放 sda*/else sda = 0;/*0,应答从机*/delay();/*2.scl 置高,通知从机读取SDA*/scl= 1;delay();
} 

2.读取从机的应答信号

UCHAR i2c_ack_read(void)
{UCHAR sack;sdaout;sclout;/*1.释放SDA,让从机控制:在SLC低电平期间拉高SDA*/scl=0;delay();sda=1;delay();sdain; /*sda 设置为输入模式,以检测从机的应答信号*//*2.scl 置高,读取从机的应答信号*/scl=1delay();if(sda==1)sack=1;/*从机无应答*/else sack=0;/*从机应答*/return sack; 
} 

4.1.6 写一个字节数据

        只是数据段传送的操作,即开始与应答之间的操作,但不包括开始和应答。

void writebyte(UCHAR dat)
{UCHAR temp=0;UCHAR i;sdaout;sclout;for(i = 0;i < 8;i++){/*1.在SCL低电平时,准备好SDA*/temp = dat&0x80;scl = 0; delay();if(temp == 0)sda = 0;else sda = 1; delay();/*2.拉高SCL,通知从机读SDA*/scl = 1;delay();dat = dat << 1;}/*3.释放SDA总线*/scl = 0;delay();sda = 1;delay();
}

4.1.7 读一个字节数据

        只是数据段传送的操作,即开始与应答之间的操作,但不包括开始和应答。

UCHAR readbyte(void)
{UCHAR i;UCHAR temp = 0;sdaout;sclout;/*1.主机释放SDA,并将SDA设为输入模式*/scl = 0;delay();sda = 1;delay();sdain; for(i = 0;i < 8;i++){/*1.拉低SCL,通知从机发送数据*/scl=0;delay();/*2.拉高SCL,读取SDA*/scl=1;delay();if(sda == 1)temp = (temp << 1) | 0x01;elsetemp = (temp << 1) | 0x00;}return temp;
}

4.2 I2C读写数据

4.2.1 向任意地址写单字节数据

        包括数据传送、应答信号(从机),但不包括开始和停止信号。

UCHAR writebyte_to_anyaddr(UCHAR addr,UCHAR dat)
{UCHAR sack;/*1.写器件地址*/writebyte(deviceaddr0);	sack = i2c_ack_read(); if(sack	==	1)return 1;/*2.写存储单元地址*/writebyte(addr);sack = i2c_ack_read(); if(sack == 1)return 1;/*3.发送数据*/writebyte(dat);	sack = i2c_ack_read();if(sack == 1)return 1;elsereturn 0;
} 

4.2.2 从任意地址读单字节数据

        包括数据传送、应答信号,但不包括开始和停止信号。

UCHAR readbyte_from_anyaddr(UCHAR addr)
{UCHAR temp = 0;UCHAR sack = 0;/*1.写器件地址*/writebyte(deviceaddr0);sack = i2c_ack_read();if(sack == 1)return 1;/*2.写存储单元地址*/writebyte(addr);sack = i2c_ack_read();if(sack == 1)return 1;/*3.重置开始,改为读方向*/i2cstart();	writebyte(deviceaddr1);sack = i2c_ack_read();if(sack==1)return 1;/*4.读数据*/temp=readbyte();return temp; 
} 

4.2.3 写n字节数据到任意地址

UCHAR writenbytes_to_anyaddr(UCHAR addr,UCHAR* buf,UCHAR buflen)
{UCHAR i = 0;UCHAR sack = 0;i2cstart();writebyte_to_anyaddr(addr,buf[i]);/*向指定地址写一个数据*/for(i = 1;i < buflen;i++){if(BYTES_PER_PAGE == 8)/*页边界处理:页内字节数为 8*/{if((addr & 0x07) == 0x07)/*地址的低 3 位为“111”时,主机须送下一页的起始地址*/{i2cstop(); /*到页边界时,主机须发停止信号,通知从机结束当前页的传送*/delay_long(1);/*结束信号与开始信号之间须延时*/i2cstart();/*开始*/writebyte(deviceaddr0); /*送器件地址,写*/sack = i2c_ack_read(); /*检测从机应答*/if(sack==1)return 1;/*无应答,返回 1*/writebyte(addr + 1);/*写数据地址,地址值加 1*/sack=i2c_ack_read();/*检测从机应答*/if(sack == 1)return 1;/*无应答,返回 1*/}}if(BYTES_PER_PAGE == 16)/*页边界处理:页内字节数为 16*/{if((addr&0x0f)==0x0f)/*地址的低 4 位为“1111”时,主机须送下一页的起始地址*/{i2cstop();delay_long(1);i2cstart();writebyte(deviceaddr0);i2c_ack_read();writebyte(addr+1);sack=i2c_ack_read();if(sack==1)return 1;}}writebyte(buf[i]); /*页内写字节,地址自动加 1*/sack=i2c_ack_read();if(sack==1)return 1;addr++;/*addr 始终等于当前写入数据的地址,以便进行页边界判断*/}return 0; /*返回 0,写成功*/
} 

4.2.4 从任意地址读n字节数据

UCHAR readnbytes_from_anyaddr(UCHAR addr,UCHAR* buf,UCHAR buflen)
{UCHAR i=0;UCHAR sack;i2cstart();/*开始*/writebyte(deviceaddr0);/*送器件地址,写*/sack=i2c_ack_read();/*检测从机应答*/if(sack==1)return 1;writebyte(addr);/*送数据地址*/sack=i2c_ack_read();/*检测从机应答*/if(sack==1)return 1;i2cstart();/*开始*/writebyte(deviceaddr1);/*送器件地址,读*/sack=i2c_ack_read();/*检测从机应答*/if(sack==1)return 1;for(i=0;i<buflen;i++){buf[i]=readbyte(); /*读一字节数据到指定的缓冲区中*/if(i==(buflen-1))i2c_ack_write(M_NACK); /*读完后,主机不应答,通知从机释放 sda*/elsei2c_ack_write(M_ACK); /*若未读完,主机应答,继续读*/}return 0;/*返回 0,读成功*/
}

4.3 串口中断服务函数

4.3.1 接收中断处理函数

__interrupt void MD_INTSR0(void)
{UCHAR err_type;UCHAR rx_data;err_type = ASIS0;rx_data = RXB0;P7=rx_data;if( err_type & 0x07 ){CALL_UART0_Error( err_type );return;}if(rx_data=='c') /*接收到 c 命令, flag 置 1*/{flag=1;return;}if(rx_data=='s') /*接收到 s 命令, flag 置 2*/{flag=2;return;}if(rx_data=='w') /*接收到 w 命令, flag 置 4*/{flag=4;return;}if(rx_data=='r')/*接收到 r 命令, flag 置 5*/{flag=5;return;}if(gUart0RxLen > gUart0RxCnt)/*正常接收数据,非命令*/{*gpUart0RxAddress = rx_data;gpUart0RxAddress++;gUart0RxCnt++;}elseflag=3;/*接收缓冲区满, flag 置 3*/
} 

4.3.2 发送中断处理函数

__interrupt void MD_INTST0(void)/*发送中断处理函数*/
{if( gUart0TxCnt > 0 ){TXS0 = *gpUart0TxAddress;gpUart0TxAddress++;gUart0TxCnt--;}else /*发送完毕*/{P1.2=0;/*将 485 设置为接收模式*/SRMK0=0;/*开接收中断*/}
}

4.4 宏定义和声明

extern volatile USHORT gUart0RxCnt; /*接收数据统计*/
extern UCHAR flag;/*串口调试软件终端发送的命令标识*/#define NOP_num 60/*延时函数中 NOP()指令的执行次数*/
#define scl P6.0 /*开漏输出引脚 P6.0 作为时钟引脚*/
#define sda P6.1 /*开漏输出引脚 P6.1 作为数据引脚*/
#define sclout PM6.0=0 /*时钟引脚输出模式*/
#define sdaout PM6.1=0 /*数据引脚输出模式*/
#define sclin PM6.0=1 /*时钟引脚输入模式*/
#define sdain PM6.1=1 /*数据引脚输入模式*/
#define deviceaddr0 0xa0 /*器件地址宏定义,输出*/
#define deviceaddr1 0xa1 /*器件地址宏定义,输入*/
#define M_NACK 1 /*主机无应答常量定义*/
#define M_ACK 0 /*主机应答常量定义*/
#define BYTES_PER_PAGE 16 /*EEPROM 页内字节数宏定义*/void delay(void); /*SCL 时序控制延时函数*/
void delay_long(UINT a);/*长延时函数*/void i2cinit(void);/*IIC 引脚初始化函数*/void i2cstart(void);/*启动函数*/
void i2cstop(void);/*停止函数*/
void i2c_ack_write(UCHAR);/*主机应答处理函数*/
UCHAR i2c_ack_read(void);/*从机应答处理函数*/void writebyte(UCHAR dat);/*写一个字节函数,只是数据段传送的操作,即开始与应答之间的操作,但不包括开始和应答*/
UCHAR readbyte(void);/*读一个字节函数,只是数据段传送的操作,即开始与应答之间的操作,但不包括开始和应答*/UCHAR writebyte_to_anyaddr(UCHAR addr,UCHAR dat);/*向任意地址写一个数,包括开始信号、数据传送、应答信号(从机),但不包括停止信号*/
UCHAR readbyte_from_anyaddr(UCHAR addr);/*从任意地址中读一个数,包括开始信号和数据传送,但不包括应答信号(主机)和停止信号*/UCHAR writenbytes_to_anyaddr(UCHAR addr,UCHAR* buf,UCHAR buflen);/*向任一地址开始的连续多个储存单元写一串数据*/
UCHAR readnbytes_from_anyaddr(UCHAR addr,UCHAR* buf,UCHAR buflen);/*从任一地址开始的连续多个储存单元读出一串数据*/

4.5 主处理函数main

void main( void )
{UCHAR wbuf[256]={0};/*待写到 EEPROM 中的数据缓冲区*/UCHAR rbuf[256]={0};/*从 EEPROM 中读出的数据缓冲区*/UCHAR wend[10]={'w','r','i','t','e',' ','e','n','d','!'};/*写完成提示*/UCHAR temp=0;UCHAR i;UCHAR wnum=0;i2cinit();/*IIC 引脚初始化*/UART0_ReceiveData( wbuf,256);/*串口接收数据函数初始化*/P1.2=0;/*RS-485 使能引脚,设置为数据接收模式*/UART0_Start(); /*启动串口*/P7.0=0; /*程序运行 LED 指示*/while (1){if(flag == 1)/*c 命令,串口接收数据函数初始化*/{UART0_ReceiveData( wbuf,256);flag=0;}if(flag == 2)/*s 命令,单片机将接收到的数据发送到串口调试终端显示*/{SRMK0=1;/*屏蔽接收中断*/P1.2=1; /*单片机设置为数据发送模式*/delay_long(1);flag=0;temp=(UCHAR)gUart0RxCnt;/*强制类型转换*/UART0_SendData(wbuf,temp);/*将接收的数据发送到串口调试终端*/}if(flag==3)/*接收缓冲区满,初始化串口接收函数,覆盖原来的数据*/{flag=0;UART0_ReceiveData( wbuf,256);}if(flag==4)/*w 命令,将 wbuf 中的数据写入 EEPROM 中*/{flag=0;DI();/*写过程,禁止中断*/temp=(UCHAR)gUart0RxCnt;writenbytes_to_anyaddr(0,wbuf,temp);i2cstop();delay_long(2);i2cstart();writebyte_to_anyaddr(250, temp);i2cstop();EI();/*开中断*/SRMK0=1;/*屏蔽接收中断*/P1.2=1; /*设置为发送模式*/UART0_SendData( wend,10);/*发送写结束字符串到串口调试终端显示*/}if(flag==5)/*r 命令,将刚才写入到 EEPROM 中的数据读出到 rbuf 中,并发送到串口调试终端显示*/{flag=0;DI();/*读过程中,禁止中断*/i2cstart();wnum=readbyte_from_anyaddr(250);i2cstop();delay_long(1);readnbytes_from_anyaddr(0,rbuf,wnum); /*读数据*/i2cstop();EI();/*开中断*/SRMK0=1;P1.2=1;delay_long(1);UART0_SendData(rbuf,wnum);}}
}

五.测试结果

        发送 “1,2,3....18” 共 18 个数给单片机,让单片机以页写的方式写入 24C04 中。

        结果如下:
1) 若不进行页边界处理,则 17 和 18 两个数覆盖 01 和 02,即为页上卷,且说明页内字节数为
16字节。(如下图 )

2)进行页边界处理后,结果如下图 :
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/766597.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSS3 中的盒模型:标准与IE盒模型的差异

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

2、Jenkins持续集成-gitlab安装和源码上传

文章目录 1、Gitlab代码托管服务器安装2、源代码上传托管 环境&资源准备 统一采用VMware中安装CentOS7&#xff0c;安装教程&#xff0c;统一设置静态IP资源包都存在于我的资源里面 资源版本&位置 名称机器IP软件代码托管服务器192.168.2.100Gitlab-12.4.2持续集成服…

Polar 2024春季个人挑战赛 Jay17 WP

Polar 2024春季个人挑战赛 Rank&#xff1a;7 【WEB】机器人 开题 起手敏感文件robots.txt 【WEB】PHP反序列化初试 最简单的php反序列化 POC&#xff1a; <?php class Easy{public $name;public function __wakeup(){echo $this->name;} } class Evil{public $evi…

手机实时监控电脑屏幕(手机可以看到电脑在干什么吗)

已经2024年了&#xff0c;假如你还在问我&#xff0c;手机可以看到电脑在干什么吗&#xff0c;有没有手机实时监控电脑屏幕的系统。 那么证明&#xff0c;你可能已经out 了。 现代科技告诉发展的态势下&#xff0c;这种技术已经很成熟了。 域智盾软件就可以实现这种效果↓我们…

Chapter 2. A simple interconnection network

A Simple Interconnection Network 一个简单的互连网络 2.1 网络规范和约束2.2 拓扑2.3 路由2.4 流量控制2.5 路由器设计性能分析 A Simple Interconnection Network 一个简单的互连网络 我们将研究简单互连网络的架构和设计&#xff0c;以提供全局视图。我们将研究最简单的…

【数据结构初阶】之堆(C语言实现)

数据结构初阶之堆&#xff08;C语言实现&#xff09; &#x1f30f; 堆的概念&#x1f30f; 堆的模拟实现&#x1f413; 堆的结构和方法接口&#x1f413; 堆的方法的模拟实现&#x1f64a; 堆的初始化&#x1f64a; 堆的构建&#x1f64a; 堆的插入&#x1f64a; 向上调整&…

【SysBench】OLTP 基准测试示例

前言 本文采用 MySQL 沙盒实例作为测试目标&#xff0c;使用 sysbench-1.20 对其做 OLTP 基准测试。 有关 MySQL 沙盒的更多信息&#xff0c;请参阅 玩转 MySQL Shell 沙盒实例&#xff0c;【MySQL Shell】6.8 AdminAPI MySQL 沙盒 。 1、部署一个 MySQL 沙盒实例 使用 mysq…

指尖论文怎么用 #经验分享#学习方法

指尖论文是一款优秀的论文写作、查重降重工具&#xff0c;被广泛认可为高效、可靠、方便的辅助工具。那么&#xff0c;如何正确地使用指尖论文呢&#xff1f; 首先&#xff0c;用户需要注册一个指尖论文的账号&#xff0c;并登录到平台上。注册过程非常简单&#xff0c;只需要输…

瑞芯微RK3576|触觉智能:开启科技新篇章

更多产品详情可关注深圳触觉智能官网&#xff01; “瑞芯微&#xff0c;创新不止步&#xff01;”——全新芯片RK3576即将震撼登场。指引科技风潮&#xff0c;创造未来无限可能&#xff01;这款芯片在瑞芯微不断创新和突破的道路上&#xff0c;不仅是对过往成就的完美延续&…

V R元宇宙平台的未来方向|V R主题馆加 盟|游戏体验馆

未来&#xff0c;VR元宇宙平台可能会呈现出以下发展趋势和可能性&#xff1a; 全面融合现实与虚拟世界&#xff1a; VR元宇宙平台将更加无缝地融合现实世界和虚拟世界&#xff0c;用户可以在虚拟环境中进行各种活动&#xff0c;与现实世界进行互动&#xff0c;并且体验到更加逼…

FileZilla 链接服务器提示 20 秒连接超时

FileZilla 有个默认设置是如果 20 秒没有数据的话会自动中断链接。 Command: Pass: **************** Error: Connection timed out after 20 seconds of inactivity Error: Could not connect to server修改配置 这个配置是可以修改的&#xff0c;修改的步骤为&#xff1a; …

数据可视化-ECharts Html项目实战(5)

在之前的文章中&#xff0c;我们学习了如何设置滚动图例&#xff0c;工具箱设置和插入图片。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢 数据可视化-ECharts…

Vue3 + Django 前后端分离项目实现密码认证登录

1、功能需求 通常中小型前后端项目&#xff0c;对安全要求不高&#xff0c;也可以采用密码认证方案。如果只用django来实现非常简单。采用 Vue3 前后端分离架构&#xff0c;实现起来稍繁琐一点&#xff0c;好处是可以利用各种前端技术栈&#xff0c;如element-plus UI库来渲染…

Git Commit 提交规范,变更日志、版本发布自动化和 Emoji 提交标准

前言 Git Commit 是开发的日常操作, 一个优秀的 Commit Message 不仅有助于他人 Review, 还可以有效的输出 CHANGELOG, 对项目的管理实际至关重要, 但是实际工作中却常常被大家忽略&#xff0c;希望通过本文&#xff0c;能够帮助大家规范 Git Commit&#xff0c;并且展示相关 …

Mongodb入门到入土,安装到实战,外包半年学习的成果

这是我参与「第四届青训营 」笔记创作活动的的第27天&#xff0c;今天主要记录前端进阶必须掌握内容Mongodb数据库,从搭建环境到运行数据库,然后使用MongodB; 一、文章内容 数据库基础知识关系型数据库和非关系型数据库为什么学习Mongodb数据库环境搭建及运行MongodbMongodb命…

【进程概念】启动进程 | 查看进程 | 创建进程

目录 启动进程 查看进程 方法1&#xff1a;/proc 方法2&#xff1a;查看脚本 ​方法3&#xff1a;系统调用获取进程标示符❗❗ 终止进程 创建进程&#xff08;主fork) &#x1f642;查看父子进程的pid &#x1f642;进程创建/执行/终止 &#x1f642;多次重新启动进…

STM32 CAN的工作模式

STM32 CAN的工作模式 正常模式 正常模式下就是一个正常的CAN节点&#xff0c;可以向总线发送数据和接收数据。 静默模式 静默模式下&#xff0c;它自己的输出端的逻辑0数据会直接传输到它自己的输入端&#xff0c;逻辑1可以被发送到总线&#xff0c;所以它不能向总线发送显性…

鸿蒙开发实战:网络请求库【axios】

简介 [Axios] &#xff0c;是一个基于 promise 的网络请求库&#xff0c;可以运行 node.js 和浏览器中。本库基于[Axios]原库v1.3.4版本进行适配&#xff0c;使其可以运行在 OpenHarmony&#xff0c;并沿用其现有用法和特性。 http 请求Promise APIrequest 和 response 拦截器…

Stable Diffusion实现光影字效果

昨天下午有人在群里发光影图片&#xff0c;大家都觉得很酷&#xff0c;我没怎么在意。直到早上我在小红书看到有人发同款图片&#xff0c;只是一晚上的时间点赞就超过了8000&#xff0c;而且评论数也很高&#xff0c;也可以做文字定制变现。研究了一下发现这个效果不难实现&…

数据结构/C++:哈希表

数据结构/C&#xff1a;哈希表 哈希表概念哈希函数直接定址法除留余数法 哈希冲突闭散列 - 开放定址法基本结构查找插入删除总代码展示 开散列 - 哈希桶基本结构查找插入删除代码展示 哈希表概念 在顺序表中&#xff0c;查找一个数据的时间复杂度为O(N)&#xff1b;在平衡树这…