记录一下,方便以后翻阅~
主要内容:
1) I2C通讯协议;
2) 24C02芯片介绍;
3) 相关实验代码解读。
实验功能:系统启动后,通过KEY1按键来控制24C02的写入,通过另外一个按键KEY0来控制24C02的读取。并在串口调试助手上面显示相关信息。
官方资料:《STM32中文参考手册V10》第24章——I2C接口
1. I2C通讯协议概念
I2C(IIC,Inter-Integrated Circuit),两线式串行总线,由PHILIPS公司开发用于连接微控制器及其外围设备。
I2C是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps以上。
I2C是半双工通信方式(这个在《学习心得十四:串口通信相关知识及配置方法》有讲)。
I2C总线系统结构如下图所示(这里先提一句,I2C协议里,空闲状态时SDA和SCL都是高电平!):
2. I2C通信协议及相关代码解读
2.1 空闲状态
I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
2.2 开始信号和停止信号
起始信号:当SCL为高时,SDA由高到低跳变。启动信号是一种电平跳变时序信号,而不是一个电平信号。
起始信号相关代码:
void IIC_Start(void)
{
SDA_OUT(); //先设SDA线为输出//
IIC_SDA=1; //SDA输出高//
IIC_SCL=1; //SCL输出高,此时为空间状态//
delay_us(4);
IIC_SDA=0; //SDA输出由高变低,IIC开始//
delay_us(4);
IIC_SCL=0; //钳住I2C总线,准备发送或接收数据//
}
停止信号:当SCL为高时,SDA由低到高跳变。停止信号是一种电平跳变时序信号,而不是一个电平信号。
停止信号相关代码:
void IIC_Stop(void)
{
SDA_OUT(); //先设SDA线为输出//
IIC_SCL=0; //SCL输出低//
IIC_SDA=0; //SDA输出低//
delay_us(4);
IIC_SCL=1; //SCL输出高//
IIC_SDA=1; //SDA输出由低变高,发送I2C总线结束信号//
delay_us(4);
}
2.3 应答信号
发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
等待应答信号的相关代码:
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入//
IIC_SDA=1;delay_us(1); //SDA设高电平//
IIC_SCL=1;delay_us(1); //SCL设高电平//
while(READ_SDA) //读取PB7的值,0或1//
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1; //失败//
}
}
IIC_SCL=0; //当PB7值为0,则SCL时钟输出低电平,0//
return 0; //成功//
}
对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平(有效应答应这样编写)。
有效/无效应答相关代码:
void IIC_Ack(void) //有效应答//
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
void IIC_NAck(void) //无效应答//
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
2.4 数据的有效性:
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
即:数据在SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。
发送数据相关代码(SCL为高电平时,发送数据):
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0; //SCL为低电平时,可以改变SDA,以实现数据的有效传输//
for(t=0;t<8;t++)
{
if((txd&0x80)>>7) //(txd&0x80)>>7值要么是0要么是1,IIC是从最高位开始传输数据的//
IIC_SDA=1; //(txd&0x80)>>7值为1时,SDA给1//
Else
IIC_SDA=0; //(txd&0x80)>>7值为0时,SDA给0//
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的//
IIC_SCL=1; //SCL为高电平时,传输SDA//
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
读数据相关代码(SCL为高电平时,接收数据):
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char
i,receive=0; //无符号字符型,对应整数范围0~255//
SDA_IN(); //SDA设置为输入//
for(i=0;i<8;i++)
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1; //SCL由低设高后,开始读信号//
receive<<=1; //先将之前接收的信号左移,最右位补0//
if(READ_SDA)receive++; //如果接收SDA为1,则最右位改为1//
delay_us(1); //如果接收SDA为0,则不做操作//
}
if (!ack)
IIC_NAck(); //发送nACK//
else
IIC_Ack(); //发送ACK//
return receive;
}
2.5 数据传输:
在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。
3. 开发版的硬件连接
如下图所示:
PB6对应IIC的时钟线,PB7对应IIC的数据线。
PB6和PB7接在EEPROM(24C02)的SCL和SDA上。
4. EEPROM(24C02)介绍
EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。
24C02是一个2Kbit的串行EEPROM存储芯片,可存储256(2K/8)个字节数据。通过I2C总线通讯读写芯片数据,通讯时钟频率可达400KHz。(备注,1Byte=8bit)
4.1 24C02芯片引脚定义如下图所示:
4.2 A0-A2的引脚用来设置24C02的地址线,由开发版给的硬件连接图可知,默认A0、A1、A2跟GND连接。
24C02的设备地址由下图的第一行所示:
上图中,24C02地址的前4位为0b1010,即0xA,后三位由A2、A1、A0决定,即0b000,最后一位根据读或写决定,读至1,写至0,因此24CO2的地址读写地址分别为:0b10100001(0xA1)和0b10100000(0xA0)。
4.3 24C02字节写时序/读时序
24C02写一个字节数据的驱动程序代码:
//写入一个字节,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);
}
24C02读一个字节数据的驱动程序代码:
//读一个字节, ReadAddr:开始读数的地址,返回值:读到的数据//
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
u8 temp=0;
IIC_Start();
if(EE_TYPE>AT24C16) //实际用的是24C02,小于AT24C16,所以直接跳到else//
{
IIC_Send_Byte(0XA0); //发送写命令//
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr>>8); //发送高地址//
IIC_Wait_Ack();
}
else
IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //发送设备地址0XA0,写数据,(ReadAddr/256)<<1)值为0//
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;
}
4.4 其他24C02驱动程序
4.4.1 初始化AT24CXX函数
void AT24CXX_Init(void)
{
IIC_Init();
}
4.4.2 检查AT24C02是否正常函数
u8 AT24CXX_Check(void)
{
u8 temp;
temp=AT24CXX_ReadOneByte(255); //避免每次开机都写AT24CXX//
if(temp==0X55)return 0; //返回0说明正常//
else //排除第一次初始化的情况//
{
AT24CXX_WriteOneByte(255,0X55);
temp=AT24CXX_ReadOneByte(255);
if(temp==0X55)return 0;
}
return 1;
}
4.4.3 发送一段数据函数
//WriteAddr :开始写入的地址 对24c02为0~255,pBuffer :数据数组首地址,NumToWrite:要写入数据的个数//
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
while(NumToWrite--)
{
AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
}
}
4.4.4 读取一段数据函数
// ReadAddr :开始读出的地址对24c02为0~255,pBuffer :数据数组首地址,NumToRead:要读出数据的个数//
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
while(NumToRead)
{
*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
5. 实验相关代码解读
5.1 myiic.h头文件代码解读
#define __MYIIC_H
#include "sys.h"
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;} //上拉输入模式//
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;} //推挽输出,50MHz//
#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信号//
void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);
#endif
5.2 myiic.c文件代码解读
#include "myiic.h"
#include "delay.h"
//编写初始化IIC函数//
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //使能GPIOB时钟//
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出//
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高//
}
//编写IIC起始信号函数//
void IIC_Start(void)
{
SDA_OUT(); //SDA线设为输出//
IIC_SDA=1; //SDA输出高//
IIC_SCL=1; //SCL输出高,此时空间状态//
delay_us(4);
IIC_SDA=0; //SDA输出低,IIC开始//
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输出高//
IIC_SDA=1; //SDA输出高,发送I2C总线结束信号//
delay_us(4);
}
//编写IIC等待应答信号函数,返回值:1,接收应答失败;0,接收应答成功//
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入//
IIC_SDA=1;delay_us(1); //SDA设高电平//
IIC_SCL=1;delay_us(1); //SCL设高电平//
while(READ_SDA) //读取PB7的值,0或1//
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1; //失败//
}
}
IIC_SCL=0; //当PB7值为0,则SCL时钟输出低电平,0//
return 0; //成功//
}
//编写IIC产生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;
}
//编写IIC不产生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发送一个字节函数,1,有应答;0,无应答//
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0; //SCL为低电平时,可以改变SDA,以实现数据的有效传输//
for(t=0;t<8;t++)
{
//IIC_SDA=(txd&0x80)>>7;
if((txd&0x80)>>7) //(txd&0x80)>>7值要么是0要么是1,IIC是从最高位开始传输数据的//
IIC_SDA=1; //(txd&0x80)>>7值为1时,SDA给1//
else
IIC_SDA=0; //(txd&0x80)>>7值为0时,SDA给0//
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1; //SCL为高电平时,传输SDA//
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; //无符号字符型,对应整数范围0~255//
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1; //SCL由低设高后,开始读信号//
receive<<=1; //先将之前接收的信号左移,最右位补0//
if(READ_SDA)receive++; //如果接收SDA为1,则最右位改为1//
delay_us(1); //如果接收SDA为0,则不做操作//
}
if (!ack)
IIC_NAck(); //发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
5.3 main.c文件代码解读
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "24cxx.h"
//要写入到24c02的字符串数组
const u8 TEXT_Buffer[]={"这是从24C02读取的数据"};
#define SIZE sizeof(TEXT_Buffer)
int main(void)
{
u8 key;
u16 i=0;
u8 datatemp[SIZE];
delay_init(); //延时函数初始化//
uart_init(115200); //串口初始化为115200//
LED_Init(); //初始化与LED连接的硬件接口//
KEY_Init();
AT24CXX_Init(); //IIC初始化//
while(AT24CXX_Check()) //检测24C02//
{
printf("检测不到值\n");
LED0=!LED0; //DS0闪烁//
LED1=!LED1; //DS1闪烁//
delay_ms(100);
}
while(1)
{
key=KEY_Scan(0);
if(key==KEY1_PRES) //KEY_UP按下,写入24C02数据//
{
AT24CXX_Write(0,(u8*)TEXT_Buffer,SIZE);
printf("向24C02写入数据\n");
}
if(key==KEY0_PRES) //KEY0按下,读取字符串并显示
{
AT24CXX_Read(0,datatemp,SIZE);
printf("从24C02读取数据,内容为:%s\n",datatemp);
}
i++;
delay_ms(10);
if(i==20)
{
LED0=!LED0; //提示系统正在运行
i=0;
}
}
}
6. 实验结果
实验结果如下图所示:
旧知识点
1)复习如何新建工程模板,可参考STM32学习心得二:新建工程模板;
2)复习基于库函数的初始化函数的一般格式,可参考STM32学习心得三:GPIO实验-基于库函数;
3)复习寄存器地址,可参考STM32学习心得四:GPIO实验-基于寄存器;
4)复习位操作,可参考STM32学习心得五:GPIO实验-基于位操作;
5)复习寄存器地址名称映射,可参考STM32学习心得六:相关C语言学习及寄存器地址名称映射解读;
6)复习时钟系统框图,可参考STM32学习心得七:STM32时钟系统框图解读及相关函数;
7)复习延迟函数,可参考STM32学习心得九:Systick滴答定时器和延时函数解读;
8)复习ST-LINK仿真器的参数配置,可参考STM32学习心得十:在Keil MDK软件中配置ST-LINK仿真器;
9)复习ST-LINK调试方法,可参考STM32学习心得十一:ST-LINK调试原理+软硬件仿真调试方法;
10)复习串口通信相关知识,可参考STM32学习心得十四:串口通信相关知识及配置方法。