实验:AT24C04基本读写操作
步骤及现象:在下载程序前,选择stc-isp的IRC频率:12MHz。
程序下载完成后,在串口助手界面,HEX模式下,选择波
特率9600,然后点击“打开串口”按钮。这时按独立按键1
在接收缓冲区打印出刚写入的16个数据。
*数据帧格式
I2C总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。
在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用“0”表示主机发送数据(T),“1”表示主机接收数据(R)。每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
在总线的一次数据传送过程中,可以有以下几种组合方式:
a、主机向从机发送数据,数据传送方向在整个传送过程中不变:
注:有阴影部分表示数据由主机向从机传送,无阴影部分则表示数据由从机向主机传送。
A表示应答,A非表示非应答(高电平)。S表示起始信号,P表示终止信号。
b、主机在第一个字节后,立即从从机读数据:
c、在传送过程中,当需要改变传送方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好反相。
总线的寻址
I2C总线协议有明确的规定:采用7位的寻址字节(寻址字节是起始信号后的第一个字节)。
(1)寻址字节的位定义
D7~D1位组成从机的地址。D0位是数据传送方向位,为“0”时表示主机向从机写数据,为“1”时表示主机由从机读数据。
主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,如果相同,则认为自己正被主机寻址,根据R/T位将自己确定为发送器或接收器。
从机的地址由固定部分和可编程部分组成。在一个系统中可能希望接入多个相同的从机,从机地址中可编程部分决定了可接入总线该类器件的最大数目。如一个从机的7位寻址位有4位是固定位,3位是可编程位,这时仅能寻址8个同样的器件,即可以有8个同样的器件接入到该I2C总线系统中。
(2)写入过程
AT24C系列E2PROM芯片地址的固定部分为1010,A2、A1、A0引脚接高、低电平后得到确定的3位编码。形成的7位编码即为该器件的地址码。
单片机进行写操作时,首先发送该器件的7位地址码和写方向位“0”(共8位,即一个字节),发送完后释放SDA线并在SCL线上产生第9个时钟信号。被选中的存储器器件在确认是自己的地址后,在SDA线上产生一个应答信号作为相应,单片机收到应答后就可以传送数据了。
传送数据时,单片机首先发送一个字节的被写入器件的存储区的首地址,收到存储器器件的应答后,单片机就逐个发送各数据字节,但每发送一个字节后都要等待应答。
AT24C系列器件片内地址在接收到每一个数据字节地址后自动加1,在芯片的“一次装载字节数”(不同芯片字节数不同)限度内,只需输入首地址。装载字节数超过芯片的“一次装载字节数”时,数据地址将“上卷”,前面的数据将被覆盖。
当要写入的数据传送完后,单片机应发出终止信号以结束写入操作。写入n个字节的数据格式 :
(3)读出过程
单片机先发送该器件的7位地址码和写方向位“0”(“伪写”),发送完后释放SDA线并在SCL线上产生第9个时钟信号。被选中的存储器器件在确认是自己的地址后,在SDA线上产生一个应答信号作为回应。
然后,再发一个字节的要读出器件的存储区的首地址,收到应答后,单片机要重复一次起始信号并发出器件地址和读方向位(“1”),收到器件应答后就可以读出数据字节,每读出一个字节,单片机都要回复应答信号。当最后一个字节数据读完后,单片机应返回以“非应答”(高电平),并发出终止信号以结束读出操作。
at24c04_i2c.c文件:
#include "at24c0x_i2c.h"
#include
#include
#define AT24_Address 0xA0 // AT24C0X的设备地址
// i2c总线引脚定义
sbit SDA = P7^6; // 数据线
sbit SCL = P7^7; // 时钟线
/********************************************************************
********** 硬件I2C操作部分 **************
*********************************************************************/
#ifdef HardI2C // 硬件I2C操作部分
// 毫秒延时
void DelayI2C(unsigned int xms)
{
unsigned int i, j;
for(i=xms;i>0;i--)
{
for(j=921;j>0;j--);
}
}
// 初始化硬件I2C
void InitI2C()
{
// 访问逻辑地址位于XDATA区域的特殊功能寄存器前需要
// 将 P_SW2(BAH)寄存器的最高位(EAXFR)置 1
P_SW2 = 0x80;
I2CCFG = 0xe0; // 使能I2C主机模式
I2CMSST = 0x00;
P_SW2 |= 0x20;
}
// 清中断
void Wait()
{
while (!(I2CMSST & 0x40));
I2CMSST &= ~0x40;
}
// 起始信号
void Start()
{
I2CMSCR = 0x01; //发送START命令
Wait();
}
// 发送数据
void SendData(char dat)
{
I2CTXD = dat; //写数据到数据缓冲区
I2CMSCR = 0x02; //发送SEND命令
Wait();
}
// 接收应答信号
void RecvACK()
{
I2CMSCR = 0x03; //发送读ACK命令
Wait();
}
// 接收数据
char RecvData()
{
I2CMSCR = 0x04; //发送RECV命令
Wait();
return I2CRXD;
}
// 发送应答信号
void SendACK()
{
I2CMSST = 0x00; //设置ACK信号
I2CMSCR = 0x05; //发送ACK命令
Wait();
}
// 发送非应答信号
void SendNAK()
{
I2CMSST = 0x01; //设置NAK信号
I2CMSCR = 0x05; //发送ACK命令
Wait();
}
// 停止信号
void Stop()
{
I2CMSCR = 0x06; //发送STOP命令
Wait();
}
// 往地址add中,写入数据dat
//void write_24c04(unsigned char add, unsigned char dat)
//{
// Start(); // 发送起始命令
// SendData(AT24_Address); // 发送设备地址+写命令
// RecvACK();
// SendData(add); // 发送存储地址
// RecvACK();
// SendData(dat); // 写数据
// RecvACK();
// Stop(); // 发送停止命令
// DelayI2C(2000); // 等待设备写数据,延时2s
//}
// 从地址add中,读数据
//unsigned char read_24c04(unsigned char add)
//{
// unsigned char dat1;
// Start(); // 发送起始命令
// SendData(AT24_Address); // 发送设备地址+写命令
// RecvACK();
// SendData(add); // 发送存储地址
// RecvACK();
// Start(); // 发送起始命令
// SendData(AT24_Address | 0x01);// 发送设备地址+读命令
// RecvACK();
// dat1 = RecvData(); // 读取数据
// SendNAK();
// Stop(); // 发送停止命令
// return dat1;
//}
// 一次写count个数据(count < 17) , 参数:首地址,数组指针,写入个数
void Mult_Write24c04(unsigned char add, unsigned char *s, unsigned char count)
{
unsigned char i=0;
Start(); // 发送起始命令
SendData(AT24_Address); // 发送设备地址+写命令
RecvACK();
SendData(add); // 发送存储地址
RecvACK();
for(i=0;i0;i--)
// for(j=921;j>0;j--);
//}
/********** i2c启动信号 *************/
/*起始信号:在时钟线处于高电平时,数据线产生下降沿*/
void I2C_Start(void)
{
SDA = 1; // 数据线拉高
Delay5us(); // 延时5微秒
SCL = 1; // 时钟线拉高
Delay5us(); // 延时5微秒
SDA = 0; // 数据线拉低
Delay5us(); // 延时5微秒
}
/********** i2c停止信号 *************/
/*停止信号:在时钟线处于高电平时,数据线产生上升沿*/
void I2C_Stop(void)
{
SDA = 0; // 数据线拉低
Delay5us(); // 延时5微秒
SCL = 1; // 时钟线拉高
Delay5us(); // 延时5微秒
SDA = 1; // 数据线拉低
Delay5us(); // 延时5微秒
}
/********** 发送应答信号 ************/
//void I2C_SendAck(bit ack)
//{
// SDA = ack;
// SCL = 1;
// Delay5us(); // 延时5微秒
// SCL = 0;
// Delay5us(); // 延时5微秒
//}
/********** i2c检测应答 *************/
bit I2C_CheckAck()
{
SCL = 1;
Delay5us(); // 延时5微秒
CY = SDA;
SCL = 0;
Delay5us(); // 延时5微秒
return CY;
}
/********** i2c写一字节 *************/
void I2C_WriteByte(unsigned char byt)
{
unsigned char i;
for(i=0; i<8; i++) // 发送八位数据
{
SCL = 0; // 置低时钟线
Delay5us(); // 延时5微秒
if(byt & 0x80)
SDA = 1; // 发送1
else
SDA = 0; // 发送0
Delay5us(); // 延时5微秒
SCL = 1; // 置高时钟线
Delay5us(); // 延时5微秒
byt <<= 1; // 数据左移一位
}
SCL = 0; // 置低时钟线
Delay5us(); // 延时5微秒
SDA = 1;
Delay5us(); // 延时5微秒
}
/********** i2c读一字节 *************/
unsigned char I2C_ReadByte(void)
{
unsigned char i, da;
for(i=0; i<8; i++) // 八位 循环八次
{
SCL = 1; // 置高时钟线
Delay5us(); // 延时5微秒
da <<= 1; // 数据左移一位
if(SDA)
da |= 1; // 接收1
SCL = 0; // 置低时钟线
Delay5us(); // 延时5微秒
}
return da; // 返回接收到的数据
}
/***** 往at24c04地址add中写入dat ****/
//void write_24c04(unsigned char add, unsigned char dat)
//{
// bit ack;
// I2C_Start(); // 起始信号
// I2C_WriteByte(AT24_Address); // 发送设备地址+写信号
// ack = I2C_CheckAck();
// I2C_WriteByte(add); // 发送存储单元地址
// I2C_CheckAck();
// I2C_WriteByte(dat);
// ack = I2C_CheckAck();
// I2C_Stop();
// AtDelay_ms(2);
//}
/**** 从at24c04地址add中读出数据 ****/
unsigned char read_24c04(unsigned char add)
{
unsigned char temp, ack;
I2C_Start(); // 起始信号
I2C_WriteByte(AT24_Address); // 发送设备地址+写信号
ack = I2C_CheckAck();
I2C_WriteByte(add); // 发送存储单元地址
ack = I2C_CheckAck();
I2C_Start(); // 起始信号
I2C_WriteByte(AT24_Address | 0x01); // 发送设备地址+读信号
ack = I2C_CheckAck();
temp = I2C_ReadByte();
I2C_Stop(); // 停止信号
return temp;
}
// 一次写count个数据(count < 17) , 参数:首地址,数组指针,写入个数
void Mult_Write24c04(unsigned char add, unsigned char *s, unsigned char count)
{
unsigned char i;
bit ack;
I2C_Start(); // 起始信号
I2C_WriteByte(AT24_Address); // 发送设备地址+写信号
ack = I2C_CheckAck();
I2C_WriteByte(add); // 发送存储单元地址
ack = I2C_CheckAck();
for(i=0;i
uart1.c文件:
#include "stc8.h"
#include "uart1.h"
bit busy; // 发送标志位
/************* 串口1初始化配置 *********************/
void Uart1Init(void) // [email protected]
{
SCON = 0x50; // 8位数据,可变波特率
AUXR |= 0x40; // 定时器1时钟为Fosc,即1T
AUXR &= 0xFE; // 串口1选择定时器1为波特率发生器
TMOD &= 0x0F; // 设定定时器1为16位自动重装方式
TL1 = 0xC7; // 设定定时初值(FEC7)H=(65223)D
TH1 = 0xFE; // 65536-12M/9600/4=65223(小数直接舍去)
ET1 = 0; // 禁止定时器1中断
TR1 = 1; // 启动定时器1
ES = 1; // 使能串口1中断
EA = 1; // 开总中断
}
/******************* 发送一个字节 ********************/
void Uart1_Byte(char dat)
{
while (busy); // 当busy=0时,跳出循环,表示可以发送数据了
busy = 1; // 发送标志置1,为下次发送做准备
SBUF = dat; // 开始发送字节
}
/****************** 发送一个字符串 ***************/
//void Uart1_String(char *s)
//{
// while(*s != '\0')
// {
// Uart1_Byte(*s++);
// }
//}
/******************** 重写putchar函数 ***************/
char putchar(char c)
{
Uart1_Byte(c); // printf映射到串口1的发送函数
return c;
}
main.c文件:
// 请先看ReadMe.txt文件
#include "stc8.h"
#include // 如果要使用"printf"需要加上这个头文件,同时在串口中要重写putchar
#include "at24c0x_i2c.h"
#include "uart1.h"
unsigned char dat[16]={0xa1,0xb2,0xc3,0xd4,0xe5,0xf6,0x1a,0x2b,0x3c,0x4d,0x5e,0x6f,0xaa,0xbb,0xcc,0xdd};
unsigned char das[16]={0};
sbit keyCtrol = P0^7; // 独立按键总开关
sbit key = P0^0; // 独立按键1
extern bit busy; // 发送数据标志位,在uart1.c文件中定义
/***** 延时函数,xms为多少就延时多少毫秒 *****/
void Delay_ms(unsigned int xms) // 晶振:12MHz
{
unsigned int i, j;
for(i=xms;i>0;i--)
for(j=921;j>0;j--);
}
/********* 主函数 ***********/
void main()
{
unsigned char i, val;
keyCtrol = 0; // 开独立按键总开关
Uart1Init(); // 串口1初始化
#ifdef HardI2C // 使用硬件I2C时,初始化操作
InitI2C();
#endif
val = 0x00; // AT24C04连存16个数据,首地址0x00。(0x00~0x0f)
Mult_Write24c04(val,dat,16);
while(1)
{
if(key == 0) // 判断独立按键1按下
{
Delay_ms(10); // 延时消抖
if(key == 0) // 再次判断独立按键按下
{
val = 0x00; // at24c04中连读16个数据,首地址0x00。(0x00~0x0f)
Mult_Read24c04(val,das,16);
for(i=0;i<16;i++) // 在串口接收缓冲区打印那16个数据
printf("%c",das[i]);
Delay_ms(200); // 避免按一次独立按键1,打印多次16个数据
}
}
}
}
void uart1_Isr() interrupt 4 // 串口1中断函数
{
if(TI)
{
TI = 0;
busy = 0;
}
}