大家好,通过前一期的学习,我们已经对ICD2 仿真烧写器和增强型PIC 实验板的使用方法及学习方式有所了解与熟悉,学会了如何用单片机来控制发光管、继电器、蜂鸣器、按键、数码管、RS232 串口、步进电机、温度传感器等资源,体会到了学习板的易用性与易学性,看了前几期实例,当你实验成功后一定很兴奋,很有成就感吧!现在我们就趁热打铁,再向上跨一步,一起来学习一下I2C 总线的工作原理及使用方法,这样我们可以将一些我们要保存的数据存储到I2C总线的非易失存储器中,实现断电保持的功能,比如:你设置了一个密码,但不至于这个设备断过电以后就要重新设置过,我们可以将密码数据写在非易失存储器里面,还有如汽车的量程表的读数是不断累计的,可以通过不断访问I2C 存储器实现。
一、I2C总线特点
I2C 总线是主从结构,单片机是主器件,存储器是从器件。一条总线可以带多个从器件( 也可以有多主结构),I2C 总线的SDA 和SCL 是双向的,开路门结构,通过上拉电阻接正电源。进行数据传输时,SDA 线上的数据必须在时钟的高电平周期保持稳定。数据线的高或低电平状态只有在SCL 线的时钟信号是低电平时才能改变,如图1 所示。
图1 数据位的有效性规定
在SCL 线是高电平时,SDA 线从高电平向低电平切换表示起始条件;当SCL 是高电平时SDA 线由低电平向高电平切换表示停止条件如图2 所示。
图2 起始和停止信号
发送到SDA 线上的每个字节必须为8 位。
可以由高位到低位传输多个字节。每个字节后必须跟一个响应位(ACK)。响应时钟脉冲由主机产生。主机释放SDA 线从机将SDA 线拉低,并在时钟脉冲的高电平期间保持稳定。如图3 示。当主机接受数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号是由主机对从机的“非应答”来实现的。然后,从机释放SDA 线,以允许主机产生终止或重复起始信号。
图3 字节格式与应答
二、数据帧格式
(1)主机向从机发送数据,数据的传送方向在传输过程中不改变,如图4 所示。
图4 主机向从机发送数据
注:阴影部分:表示主机向从机发送数据;无阴影部分:表示主机向从机读取数据。
A:表示应答;:表示非应答。S:起始信号;P :终止信号。
(2)主机在第一个字节后,立即向从机读取数据,如图5 所示。
图5 主机在第一个字节后立即读从机
(3)复合格式,如图6 所示。传输改变方向的时候,起始条件和从机地址都会被重复,但R/ W-位取反。如果主机接收器发送一个停止或重复起始信号,它之前应该发送了一个不响应信号()。
图6 复合格式
由以上格式可见,无论哪种传输方式,起始信号、终止信号和地址均由主机发出(图中阴影部分),数据字节的传送方向则由寻址字节中的方向位规定,每个字节的传送都必须有应答位(A 或)。
下面通过24C02 实例在增强型PIC 实验板上编程,其硬件原理图如图7 所示,U7 为实验板上24C02 芯片,SDA 与单片机的RB5 口相连,SCL 与单片机RB4 相连,七段数码管D5、D7、D8 组成了显示单元,字形码的数据通过RC 口送入,各数码管的显示片选信号分别不同的RA 口进行控制。
图7 读/ 写AT24C 系列存储器原理图
在MPLab IDE 软件中新建工程,加入源程序代码,同时进行芯片型号的选择和配置位的设置,我们实验所用的芯片型号为PIC16F877A。
编写的程序代码如下,其中程序流程图如图8 所示。
三、软件流程图
图8 I2C 总线读/ 写数据流程图
四、软件代码
/**********/
/* 目标器件:PIC16F877A */
/* 晶振:4.0MHZ */
/* 编译环境:MPLAB V7.51 */
/**********/
/**********
包含头文件
**********/
#include /********** 数据定义 **********/ #define address 0xa #define nop() asm("nop") #define OP_READ 0xa1 // 器件地址以及读取操作 #define OP_WRITE 0xa0 // 器件地址以及写入操作 /********** 端口定义 **********/ #define SCL RB4 #define SDA RB5 #define SCLIO TRISB4 #define SDAIO TRISB5 /********** 共阴LED 段码表 **********/ const char table[]={0xC0,0xF9,0xA4,0x B0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x 83,0xC6,0xA1,0x86,0x8E}; /********** 函数功能: 延时子程序 **********/ void delay() { int i; for(i=0;i<100;i++) {;} } /********** 函数功能: 开始信号 **********/ void start() { SDA=1; nop(); SCL=1; nop();nop();nop();nop();nop(); SDA=0; nop();nop();nop();nop();nop(); SCL=0; nop();nop(); } /********** 函数功能: 停止信号 **********/ void stop() { SDA=0; nop(); SCL=1; nop();nop();nop();nop();nop(); SDA=1; nop();nop();nop();nop(); } /********** 函数功能: 读取数据 出口参数:read_data **********/ unsigned char shin() { unsigned char i,read_data; for(i=0;i<8;i++) { nop();nop();nop(); SCL=1; nop();nop(); read_data《=1; if(SDA == 1) read_data=read_data+1; nop(); SCL=0; } return(read_data); } /********** 函数功能: 向EEPROM 写数据 入口参数:write_data 出口参数:ack_bit **********/ bit shout(unsigned char write_data) { unsigned char i; unsigned char ack_bit; for(i = 0; i < 8; i++) { if(write_data&0x80) SDA=1; else SDA=0; nop(); SCL = 1; nop();nop();nop();nop();nop(); SCL = 0; nop(); write_data 《= 1; } nop();nop(); SDA = 1; nop();nop(); SCL = 1; nop();nop();nop(); ack_bit = SDA; // 读取应答 SCL = 0; nop();nop(); return ack_bit; // 返回AT24Cxx 应答位 } /********** 函数功能: 向指定地址写数据 入口参数:addr,write_data **********/ void write_byte(unsigned char addr, unsigned char write_data) { start(); shout(OP_WRITE); shout(addr); SDAIO = 0; // 在写入数据前SDA 应设置为输出 shout(write_data); stop(); delay(); } /********** 函数功能: 向指定地址读数据 入口参数:random_addr 出口参数:read_data **********/ unsigned char read_random(unsigned char random_addr) { unsigned char read_data; start(); shout(OP_WRITE); shout(random_addr); start(); shout(OP_READ); SDAIO = 1; // 读取数据前SDA 应设置为输入 read_data = shin(); stop(); return(read_data); } /********** 函数功能: 显示子程序 入口参数:k **********/ void display(unsigned char k) { TRISA=0X00; // 设置A 口全为输出 PORTC=table[k/1000]; // 显示千位 PORTA=0xEF; delay(); PORTC=table[k/100%10]; // 显示百位 PORTA=0xDF; delay(); PORTC = table [k/ 10%10] ; // 显示十位 PORTA=0xFB; delay(); PORTC=table[k%10]; // 显示个位 PORTA=0xF7; delay(); } /********** 函数功能: 主程序 **********/ void main() { unsigned char eepromdata; TRISB=0X00; OPTION&=~(1《7); // 设置RB 口内部上拉电阻有效 TRISC=0X00; PORTB=0X00; PORTC=0xff; TRISA=0X00; eepromdata=0; write_byte(0x01,0x55); // 向0x01 地址写入0x55(85) 的数据 delay(); write_byte(0x02,0xaa); // 向0x02 地址写入0xAA(170) 的数据 delay(); eepromdata=read_random(0x02); // 读取其中一个地址内的数据来验证 while(1) { display(eepromdata); } } 编好程序后将编译好的HEX 码通过ICD2仿真烧写器烧入单片机芯片,上电运行,主程序中在0x01 地址写入了“0x55”, 在0x02 地址写入了“0xaa”,然后在while 循环中读出0x02地址的值,也就是我们之前写入的“0x55”,读出后显示在数码管上,我们可以看到数码管显示“170”,即“0xaa”相应的十进制数。 作为初学者的读者一定对有些语句会有点疑问,可以看程序中的注释部份,24c 系列IC 数据手册和源程序相结合来进行分析。
上一篇:基于PIC单片机产生SPWM信号控制逆变桥的方法设计
下一篇:PIC单片机之步进电机
推荐阅读最新更新时间:2024-11-08 18:09
设计资源 培训 开发板 精华推荐
- LT3756IMSE-1 94% 效率 30W 白光 LED 前照灯驱动器的典型应用电路
- DC1547B,用于 LT3757AEDD 升压的升压演示板,3.1V = VIN = 9V,VOUT = 12V @ 1.5A
- 冰墩墩高清修正版
- NCD57253GEVB:NCD57253GEVB - 评估板
- 用于便携式的 12 位、1 通道 DAC
- STM32F030番茄闹钟_V1.0_[20180723_[验证中]
- ESP32开发板v1
- RT9198 300mA、低噪声、超快速CMOS LDO稳压器的典型应用
- 使用 LTC2380IMS-16、16 位、2Msps、低功耗 SAR ADC 的典型应用
- LTC1728-1.8,在加电期间跟踪多个电源