通信标准 | IIC |
---|---|
通信方式 | 同步通信 |
引脚说明 | SCL:同步时钟;SDA数据输入/输出端 |
通信方向 | 半双工 |
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。经常IIC和SPI接口被认为指定是一种硬件设备,但其实这样的说法是不尽准确的,严格的说他们都是人们所定义的软硬结合体,分为物理层(四线结构)和协议层(主机,从机,时钟极性,时钟相位)。
常见物理接口:
信号 | 说明 |
---|---|
开始信号(必要) | SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。 |
结束信号(可不要) | SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。 |
应答信号(可不要) | 接收方接收到 8bit 数据后,向发送方发出特定的低电平脉冲(应答信号),表示已收到数据。若未收到应答信号,则判断通信失败。 |
具体如下:
起始与终止信号 | |
---|---|
应答信号(以主发从收为例) |
参考 ---- [https://www.cnblogs.com/zhangjiansheng/p/7738390.html]
参考 1---- [https://blog.csdn.net/bluewhaletech/article/details/37876111]
参考2 ---- [https://www.cnblogs.com/aaronLinux/p/6218660.html]
写在前面:IIC有根据历史原因分有硬件IIC与软件模拟IIC两种(有兴趣自己找找),以下的使用以软件模拟IIC为主(后序有机会再弄硬件IIC)。附两者区别:
是什么:
二者区别:
参考1 ---- [https://blog.csdn.net/faihung/article/details/59146937]
参考2 ---- [https://www.cnblogs.com/wy9264/p/11863239.html]
24C02是什么:【百度百科-24C02】
STM32F1与24C02连接图:
24C02引脚:
引脚名 | 说明 | 引脚名 | 说明 |
---|---|---|---|
A0-A2 | 地址输入线 | WP | 写保护 |
SCL | 时钟线 | SDA | 数据线 |
上面提到从机地址由7位组成,而地址输入线只有三位,因此完整的地址组成需看数据手册:
前面的xK代表不同大小的24Cxx(型号)
由于是使用软件模拟IIC,因此需要自己加入两个驱动程序,IIC驱动程序+从机设备(24C02)驱动程序,将两个驱动程序分别封装好加入项目即可。
·宏定义(myiic.h)
·下列程序(myiic.c)
①IIC初始化
②产生 IIC 起始信号
③产生 IIC 停止信号
④等待应答信号到来(返回值:1->接收应答失败;0->接收应答成功)
⑤产生 ACK 应答(返回从机有无应答:1->有应答;0->无应答)
⑥不产生 ACK 应答(ack=1 时,发送 ACK;ack=0,发送 !ACK)
⑦发送一个字节
⑧读 1 个字节
·宏定义(myiic.h)
#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"
//IO方向设置
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//IO操作函数
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
#endif
·下列程序(myiic.c)
①IIC初始化
void IIC_Init(void)
{
RCC->APB2ENR|=1<<3; //先使能外设 IO PORTB 时钟
GPIOB->CRL&=0X00FFFFFF; //PB6/7 推挽输出
GPIOB->CRL|=0X33000000;
GPIOB->ODR|=3<<6; //PB6,7 输出高
}
②产生 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;
IIC_SDA=1; //发送 I2C 总线结束信号
delay_us(4);
}
④等待应答信号到来(返回值: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;
}
⑦发送一个字节(返回从机有无应答: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;
}
·宏定义(24cxx.h)
·下列程序(24cxx.c)
①IIC接口初始化
②在指定地址读 1 个数据(返回值 :读到的数据)
③在指定地址写1 个数据(返回值 :读到的数据)
④检查从机设备(24C02)是否正常,假设使用其他
⑤在 AT24CXX 里面的指定地址开始连续读出指定个数的数据
⑥在 AT24CXX 里面的指定地址开始连续写入指定个数的数据
·宏定义(24cxx.h)
#ifndef __24CXX_H
#define __24CXX_H
#include "myiic.h"
#define AT24C01 127
#define AT24C02 255
#define AT24C04 511
#define AT24C08 1023
#define AT24C16 2047
#define AT24C32 4095
#define AT24C64 8191
#define AT24C128 16383
#define AT24C256 32767
//ALIENTEK STM32 开发板使用的是 24c02,所以定义 EE_TYPE 为 AT24C02
#define EE_TYPE AT24C02
u8 AT24CXX_ReadOneByte(u16 ReadAddr);//指定地址读取一个字节
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite); //指定地址写入一个字节
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite);
//从指定地址开始写入指定长度的数据
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead);
//从指定地址开始读出指定长度的数据
u8 AT24CXX_Check(void); //检查器件
void AT24CXX_Init(void); //初始化 IIC
#endif
①IIC接口初始化
//初始化 IIC 接口
void AT24CXX_Init(void)
{
IIC_Init();
}
②在指定地址读 1 个数据(返回值 :读到的数据)
//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); //进入接收模式
IIC_Wait_Ack();
temp=IIC_Read_Byte(0);
IIC_Stop(); //产生一个停止条件
return temp;
}
③在指定地址写1 个数据(返回值 :读到的数据)
//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(10); //EEPROM 的写入速度比较慢,加入延迟
}
④检查从机设备(24C02)是否正常,假设使用其他
u8 AT24CXX_Check(void)
{
u8 temp;
temp=AT24CXX_ReadOneByte(255);//避免每次开机都写 AT24CXX
if(temp==0X55)return 0;
else//排除第一次初始化的情况
{
AT24CXX_WriteOneByte(255,0X55);
temp=AT24CXX_ReadOneByte(255);
if(temp==0X55)return 0;
}
return 1;
}
⑤在 AT24CXX 里面的指定地址开始连续读出指定个数的数据
//ReadAddr :开始读出的地址 对 24c02 为 0~255
//pBuffer :数据数组首地址
//NumToRead:要读出数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
while(NumToRead)
{
*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
NumToRead--; }
}
⑥在 AT24CXX 里面的指定地址开始连续写入指定个数的数据
//WriteAddr :开始写入的地址 对 24c02 为 0~255
//pBuffer :数据数组首地址
//NumToWrite:要写入数据的个数
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
while(NumToWrite--) {
AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
} }