网站开发怎样搭建,网站版块设计教程,中国十大热门网站排名,网络营销百度百科内容
在数码管右3位显示数字#xff0c;从0开始#xff0c;按K1键将数据写入到EEPROM内保存#xff0c;按K2键读取EEPROM内保存的数据#xff0c;按K3键显示数据加1#xff0c;按K4键显示数据清零#xff0c;最大能写入的数据是255#xff1b;
I2C介绍
I2C简介
I2C从0开始按K1键将数据写入到EEPROM内保存按K2键读取EEPROM内保存的数据按K3键显示数据加1按K4键显示数据清零最大能写入的数据是255
I2C介绍
I2C简介
I2CInter-Integrated Circuit总线是由PHILIPS公司开发的两线式串行总线用于连接微控制器及其外围设备是微电子通信控制领域广泛采用的一种总线标准
它是同步通信的一种特殊形式具有接口线少控制方式简单器件封装形式小通信速率较高等优点
I2C总线只有两根双向信号线一根是数据线SDA另一根是时钟线SCL
I2C物理层
它的物理层有如下特点
它是一个支持多设备的总线“总线”指多个设备共用的信号线在一个I2C通讯总线中可连接多个I2C通讯设备支持多个通讯主机及多个通讯从机一个I2C总线只使用两条总线线路一条双向串行数据线(SDA)一条串行时钟线(SCL)数据线即用来表示数据时钟线用于数据收发同步每个连接到总线的设备都有一个独立的地址主机可以利用这个地址进行不同设备之间的访问总线通过上拉电阻接到电源当I2C设备空闲时会输出高阻态而当所有设备都空闲都输出高阻态时由上拉电阻把总线拉成高电平多个主机同时使用总线时为了防止数据冲突会利用仲裁方式决定由哪个设备占用总线具有三种传输模式标准模式传输速率为100kbit/s快速模式为400kbit/s高速模式下可达3.4Mbit/s但目前大多I2C设备尚不支持高速模式连接到相同总线的IC数量受到总线的最大电容400pF限制
I2C总线常用的术语
主机启动数据传送并产生时钟信号的设备从机被主机寻址的器件多主机同时有多于一个主机尝试控制总线但不破坏传输主模式用I2CNDAT支持自动字节计数的模式位I2CRMI2CSTTI2CSTP控制数据的接收和发送从模式发送和接收操作都是由I2C模块自动控制的仲裁是一个在有多个主机同时尝试控制总线但只允许其中一个控制总线并使传输不被破坏的过程同步两个或多个器件同步时钟信号的过程发送器发送数据到总线的器件接收器从总线接收数据的器件
I2C协议层
I2C的协议定义了通信的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节
数据有效性规定
I2C总线进行数据传送时时钟信号为高电平期间数据线上的数据必须保持稳定只有在时钟线上的信号为低电平期间数据线上的高电平或低电平状态才允许变化
如下图 每次数据传输都以字节为单位每次传输的字节数不受限制
起始和终止信号
SCL线为高电平期间SDA线由高电平向低电平的变化表示起始信号SCL线为高电平期间SDA 线由低电平向高电平的变化表示终止信号
如下图 起始和终止信号都是由主机发出的在起始信号产生后总线就处于被占用的状态在终止信号产生后总线就处于空闲状态
应答响应
每当发送器件传输完一个字节的数据后后面必须紧跟一个校验位这个校验位是接收端通过控制SDA数据线来实现的以提醒发送端数据我这边已经接收完成数据传送可以继续进行
这个校验位其实就是数据或地址传输过程中的响应响应包括“应答(ACK)”和“非应答(NACK)”两种信号
作为数据接收端时当设备(无论主从机)接收到I2C传输的一个字节数据或地址后若希望对方继续发送数据则需要向对方发送“应答(ACK)”信号即特定的低电平脉冲发送方会继续发送下一个数据
若接收端希望结束数据传输则向对方发送“非应答(NACK)”信号即特定的高电平脉冲发送方接收到该信号后会产生一个停止信号结束信号传输
应答响应时序图如下 每一个字节必须保证是8位长度
数据传送时先传送最高位MSB每一个被传送的字节后面都必须跟随一位应答位即一帧共有 9 位
由于某种原因从机不对主机寻址信号应答时如从机正在进行实时性的处理工作而无法接收总线上的数据它必须将数据线置于高电平而由主机产生一个终止信号以结束总线的数据传送
如果从机对主机进行了应答但在数据传送一段时间后无法继续接收更多的数据时从机可以通过对无法接收的第一个数据字节的“非应答”通知主机主机则应发出终止信号以结束数据的继续传送
当主机接收数据时它收到最后一个数据字节后必须向从机发出一个结束传送的信号这个信号是由对从机的“非应答”来实现的然后从机释放SDA线以允许主机产生终止信号
这些信号中起始信号是必需的结束信号和应答信号都可以不要
总线的寻址方式
I2C总线寻址按照从机地址位数可分为两种一种是7位另一种是10位
采用7位的寻址字节寻址字节是起始信号后的第一个字节的位定义如下 D7D1位组成从机的地址D0位是数据传送方向位为“0”时表示主机向从机写数据为“1”时表示主机由从机读数据
10位寻址和7位寻址兼容而且可以结合使用10位寻址不会影响已有的7位寻址有7位和10位地址的器件可以连接到相同的I2C总线
以7位寻址为例当主机发送了一个地址后总线上的每个器件都将头7位与它自己的地址比较如果一样器件会判定它被主机寻址其他地址不同的器件将被忽略后面 的数据信号至于是从机接收器还是从机发送器都由R/W位决定的
从机的地址由固定部分和可编程部分组成在一个系统中可能希望接入多个相同的从机从机地址中可编程部分决定了可接入总线该类器件的最大数目如一个从机 的7位寻址位有4位是固定位3位是可编程位这时仅能寻址8个同样的器件即可以有8个同样的器件接入到该I2C总线系统中
数据传输
I2C总线上传送的数据信号是广义的既包括地址信号又包括真正的数据信号
在起始信号后必须传送一个从机的地址7位第8位是数据的传送方向位R/W用“0”表示主机发送写数据W“1”表示主机接收数据R
每次数据传送总是由主机产生的终止信号结束但是若主机希望继续占用总线进行新的数据传送则可以不产生终止信号马上再次发出起始信号对另一从机进行寻址
在总线的一次数据传送过程中可以有以下几种组合方式 1 主机向从机发送数据数据传送方向在整个传送过程中不变 注意有阴影部分表示数据由主机向从机传送无阴影部分则表示数据由从机向主机传送A表示应答A非表示非应答高电平S表示起始信号P表示终止信号 2 主机在第一个字节后立即从从机读数据 3 在传送过程中当需要改变传送方向时起始信号和从机地址都被重复产生一次但两次读/写方向位正好相反
AT24C02芯片介绍
由于51单片机没有硬件I2C接口即使有硬件接口我们通常还是采用软件模拟I2C
主要原因是硬件IIC设计的比较复杂而且稳定性不怎么好程序移植比较麻烦而用软件模拟IIC最大的好处就是移植方便同一个代码兼容所有单片机任何一个单片机只要有IO口不需要特定IO都可以很快的移植过去
芯片简介
AT24C02芯片是一种EEPROM即掉电后数据不丢失的存储芯片
AT24C01/02/04/08/16……是一个1K/2K/4K/8K/16K位串行CMOS内部含有128/256/512/1024/2048个8位字节AT24C01有一个8字节页写缓冲器AT24C02/04/08/16有一个16字节页写缓冲器
该器件通过I2C总线接口进行操作它有一个专门的写保护功能
此芯片具有I2C通信接口芯片内保存的数据在掉电情况下都不丢失所以通常用于存放一些比较重要的数据等
芯片管脚说明
如图所示
数据格式说明
AT24C02器件地址为7位高4位固定为1010低3位由A0/A1/A2信号线的电平决定
因为传输地址或数据是以字节为单位传送的当传送地址时器件地址占7位还有最后一位最低位 R/W用来选择读写方向它与地址无关
其格式如下 开发板该芯片的A0/A1/A2连接到GND所以器件地址为1010000即0x50未计算最低位
如果要对芯片进行写操作时R/W即为0写器件地址命令即为0XA0如果要对芯片进行读操作时R/W即为1此时读器件地址命令为0XA1
开发板上也将WP引脚直接接在GND上此时芯片允许数据正常读写
I2C总线时序 原理图 由图可知I2C总线的时钟线SCL连接P21口数据线SDA连接P20口
由于开发板单片机IO口默认有上拉电阻因此该总线已经默认是高电平无需改动
思路
编写检测4个独立按键按下的函数并返回对应的键值再做出相应的应答
编写数码管显示函数可以选择从第几位开始显示并根据段码数据显示对应数据
根据I2C时序图编写I2C的产生起始、终止、应答、非应答等信号和等待应答信号的函数发送和接收字节数据的函数
根据I2C的操作编写向指定存储器地址发送和接收数据的函数 注意写命令为0XA0读/接收命令为0XA1
在主函数里根据对应的键值响应对应的函数并实现对应要求
编码
User
存放主函数程序
main.c
/** Description: 在数码管右3位显示数字从0开始按K1键将数据写入到EEPROM内保存按K2键读取EEPROM内保存的数据按K3键显示数据加1按K4键显示数据清零最大能写入的数据是255*/
#include public.h
#include 24c02.h
#include key.h
#include smg.h#define EEPROM_ADDRESS 0 // 定义数据存入EEPROM的起始地址void main()
{u8 key_temp 0; // 存储返回的键值u8 save_value 0; // 存储从EEPROM返回的数据u8 save_buf[3]; // 存储数码管要显示的数据while (1){key_temp key_scan(0);if (key_temp KEY1_PRESS){at24c02_write_one_byte(EEPROM_ADDRESS, save_value);}else if (key_temp KEY2_PRESS){save_value at24c02_read_one_byte(EEPROM_ADDRESS);}else if (key_temp KEY3_PRESS){save_value;if (save_value 255)save_value 255;}else if (key_temp KEY4_PRESS){save_value 0;}save_buf[0] save_value / 100;save_buf[1] save_value % 100 / 10;save_buf[2] save_value % 100 % 10;smg_display(save_buf, 6);}
}Public
存放一些通用的程序
public.h
#ifndef _public_H
#define _public_H#include reg52.htypedef unsigned int u16; // 对系统默认数据类型进行重定义
typedef unsigned char u8;void delay_10us(u16 ten_us);
void delay_ms(u16 ms);#endifpublic.c
#include public.h/*** description: 延时函数ten_us1时大约延时10us* param {u16} ten_us 延时倍数* return {*}*/
void delay_10us(u16 ten_us)
{while (ten_us--);
}/**ms延时函数ms1时大约延时1ms**** param {u16} ms 延时倍数* return {*}*/
void delay_ms(u16 ms)
{u16 i, j;for (i ms; i 0; i--)for (j 110; j 0; j--);
}App/24c02
存放写入和输出EEPROM即at24c02芯片的函数程序
24c02.h
#ifndef _24c02_H
#define _24c02_H#include public.hvoid at24c02_write_one_byte(u8 addr, u8 dat); // AT24C02指定地址写数据
u8 at24c02_read_one_byte(u8 addr); // AT24C02指定地址读数据
#endif24c02.c
#include 24c02.h
#include iic.h/*** description: 在AT24CXX指定地址写入一个数据* param {u8} addr 写入数据的目的地址* param {u8} dat 要写入的数据* return {*}*/
void at24c02_write_one_byte(u8 addr, u8 dat)
{iic_start();iic_write_byte(0XA0); // 发送写命令iic_wait_ack();iic_write_byte(addr); // 发送写地址iic_wait_ack();iic_write_byte(dat); // 发送字节iic_wait_ack();iic_stop(); // 产生一个停止条件delay_ms(10);
}/*** description: 在AT24CXX指定地址读出一个数据* param {u8} addr 目标数据的地址* return {u8} 读到的数据*/
u8 at24c02_read_one_byte(u8 addr)
{u8 temp 0;iic_start();iic_write_byte(0XA0); // 发送写命令iic_wait_ack();iic_write_byte(addr); // 发送写地址iic_wait_ack();iic_start();iic_write_byte(0XA1); // 进入接收模式iic_wait_ack();temp iic_read_byte(0); // 读取字节iic_stop(); // 产生一个停止条件return temp; // 返回读取的数据
}App/iic
存放I2C相关的操作程序
iic.h
#ifndef _iic_H
#define _iic_H#include public.h// 定义EEPROM控制脚
sbit IIC_SCL P2 ^ 1; // SCL时钟线
sbit IIC_SDA P2 ^ 0; // SDA数据线// IIC所有操作函数
void iic_start(void); // 发送IIC开始信号
void iic_stop(void); // 发送IIC停止信号
void iic_write_byte(u8 txd); // IIC发送一个字节
u8 iic_read_byte(u8 ack); // IIC读取一个字节
u8 iic_wait_ack(void); // IIC等待ACK信号
void iic_ack(void); // IIC发送ACK信号
void iic_nack(void); // IIC发送NACK信号#endifiic.c
#include iic.h/*** description: 产生IIC起始信号* return {*}*/
void iic_start(void)
{IIC_SDA 1; // 如果把该条语句放在SCL后面第二次读写会出现问题delay_10us(1);IIC_SCL 1;delay_10us(1);IIC_SDA 0; // 当SCL为高电平时SDA由高变为低delay_10us(1);IIC_SCL 0; // 钳住I2C总线准备发送或接收数据delay_10us(1);
}/*** description: 产生IIC停止信号* return {*}*/
void iic_stop(void)
{IIC_SDA 0; // 如果把该条语句放在SCL后面第二次读写会出现问题delay_10us(1);IIC_SCL 1;delay_10us(1);IIC_SDA 1; // 当SCL为高电平时SDA由低变为高delay_10us(1);
}/*** description: 产生ACK应答* return {*}*/
void iic_ack(void)
{IIC_SCL 0;IIC_SDA 0; // SDA为低电平delay_10us(1);IIC_SCL 1;delay_10us(1);IIC_SCL 0;
}/*** description: 产生NACK非应答* return {*}*/
void iic_nack(void)
{IIC_SCL 0;IIC_SDA 1; // SDA为高电平delay_10us(1);IIC_SCL 1;delay_10us(1);IIC_SCL 0;
}/*** description: 等待应答信号到来* return {u8} 1/0接收应答失败/接收应答成功*/
u8 iic_wait_ack(void)
{u8 time_temp 0;IIC_SCL 1;delay_10us(1);while (IIC_SDA) // 等待SDA为低电平{time_temp;if (time_temp 100) // 超时则强制结束IIC通信{iic_stop();return 1;}}IIC_SCL 0;return 0;
}/*** description: IIC发送一个字节* param {u8} dat 要发送的字节* return {*}*/
void iic_write_byte(u8 dat)
{u8 i 0;IIC_SCL 0;for (i 0; i 8; i) // 循环8次将一个字节传出先传高再传低位{if ((dat 0x80) 0)IIC_SDA 1;elseIIC_SDA 0;dat 1;delay_10us(1);IIC_SCL 1;delay_10us(1);IIC_SCL 0;delay_10us(1);}
}/*** description: IIC读一个字节* param {u8} ack ack1时发送ACKack0发送nACK* return {u8} 应答或非应答*/
u8 iic_read_byte(u8 ack)
{u8 i 0, receive 0;for (i 0; i 8; i) // 循环8次将一个字节读出先读高再传低位{IIC_SCL 0;delay_10us(1);IIC_SCL 1;receive 1;if (IIC_SDA)receive;delay_10us(1);}if (!ack)iic_nack();elseiic_ack();return receive;
}App/key
存放独立按键操作程序
key.h
#ifndef _key_H
#define _key_H#include public.h// 定义独立按键控制脚
sbit KEY1 P3 ^ 1;
sbit KEY2 P3 ^ 0;
sbit KEY3 P3 ^ 2;
sbit KEY4 P3 ^ 3;// 使用宏定义独立按键按下的键值
#define KEY1_PRESS 1
#define KEY2_PRESS 2
#define KEY3_PRESS 3
#define KEY4_PRESS 4
#define KEY_UNPRESS 0u8 key_scan(u8 mode);#endifkey.c
#include key.h/*** description: 检测独立按键是否按下按下则返回对应键值* param {u8} mode mode0单次扫描按键mode1连续扫描按键* return {u8} k1到k5的键值1-50表示没有按键按下*/
u8 key_scan(u8 mode)
{static u8 key 1;if (mode)key 1; // 连续扫描按键if (key 1 (KEY1 0 || KEY2 0 || KEY3 0 || KEY4 0)) // 任意按键按下{delay_10us(1000); // 消抖key 0;if (KEY1 0)return KEY1_PRESS;else if (KEY2 0)return KEY2_PRESS;else if (KEY3 0)return KEY3_PRESS;else if (KEY4 0)return KEY4_PRESS;}else if (KEY1 1 KEY2 1 KEY3 1 KEY4 1) // 无按键按下{key 1;}return KEY_UNPRESS;
}App/smg
存放数码管显示控制程序
smg.h
#ifndef _smg_H
#define _smg_H#include public.h#define SMG_A_DP_PORT P0 // 使用宏定义数码管段码口// 定义数码管位选信号控制脚
sbit LSA P2 ^ 2;
sbit LSB P2 ^ 3;
sbit LSC P2 ^ 4;void smg_display(u8 dat[], u8 pos);#endifsmg.c
#include smg.h// 共阴极数码管显示0~F的段码数据
u8 gsmg_code[17] {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71};/*** description: 动态数码管显示函数* param {u8} dat 要显示的数据* param {u8} pos 从左开始第几个位置开始显示范围1-8* return {*}*/
void smg_display(u8 dat[], u8 pos)
{u8 i 0;u8 pos_temp pos - 1;for (i pos_temp; i 8; i){switch (i) // 位选{case 0:LSC 1;LSB 1;LSA 1;break;case 1:LSC 1;LSB 1;LSA 0;break;case 2:LSC 1;LSB 0;LSA 1;break;case 3:LSC 1;LSB 0;LSA 0;break;case 4:LSC 0;LSB 1;LSA 1;break;case 5:LSC 0;LSB 1;LSA 0;break;case 6:LSC 0;LSB 0;LSA 1;break;case 7:LSC 0;LSB 0;LSA 0;break;}SMG_A_DP_PORT gsmg_code[dat[i - pos_temp]]; // 传送段选数据delay_10us(100); // 延时一段时间等待显示稳定SMG_A_DP_PORT 0x00; // 消影}
}编译和结果
按F7编译无错误生成.hex文件使用pz-isp将hex文件下载到单片机
结果在数码管右3位显示数字从0开始按K1键写入数据按K2键读取数据按K3键显示数据加1按K4键显示数据清零最大能写入的数据是255