STM32F4探索者开发板板载24C02EEPROM,自制了一块开发板,需要存储容量更大的24C08A芯片,焊接后发现开发板上能成功运行的程序,在自己的板子上无法运行,24C08和24C02贴片封装一样,制板时也没仔细看24Cxx系列数据手册,照着ExploerSTM32F4_V2.2_SCH设计了电路,发现问题后详细读了24Cxx系列手册,发现电路可以用,万幸!!
写的程序是根据例程实验24 IIC实验改的,用24C08不行,试一下24C02,飞线把24C02换上,下载程序,发现还是不能运行(这并不是24C02的问题,因为用到了LCD显示,有些参数是从24C02中读取显示的,读取有整型浮点型,用的是全新的24C02芯片,读出来转换会有点问题。这其实后来才明白的)。没办法,只能从头开始调,从例程开始,先实现能读写24C02,再用LCD显示。
把例程写进去,可以运行。再试试自己的程序,不行。多次实验突然有了点想法,把24C02先全部写0,再试试能不能运行。结果证明问题就出在这里,从24C02中读出数据转换成int会有问题,导致程序跑飞。24C02调成功后,不甘心24C08不能用,于是把24C02换成了24C08。
认认真真把24Cxx手册读了几遍,在网上搜了搜24C08读写程序,把24Cxx系列的随机读写时序弄明白了,看了看例程中的读写程序,也应该没错,把例程下进去,发现还是有问题,读出数据串口打印总是显示’?’,而且LCD显示不出来,心态快崩了呀。无意间看到有分享用逻辑分析仪观察IIC读写时序的,没有逻辑分析仪,示波器倒是有,又找了找用示波器观察IIC时序的文章。很久没用示波器了,找了找视频学习了下,终于把时序图弄出来了。通过观察IIC读写时序,还真的找出了问题,本来想的是通过观察比较24C02读写时序和24C08读写时序,看看24C08问题到底出在哪。在观察24C02读写时序时,发现产生停止信号时,SCL和SDA几乎是同时变为高,IIC协议中停止信号是SCL为高时,SDA由低变为高,恰当的做法是SCL拉高后,延时一段时间再将SDA拉高,回到例程,找到IIC_Stop()函数,发现延时SDA=1放到了延时之前,改了之后,下载运行,成功读写!!!把24C08全写为0,再把自己的程序下到里边,完美运行!!!
这次经历虽然有点糟心,但问题解决的那刻,心情还是很舒爽的。通过观察时序图,加深了对IIC的理解,用示波器进行故障排除真的是一种很好的方法。下面我就具体介绍24C08读写时序以及用示波器观察IIC时序的操作方法,如有错误还请大家指正。
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送, 高速 IIC 总线一般可达 400kbps 以上。
信号有效性:IIC在传数据时需要满足条件,SCL高电平期间,SDA电平必须稳定,SCL低电平期间,SDA电平可以变化。如图所示:
IIC写时序图:发送方每传完8bit数据等待接收应答信号,图中红线区域即为有效应答。
//IO方向设置
#define SDA_IN() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;} //PB9输入模式
#define SDA_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;} //PB9输出模式
//IO操作函数
#define IIC_SCL PBout(8) //SCL
#define IIC_SDA PBout(9) //SDA
#define READ_SDA PBin(9) //输入SDA
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
//GPIOB8,B9初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
IIC_SCL=1;
IIC_SDA=1;
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
delay_us(4);
IIC_SDA=1;//发送I2C总线结束信号
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
例程中结束:
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
更改后结束:
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
delay_us(4);
IIC_SDA=1;//发送I2C总线结束信号
}
更改后的才是与时序图对应,也是24C08不能读的问题所在。
4. 发送接收一个字节:
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
IIC用到的基本读写函数写完了,和24C02/08通信,实现特定地址的读写还需要按照芯片的要求写具体函数。
AT24C01A/02/04/08A/16A大小分别为128/256/512/1024/2048字节,掌握24Cxx地址组成才能实现指定地址的读写。封装引脚图:
需要注意的是A2、A1、A0三个脚,手册中是这样描述的:
对于24C02,大小为256字节,8位寻址,A0、A1、A2作为硬件寻址;而对于24C08A,1024字节,需要10位才能寻址,A0、A1用来页寻址,A2作为硬件选址,也就是说一个设备可以搭载2块24C08A,8块24C02。下图为设备地址表:
设备地址高四位固定为1010,A2、A1、A0与硬件电路连接有关,P0、P1、P2是页地址,最后一位代表读写操作,1表示读,0表示写。只有确定了设备地址(DEVICE ADDRESS)和字节地址(WORD ADDRESS),才能往指定地址读写。
以24C08为例,1K字节寻址需要10位,要写的地址位Addr,AddrH=(Addr/256),AddrL=(Addr%256),AddrH与设备地址里的A1、A0对应,假设A2位为0表示选中该片24C08(可以搭载2片24C08),高四位为1010,最低位为0表示写。则DEVICE ADDRESS可由此计算得来:0XA0+((WriteAddr/256)<<1)
字节地址:WORD ADDRESS=Addr%256
//在AT24CXX指定地址写入一个数据
//WriteAddr :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0XA0); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr>>8);//发送高地址
}else IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //发送器件地址0XA0,写数据
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr%256); //发送低地址
IIC_Wait_Ack();
IIC_Send_Byte(DataToWrite); //发送字节
IIC_Wait_Ack();
IIC_Stop();//产生一个停止条件
delay_ms(20);
}
//在AT24CXX指定地址读出一个数据
//ReadAddr:开始读数的地址
//返回值 :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
u8 temp=0;
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0XA0); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr>>8);//发送高地址
}else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //发送器件地址0XA0,写数据
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr%256); //发送低地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(0XA1+((ReadAddr/256)<<1));
// IIC_Send_Byte(0XA1); //进入接收模式
IIC_Wait_Ack();
temp=IIC_Read_Byte(0);
IIC_Stop();//产生一个停止条件
return temp;
}
用的是RIGOL DS1102D示波器,基本忘了怎么用了,在网上找视频学了学。
示波器实物图:
CH1接SDA,CH2接SCL,CHI电压幅度调节为2V,CH2电压幅度调节为2V,时间调整为100或200us(根据IIC频率设置,设置不当可能观察不到波形或观察不到完整的波形)。触发模式选择边沿触发,边沿类型选下降沿触发,触发信源选择CH1(CH1接SDA,IIC通讯时会先发开始信号,、即SCL为高时,SDA由高变为低,最先变化的时SDA,且第一个边沿为下降沿),触发方式为单次触发,边沿触发需要设定TRIGGER值(电压越过该值认为是出现边沿),值在1.5-3.0V之间即可。设置完成后,连接SCL、SDA就可以进行观察了。
可以将每次的波形存储到U盘中,用WFMReader软件(下载地址)如下图,打开保存的wfm文件观察时序。波形显示后调节时间分度为50us,再保存到U盘中,会有更好的观察效果。
主程序中通过按键控制往24C08地址0写入/读出‘E’,波形图如下:
如图所示,SCL为蓝色,SDA为黄色,红色区域表示开始信号(SCL为高,SDA由高变低),之后在SCL为高时发送一位数据,发送8位后等待产生应答信号,右图中可以看出发送的数据一次为1010 0000,也就是设备地址0xA0,绿色区域为应答信号,参考IIC_Wait_Ack(),收到应答后就可以发送下一数据了。
第二个要发送的数据是字节地址,发送的是00000000,等待应答后发送要写的‘E’,波形图应该是发送:01000101。
单字节写的波形图到此结束,跟程序代码是相对应的,可以对比代码看波形图 。
单字节写弄明白后,单字节读波形图也就很容易理解了。
开始信号,发送设备地址写(最低位为0),等应答。
收到应答后发送字节地址,再次发送开始信号。
发送设备地址读(最低位为1),进入接受模式。
读出来的数据是:01000101,是‘E’的ASCII码,非应答信号是24C08发出的,接收完数据后,结束信号是STM32发送的,一次完整的字节读到此结束。
IIC时序图、24Cxx读写时序图、程序代码、波形图是相对应的,仔细对比学习后,相信大家会对IIC读写过程会有更加深入的了解。