记录一下,方便以后翻阅~
学习板:正点原子 战舰V3
修改内容:
1)2020年11月29日:对代码部分的解释进行了修正,将代码冗余进行删除,代码仅支持24C02与F103通讯;
主要内容:
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由高到低跳变。启动信号是一种电平跳变时序信号,而不是一个电平信号。
停止信号:当SCL为高时,SDA由低到高跳变。停止信号是一种电平跳变时序信号,而不是一个电平信号。
2.3 应答信号
发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平(有效应答应这样编写)。
2.4 数据的有效性:
I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
即:数据在SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。
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字节写时序/读时序
5. 实验相关代码解读
5.1 myiic.h头文件代码解读
/**
******************************** STM32F10x *********************************
* @文件名称: myiic.h
* @修改作者: Aaron
* @库版本号: V3.5.0
* @工程版本: V1.0.0
* @开发日期: 2020年11月28日
* @摘要简述: myiic头文件
******************************************************************************/
/*-----------------------------------------------------------------------------
* @更新日志:
* @无
* ---------------------------------------------------------------------------*/
#ifndef __MYIIC_H
#define __MYIIC_H
/* 包含的头文件 --------------------------------------------------------------*/
#include "sys.h"
#include "stm32f10x.h"
/* 寄存器方法控制GPIO管脚输入输出模式 ----------------------------------------*/
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;} // PB7 上拉输入模式
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;} // PB7 通用推挽输出,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停止信号
u8 IIC_Wait_Ack(void); // IIC等待ACK信号
void IIC_Ack(void); // IIC发送ACK信号
void IIC_NAck(void); // IIC不发送ACK信号
void IIC_Send_Byte(u8 txd); // IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);// IIC读取一个字节
#endif /* __MYIIC_H */
/****** Copyright (C)2020 Aaron. All Rights Reserved ****** END OF FILE *******/
5.2 myiic.c文件代码解读
/**
******************************** STM32F10x *********************************
* @文件名称: myiic.c
* @修改作者: Aaron
* @库版本号: V3.5.0
* @工程版本: V1.0.0
* @开发日期: 2020年11月28日
* @摘要简述: myiic源文件,包含IIC底层驱动程序
******************************************************************************/
/*-----------------------------------------------------------------------------
* @更新日志:
* @无
* ---------------------------------------------------------------------------*/
/* 包含的头文件 --------------------------------------------------------------*/
#include "myiic.h"
#include "delay.h"
/**********************************************************************
函数名称:IIC_Init()
函数功能:IIC初始化函数,配置底层IO管脚,并设为空闲状态,属IIC底层驱动程序
入口参数:无
返回参数:无
修改作者:Aaron
***********************************************************************/
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_Start()
函数功能:IIC起始信号,当SCL为高时,SDA由高到低跳变,属IIC底层驱动程序
入口参数:无
返回参数:无
修改作者:Aaron
***********************************************************************/
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_Stop()
函数功能:IIC停止信号,当SCL为高时,SDA由低到高跳变,属IIC底层驱动程序
入口参数:无
返回参数:无
修改作者:Aaron
**********************************************************************/
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);
}
/*********************************************************************
函数名称:u8 IIC_Wait_Ack()
函数功能:IIC等待应答信号函数,属IIC底层驱动程序
应答信号为低电平时,为有效应答位,则接收器成功接收该字节;
应答信号为高电平时,为无效应答位,则接收器接收该字节失败。
入口参数:无
返回参数:u8 返回值:1,接收应答失败;0,接收应答成功
修改作者:Aaron
**********************************************************************/
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的值,一直读PB7=1,高电平,则不断循环,直到溢出
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1; // 应答失败
}
}
IIC_SCL=0; // 当PB7=0,则SCL时钟输出低电平,0
return 0; // 成功
}
/*********************************************************************
函数名称:void IIC_Ack(void)
函数功能:IIC产生有效ACK应答函数,此时作为接收器,属IIC底层驱动程序
有效应答位ACK的要求是:
接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,
并确保在该时钟的高电平期间为稳定的SDA低电平。
入口参数:无
返回参数:无
修改作者:Aaron
**********************************************************************/
void IIC_Ack(void)
{
IIC_SCL=0; // SCL设为低电平
SDA_OUT();
IIC_SDA=0; // SDA设为低电平
delay_us(2);
IIC_SCL=1; // SCL设为高电平
delay_us(2);
IIC_SCL=0; // SCL设为低电平,从而SCL第九个高电平期间,SDA一直为低电平
}
/*********************************************************************
函数名称:void IIC_NAck(void)
函数功能:IIC不产生有效ACK应答函数,此时作为接收器,属IIC底层驱动程序
入口参数:无
返回参数:无
修改作者:Aaron
**********************************************************************/
void IIC_NAck(void)
{
IIC_SCL=0; // SCL设为低电平
SDA_OUT();
IIC_SDA=1; // SCL设为高电平
delay_us(2);
IIC_SCL=1; // SCL设为高电平
delay_us(2);
IIC_SCL=0; // SCL设为低电平,从而SCL第九个高电平期间,SDA一直为高电平
}
/*********************************************************************
函数名称:IIC_Send_Byte(u8 txd)
函数功能:IIC发送一个字节函数,属IIC底层驱动程序
入口参数:u8 txd,待发送的数据,8位
返回参数:无
修改作者:Aaron
**********************************************************************/
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT(); // SDA设为输出
IIC_SCL=0; // SCL为低电平时,此时可改变SDA,以实现数据的有效传输
for(t=0;t<8;t++)
{
if((txd&0x80)>>7) // 获取u8 txd的最高位(0或1),并右移7位
IIC_SDA=1; // 若为1,则SDA给1,即u8 txd数据从高位发送
else
IIC_SDA=0; // 若为0,则SDA给0,即u8 txd数据从高位发送
txd<<=1;
delay_us(2); // 对TEA5767这三个延时都是必须的
IIC_SCL=1; // SCL设为高电平,传输SDA
delay_us(2);
IIC_SCL=0; // SCL设为低电平,传输SDA完成
delay_us(2);
}
}
/*********************************************************************
函数名称:u8 IIC_Read_Byte(unsigned char ack)
函数功能:IIC读取一个字节函数,属IIC底层驱动程序
读1个字节,ack=1时,发送ACK,ack=0,发送nACK
入口参数:unsigned char ack
返回参数:u8
修改作者:Aaron
**********************************************************************/
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,则不做操作,延迟1us
}
if (!ack)
IIC_NAck(); // 发送nACK
else
IIC_Ack(); // 发送ACK
return receive;
}
/****** Copyright (C)2020 Aaron. All Rights Reserved ****** END OF FILE *******/
5.3 24c02.h头文件代码解读
/**
******************************** STM32F10x *********************************
* @文件名称: 24c02.h
* @修改作者: Aaron
* @库版本号: V3.5.0
* @工程版本: V1.0.0
* @开发日期: 2020年11月28日
* @摘要简述: 24c02头文件
******************************************************************************/
/*-----------------------------------------------------------------------------
* @更新日志:
* @无
* ---------------------------------------------------------------------------*/
#ifndef __24C02_H
#define __24C02_H
/* 包含的头文件 --------------------------------------------------------------*/
#include "stm32f10x.h"
/* 24CXX驱动 -----------------------------------------------------------------*/
#define AT24C02 255 // 开发板采用的芯片
/* 函数申明 ------------------------------------------------------------------*/
void AT24C02_Init(void); // 初始化IIC
u8 AT24C02_ReadOneByte(u16 ReadAddr); // 指定地址读取一个字节
void AT24C02_WriteOneByte(u16 WriteAddr,u8 DataToWrite); // 指定地址写入一个字节
void AT24C02_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len); // 指定地址开始写入指定长度的数据,用于写入16bit或者32bit的数据
u32 AT24C02_ReadLenByte(u16 ReadAddr,u8 Len); // 指定地址开始读取指定长度数据,用于读出16bit或者32bit的数据
u8 AT24C02_Check(void); // 检查24C02是否通讯正常
void AT24C02_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead); // 从指定地址开始读出指定长度的数据
void AT24C02_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite); // 从指定地址开始写入指定长度的数据
#endif /* __24C02_H */
/****** Copyright (C)2020 Aaron. All Rights Reserved ****** END OF FILE *******/
5.4 24c02.c文件代码解读
/**
******************************** STM32F10x *********************************
* @文件名称: 24c02.c
* @修改作者: Aaron
* @库版本号: V3.5.0
* @工程版本: V1.0.0
* @开发日期: 2020年11月28日
* @摘要简述: 24c02源文件
******************************************************************************/
/*-----------------------------------------------------------------------------
* @更新日志:
* @无
* ---------------------------------------------------------------------------*/
/* 包含的头文件 --------------------------------------------------------------*/
#include "24c02.h"
#include "myiic.h"
#include "delay.h"
/**************************************************************************************
函数名称:AT24C02_Init()
函数功能:24C02驱动函数
因为开发板选择PB6和PB7作为与24C02芯片通信的引脚,所以直接套用IIC_Init()函数
入口参数:无
返回参数:无
修改作者:Aaron
***************************************************************************************/
void AT24C02_Init(void)
{
IIC_Init();
}
/**************************************************************************************
函数名称:u8 AT24C02_ReadOneByte(u16 ReadAddr)
函数功能:在AT24C02指定地址读出一个字节的数据,由从24C02芯片读取一个字节数据
入口参数:u16 ReadAddr:开始读数的地址
返回参数:u8 读到的数据
修改作者:Aaron
***************************************************************************************/
u8 AT24C02_ReadOneByte(u16 ReadAddr)
{
u8 temp=0;
IIC_Start(); // IIC起始信号
IIC_Send_Byte(0XA0); // 发送设备地址0XA0,进入写数据模式
IIC_Wait_Ack(); // 等待响应
IIC_Send_Byte(ReadAddr); // 发送读取数据的起始地址
IIC_Wait_Ack(); // 等待响应
IIC_Start(); // IIC起始信号
IIC_Send_Byte(0XA1); // 进入接收数据模式
IIC_Wait_Ack(); // 等待响应
temp=IIC_Read_Byte(0); // 读取一个字节的数据
IIC_Stop(); // 产生一个停止条件
return temp;
}
/**************************************************************************************
函数名称:AT24C02_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
函数功能:在AT24C02指定地址写入一个字节的数据,由开发板写入一个数据至24C02芯片
入口参数:u16 WriteAddr :写入数据的起始地址;
u8 DataToWrite:要写入的数据
返回参数:无
修改作者:Aaron
***************************************************************************************/
void AT24C02_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
IIC_Start(); // IIC起始信号
IIC_Send_Byte(0XA0); // 发送设备地址0XA0,进入写数据模式
IIC_Wait_Ack(); // 等待响应
IIC_Send_Byte(WriteAddr); // 发送写入数据的起始地址
IIC_Wait_Ack(); // 等待响应
IIC_Send_Byte(DataToWrite); // 发送一个字节的数据
IIC_Wait_Ack(); // 等待响应
IIC_Stop(); // 产生一个停止条件
delay_ms(10);
}
/**************************************************************************************
函数名称:u8 AT24C02_Check()
函数功能:检查AT24C02是否正常,读取最后一个地址(255)来存储数据
入口参数:无
返回参数:u8,返回1:检测失败;返回0:检测成功
修改作者:Aaron
***************************************************************************************/
u8 AT24C02_Check(void)
{
u8 temp;
temp=AT24C02_ReadOneByte(255); // 读取第255个地址的数据
if(temp==0X55)
return 0;
else // 排除第一次初始化的情况
{
AT24C02_WriteOneByte(255,0X55); // 先在第255个地址写入0x55数据
temp=AT24C02_ReadOneByte(255); // 再读取第255个地址的数据
if(temp==0X55)
return 0;
}
return 1;
}
/**************************************************************************************
函数名称:AT24C02_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
函数功能:在AT24C02里面的指定地址开始读出指定个数的数据
入口参数:u16 ReadAddr : 开始读出的地址,对24c02为0~255;
u8 *pBuffer : 数据数组首地址;
u16 NumToRead : 要读出数据的个数
返回参数:无
修改作者:Aaron
***************************************************************************************/
void AT24C02_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
while(NumToRead)
{
*pBuffer++=AT24C02_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
/**************************************************************************************
函数名称:AT24C02_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
函数功能:AT24C02里面的指定地址开始写入指定个数的数据
入口参数:u16 WriteAddr : 开始写入的地址 对24c02为0~255;
u8 *pBuffer : 数据数组首地址;
u16 NumToWrite : 要写入数据的个数
返回参数:无
修改作者:Aaron
***************************************************************************************/
void AT24C02_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
while(NumToWrite--)
{
AT24C02_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
}
}
/****** Copyright (C)2020 Aaron. All Rights Reserved ****** END OF FILE *******/
5.5 main.c文件代码解读
/**
******************************** STM32F10x *********************************
* @文件名称: main.c
* @修改作者: Aaron
* @库版本号: V3.5.0
* @工程版本: V1.0.0
* @开发日期: 2020年11月28日
* @摘要简述: 主函数
******************************************************************************/
/*-----------------------------------------------------------------------------
* @更新日志:
* @无
* ---------------------------------------------------------------------------*/
/* 包含的头文件 ---------------------------------------------------------------*/
#include "led.h"
#include "key.h"
#include "delay.h"
#include "sys.h"
#include "serial_communication.h"
#include "24c02.h"
/* 定义常量 ------------------------------------------------------------------*/
const u8 TEXT_Buffer[]={"这是从24C02读取的数据"}; // 要写入到24c02的字符串数组
/* 宏定义 --------------------------------------------------------------------*/
#define SIZE sizeof(TEXT_Buffer) // 计算TEXT_Buffer[]长度
/*********************************************************************
函数名称:int main()
函数功能:主函数
入口参数:无
返回参数:无
修改作者:Aaron
**********************************************************************/
int main(void)
{
u8 key;
u16 i=0;
u8 datatemp[SIZE];
delay_init(); // 延时函数初始化
My_USART1_Init(); // 串口初始化为115200
LED_Init(); // LED管脚初始化
KEY_Init(); // 按键管脚初始化
AT24C02_Init(); // IIC初始化
while(AT24C02_Check()) // 检测24C02
{
printf("检测不到值\n");
DS0=!DS0; // DS0闪烁
DS1=!DS1; // DS1闪烁
delay_ms(100);
}
printf("24C02芯片测试成功!\n");
while(1)
{
key=KEY_Scan(0);
if(key==KEY1_PRES) // KEY1按下,写数据
{
AT24C02_Write(0,(u8*)TEXT_Buffer,SIZE);
printf("向24C02写入数据\n");
}
if(key==KEY0_PRES) // KEY0按下,读数据
{
AT24C02_Read(0,datatemp,SIZE);
printf("从24C02读取数据,内容为:%s\n",datatemp);
}
i++;
delay_ms(10);
if(i==20)
{
DS0=!DS0; // 提示系统正在运行
i=0;
}
}
}
/****** Copyright (C)2020 Aaron. All Rights Reserved ****** END OF FILE *******/
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学习心得十四:串口通信相关知识及配置方法。