I2C协议+实现源码

时间:2024-03-01 18:31:54

转自:https://blog.csdn.net/weixin_41995541/article/details/89843330

摘要
I2C通信协议
简介
补充
空闲状态
start和stop信号
应答信号
数据有效性规定
数据传输
延时
I2C协议的实现源码
硬件说明
头文件
sys.h
主函数
初始化I2C
产生开始和停止信号
等待应答信号
产生或不产生应答
I2C写操作
I2C读操作
对24C02操作
24C02的时序图
头文件
初始化IIC接口
写数据(一个字节)
写数据(长度为Len)
写数据(指定长度)
读数据(一个字节)
读数据(长度为Len)
读数据(指定长度)
检查AT24CXX是否正常
主函数
实验结果截图
摘要
参考资料:

【STM32】IIC的基本原理(实例:普通IO口模拟IIC时序读取24C02)
https://blog.csdn.net/qq_38410730/article/details/80312357
STM32下模拟I2C的C语言实现
https://blog.csdn.net/qq_38410730/article/details/80312357
I2C协议小结
https://blog.csdn.net/huangkangying/article/details/73182432

 

I2C通信协议
简介
I2C–(IIC,Inter-Integrated Circuit),内部集成电路,两线式串行总线
由 SDA 和 SCL 时钟线构成,可发送和接收数据
在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送
通信协议:I2C 协议
传输距离:短距离传输
同步/异步:同步
传输信号:TTL 电平信号
通信方式(单/半/全双工):半双工
通信线:两根线,SCL 和 SDA
SCL:I2C 是串行同步通信,需要 SCL 线传输同步脉冲信号
SDA:用于半双工传输信号,数据/地址/控制信号复用SDA线
片选:通过软件地址,发送不同的设备地址,选择与不同的 I2C 设备通信
I/O接口(I2C 控制器),可能是独立的,也可能和 CPU 集成在一起,如MCU


IO桥:PC机才有IO桥,单片机没有
I2C 通信作用:
用于连接各种使用 I2C 通信的设备,这些设备有EEPROM, LCD,AD. RTC (时钟) ,蓝牙、ZigBee、wifi等无线通信模块。
使用 I2C 属于简单通信,使用 I2C 通信的设备也都比较简单
使用 I2C 通信的设备,一般都是直接做在了电路板上。
补充
作者:huangkangying
来源:CSDN
原文:https://blog.csdn.net/huangkangying/article/details/73182432
版权声明:本文为博主原创文章,转载请附上博文链接!

两线串行协议(SCL, SDA),主从模式,支持多主控,但在同一时刻只能有一个主控
地址组成:7bit 地址数组+1bit读写位, 共8bit。地址范围0~127, 0地址为广播地址。
一次发送8bit数据,数据格式为大端模式。
速率:标准模式100Kbps, 快速模式400Kbps,高速模式3.4Mbps。
SCL由master提供,只有master才能同时控制SCL和SDA
SCL为低电平时才能改变数据,SCL为高电平时,数据有效。
起始条件: 当SCL为高电平时,master拉低SDA,总线进入start状态。
结束条件: 当SCL为高电平时,master拉高SDA,总线进入stop状态。
无论是发送地址还是数据,其后都紧跟着一个ACK/NACK。
当master写数据里,ACK和NACK由slave提供。
当master读数据里,ACK和NACK由master提供。
下面为master写数据示例:ACK:在第9个CLK周期,slave拉低SDA,发送一个ACK
NACK: 在第9个CLK周期,slave保持SDA为高,发送一个NACK
一次可以传输多个字节,直到master发送start/stop。
如果slave设备来不及处理data,它可以保持SCL为低,强制master进入等待状态。???
空闲状态
规定空闲状态 SDA 和 SCL 都必须拉高
SDA保持高电平
SCL保持高电平


start和stop信号
有头有尾还有肚

开始信号:SCL 高电平期间,SDA 由高–>低电平

停止信号:SCL 高电平期间,SDA 由低–>高电平

起始信号:当SCL为高期间, SDA由高到低的跳变;

启动信号是一种电平跳变时序信号,而不是一个电平信号。

停止信号:当SCL为高期间, SDA由低到高的跳变;

停止信号也是一种电平跳变时序信号,而不是一个电平信号。

 

应答信号
应答信号 ACK
发送器每发送一个字节(8bit),就在时钟脉冲 9 期间释放数据线,由接收器反馈一个应答信号。
应答信号为低电平时,规定为有效应答位(ACK),表示接收器已经成功地接收了该字节;
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器该字节接收失败
反馈有效应答位的要求:接收器在第 9 个时钟脉冲之前的低电平期间将 SDA 线拉低,并且确保在该时钟(第 9 个时钟脉冲)的高电平期间为稳定的低电平。
如果接收机是主机,则它在接收到最后一个字节后,发送一个 NACK 信号,一通知被控发送器(从机)结束数据发送,并释放 SDA 线,以便主控接收器发送一个停止信号。


数据有效性规定
犹如物流:

快递车(SCL)运送期间(SCL高电平),货物(数据)在车内(SDA保持)保持不动
当快递车(SCL)停下(SCL低电平),才允许快递小哥(器件)派送(SDA可变)货物(传送数据)
SCL高电平,SDA须保持

SCL低电平,SDA可变

为了与开始和停止信号区别

即:数据在SCL的上升沿到来之前就需准备好。并在下降沿到来之前必须稳定。

 

 

数据传输
在 I2C 总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制) ,
即在SCL串行时钟的配合下,在 SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。


延时


I2C协议的实现源码
硬件说明
采用了正点原子的 STM32F103RCT6 开发板
实验目的:对 24C02 进行操作

 


头文件
#ifndef __IIC_H
#define __IIC_H
#include "sys.h" //实现了IO口的位带操作

//IO方向设置,操作寄存器
//简单点:就是把SDA的PC11口设为输入或输出
#define SDA_IN() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=8<<12;}
#define SDA_OUT() {GPIOC->CRH&=0XFFFF0FFF;GPIOC->CRH|=3<<12;}

//IO操作函数,IO口位带操作
//简单点:就是可以对SCL的PC12和SDA的PC11进行赋值操作
#define IIC_SCL PCout(12) //SCL
#define IIC_SDA PCout(11) //SDA
#define READ_SDA PCin(11) //输入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信号

void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);
#endif


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
sys.h
#ifndef __SYS_H
#define __SYS_H
#include "stm32f10x.h"

//0,不支持ucos
//1,支持ucos
#define SYSTEM_SUPPORT_OS 0 //定义系统文件夹是否支持UCOS

//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C

#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08

//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入

#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入

#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入

#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入

#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入

#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入

#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入

//以下为汇编函数
void WFI_SET(void); //执行WFI指令
void INTX_DISABLE(void);//关闭所有中断
void INTX_ENABLE(void); //开启所有中断
void MSR_MSP(u32 addr); //设置堆栈地址

#endif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
主函数
参考24C02的时序图
初始化I2C
主机初始化,配置引脚,开启相应的时钟
//初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

//RCC->APB2ENR|=1<<4; //先使能外设IO PORTC时钟
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC, ENABLE ); //使能外设IO PORTC时钟

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_11; //同时设置PC11和PC12引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);

//空闲状态,主机的SCL和SDA都要拉高
IIC_SCL=1;
IIC_SDA=1;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
产生开始和停止信号
当然这是主机产生

思考:

只设置了SDA为输出,SCL不管吗???可能是SCL始终是输出,是主机产生的时钟脉冲。而SDA可能是输出也可能是输入。所以需要确保它的状态。
我觉得 I2C 停止信号应该这样写:???
void IIC_Stop(void)
{
SDA_OUT(); //设置SDA线为输出
IIC_SCL=1; //SCL置高
IIC_SDA=0;
delay_us(4); //保持一段时间

//发送I2C总线结束信号
IIC_SDA=1; //STOP:when CLK is high, DATA change form low to high
delay_us(4); //保持一段时间
}
1
2
3
4
5
6
7
8
9
10
11
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //设置SDA线为输出,只设置了SDA为输出,SCL不管吗???
IIC_SDA=1; //SDA置高
IIC_SCL=1; //SCL置高
delay_us(4); //让数据保持一段时间(电平嘛)传输的是TTL电平信号

//SDA由高-->低跳变,SCL保持高电平
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; //SCL置低
IIC_SDA=0; //SDA置低
delay_us(4); //保持一段时间

IIC_SCL=1; //SCL置高

//发送I2C总线结束信号
IIC_SDA=1; //STOP:when CLK is high, DATA change form low to high
delay_us(4); //保持一段时间
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29


等待应答信号
当然是发送方等待(发送方可能是主机也可能是从机)

//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入

//等待应答的时候把SDA和SCL线都给拉高 ???
IIC_SDA=1; delay_us(1);
IIC_SCL=1; delay_us(1);

while(READ_SDA) //READ_SDA就是PC11(SDA)引脚,当输入为低时就是(ACK)信号
{
//等待接收方应答
ucErrTime++;
if(ucErrTime>250) //超时传送失败
{
IIC_Stop(); //传送失败发送停止信号
return 1;
}
}

IIC_SCL=0; //主机时钟输出0,为下一步做准备
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
产生或不产生应答
这里应该是针对主机而言的,此时主机是接收方???

思考:

SCL先置低,再置高,再置低,这恰恰就是第 9 个时钟脉冲
而在 SCL 为高电平这段时间,SDA 要保持低电平或高电平
不可以这样写(违背了在SCL高电平期间,SDA要保持稳定):
void IIC_Ack(void)
{
IIC_SCL=0; //SCL置低
SDA_OUT(); //SDA设为输出

IIC_SCL=1; //SCL置高
delay_us(2); //保持一段时间

IIC_SDA=0; //SDA置低,表示应答(ACK)
delay_us(2); //保持一段时间

IIC_SCL=0; //SCL置低
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//第 9 个时钟脉冲问题
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0; //SCL置低
SDA_OUT(); //SDA设为输出
IIC_SDA=0; //SDA置低,表示应答(ACK)
delay_us(2); //保持一段时间
IIC_SCL=1; //SCL置高
delay_us(2); //保持一段时间
IIC_SCL=0; //SCL置低
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0; //SCL置低
SDA_OUT(); //SDA设为输出
IIC_SDA=1; //SDA置高,表示非应答(NACK)
delay_us(2); //保持一段时间
IIC_SCL=1; //SCL置高
delay_us(2); //保持一段时间
IIC_SCL=0; //SCL置低
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23


I2C写操作
无论读写均由主机触发开始条件和结束条件

如果从机接了主机的中断线,也只是提醒主机而已,任然需要主机触发开始条件

思考:

拉低时钟开始数据传输,规定SCL低电平,SDA才允许变化,否则就是开始和停止信号了
对TEA5767这三个延时都是必须的???为啥
SCL同样的先置低,再置高,最后置低,恰好也是一个时钟脉冲,并且只有在SCL低电平期间,SDA才传送数据:验证了这句话(在 I2C 总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制) ,即在SCL串行时钟的配合下,在 SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。
如果不能进行位操作,可用如下代码:

for(t=0;t<8;t++)
{
//规定从高位开始传输;
if((txd & 0x80) >> 7)//判断最高位是0还是1,然后写入PC11,写入总线
{
PC |= (1<<11); //PC11输出1
}
else
{
PC &= ~(1<<11); //PC11输出0
}
txd <<= 1; //将数据(地址)左移一位
delay_us(2); //对TEA5767这三个延时都是必须的,为啥???
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//IIC发送一个字节
//返回从机有无应答,利用上述的 IIC_Wait_Ack 等待应答函数
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT(); //SDA设为输出

//拉低时钟开始数据传输,规定SCL低电平,SDA才允许变化,否则就是开始和停止信号了
IIC_SCL=0;

for(t=0;t<8;t++)
{
//规定从高位开始传输;
IIC_SDA = (txd & 0x80) >> 7;
txd <<= 1;
delay_us(2); //对TEA5767这三个延时都是必须的,为啥???
IIC_SCL=1; //SCL置高
delay_us(2);
IIC_SCL=0; //SCL置低
delay_us(2);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25


I2C读操作
无论读写均由主机触发开始条件和结束条件
思考:
SCL先置低,然后置高,再置低,也是一个脉冲
然后在SCL为高电平期间读取SDA的数据,因为SCL高电平期间,SDA数据保持稳定
//读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; //SCL置低
delay_us(2); //保持
IIC_SCL=1; //SCL置高

receive <<= 1; //等同于乘以2

//先移位再加,不可先加在移位

//主机通过判断P11输入引脚的电平高低来接收数据
//高电平就加一,低电平就什么都不做
if(READ_SDA)receive++;
delay_us(1);
}
if (ack)
IIC_Ack(); //发送ACK
else
IIC_NAck(); //发送nACK

return receive;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28


对24C02操作
EEPROM(24C02),2就是 2K ,总容量是256(2K/8)个字节
详细信息请参考 AT2402 芯片手册

 

 


24C02的时序图
必须知会24C02的时序图,不同硬件有不同的时序图
写时序:


读时序:


头文件
#ifndef __24CXX_H
#define __24CXX_H
#include "myiic.h"

//Mini STM32开发板
//24CXX驱动函数(适合24C01~24C16,24C32~256未经过测试!有待验证!)
//2010/6/10
//V1.2

#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
//Mini STM32开发板使用的是24c02,所以定义EE_TYPE为AT24C02
#define EE_TYPE AT24C02

u8 AT24CXX_ReadOneByte(u16 ReadAddr); //指定地址读取一个字节
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite); //指定地址写入一个字节
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len);//指定地址开始写入指定长度的数据
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len); //指定地址开始读取指定长度数据
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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
初始化IIC接口
//初始化IIC接口
void AT24CXX_Init(void)
{
IIC_Init();
}

1
2
3
4
5
6
写数据(一个字节)
//在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(10);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
写数据(长度为Len)
//在AT24CXX里面的指定地址开始写入长度为Len的数据
//该函数用于写入16bit或者32bit的数据.
//WriteAddr :开始写入的地址
//DataToWrite:数据数组首地址
//Len :要写入数据的长度2,4
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
{
u8 t;
for(t=0;t<Len;t++)
{
AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
写数据(指定长度)
//在AT24CXX里面的指定地址开始写入指定个数的数据
//WriteAddr :开始写入的地址 对24c02为0~255
//pBuffer :数据数组首地址
//NumToWrite:要写入数据的个数
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
while(NumToWrite--)
{
AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
读数据(一个字节)
//在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);//发送高地址
IIC_Wait_Ack();
}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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
读数据(长度为Len)
//在AT24CXX里面的指定地址开始读出长度为Len的数据
//该函数用于读出16bit或者32bit的数据.
//ReadAddr :开始读出的地址
//返回值 :数据
//Len :要读出数据的长度2,4
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
{
u8 t;
u32 temp=0;
for(t=0;t<Len;t++)
{
temp<<=8;
temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);
}
return temp;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
读数据(指定长度)
//在AT24CXX里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 对24c02为0~255
//pBuffer :数据数组首地址
//NumToRead:要读出数据的个数
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
while(NumToRead)
{
*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
NumToRead--;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
检查AT24CXX是否正常
//检查AT24CXX是否正常
//这里用了24XX的最后一个地址(255)来存储标志字.
//如果用其他24C系列,这个地址要修改
//返回1:检测失败
//返回0:检测成功
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
主函数
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "lcd.h"
#include "key.h"
#include "24cxx.h"
#include "myiic.h"


//要写入到24c02的字符串数组
const u8 TEXT_Buffer[]={"MiniSTM32 IIC TEST"};

#define SIZE sizeof(TEXT_Buffer) //获取字节个数

int main(void)
{
u8 key;
u16 i=0;
u8 datatemp[SIZE];
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断优先级分组2
delay_init(); //延时函数初始化
uart_init(9600); //串口初始化为9600
LED_Init(); //初始化与LED连接的硬件接口
LCD_Init();
KEY_Init(); //按键初始化

AT24CXX_Init(); //IIC初始化

//用LCD显示屏显示出来
POINT_COLOR=RED;//设置字体为红色
LCD_ShowString(60,50,200,16,16,"Mini STM32");
LCD_ShowString(60,70,200,16,16,"IIC TEST");
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2014/3/9");
LCD_ShowString(60,130,200,16,16,"WK_UP:Write KEY0:Read"); //显示提示信息

while(AT24CXX_Check())//检测不到24c02
{
LCD_ShowString(60,150,200,16,16,"24C02 Check Failed!");
delay_ms(500);
LCD_ShowString(60,150,200,16,16,"Please Check! ");
delay_ms(500);
LED0=!LED0;//DS0闪烁
}
LCD_ShowString(60,150,200,16,16,"24C02 Ready!");
POINT_COLOR=BLUE;//设置字体为蓝色

while(1)
{
key=KEY_Scan(0);
if(key==WKUP_PRES)//WK_UP 按下,写入24C02
{
LCD_Fill(0,170,239,319,WHITE);//清除半屏
LCD_ShowString(60,170,200,16,16,"Start Write 24C02....");
AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE);
LCD_ShowString(60,170,200,16,16,"24C02 Write Finished!");//提示传送完成
}
if(key==KEY0_PRES)//KEY0 按下,读取字符串并显示
{
LCD_ShowString(60,170,200,16,16,"Start Read 24C02.... ");
AT24CXX_Read(0,datatemp,SIZE);
LCD_ShowString(60,170,200,16,16,"The Data Readed Is: ");//提示传送完成
LCD_ShowString(60,190,200,16,16,datatemp);//显示读到的字符串
}
i++;
delay_ms(10);
if(i==20)
{
LED0=!LED0;//提示系统正在运行
i=0;
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
实验结果截图
实验结果视频地址:

视频地址:http://t.cn/EoZQHMm?m=4368576113101384&u=5955627170
————————————————
版权声明:本文为CSDN博主「Iem Gnay」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41995541/article/details/89843330