前言
最近跟着B站上的up主“江科科技”学习完了整套STM32入门课程,也算是入门的小白了,就此下定决心开始写一些博客来记录自己学过的一些电路模块和嵌入式知识,一方面是加深理解,另一方面是分享出来给可能需要用到的人。同时这个也是本小白写的第一篇博客,如有任何问题还请批评指正,十分感谢!
以下的代码和工程都是基于“江科科技”开源的工程基础之上进行的二次开发。视频链接:STM32入门教程-2023版 细致讲解 中文字幕_哔哩哔哩_bilibili
AT24C02介绍
AT24C02是一种电可擦除E2PROM,内部容量大小为2K,采用两线(I2C)串行接口对外通讯。同时具有页写能力,每页有8字节,共32页,AT24C02最常见的封装形式为8-pin SOP。
常见的AT24C02封装形式(图片来源于网络)
AT24C02特性
AT24C02芯片手册数据
这里是一些AT24C02芯片的一些参考数据,比较重要的部分图内已指明(内部写周期与后面的代码有关)。
AT24C02的引脚说明和存储结构
AT24C02芯片手册数据
用简洁的话概括如下:
A0,A1,A2 - 可以作为器件的地址输入引脚,用于实现多器件级联功能
SDA - 串行数据线
SCL - 串行时钟线
WP - 写保护
GND - 地
VCC - 电源
AT24C02的I2C读写地址
AT24C02芯片手册数据
由上表我们可以看出,AT24C02的读地址为8位二进制数据,转换为十六进制为0xA0,改写为写地址只需在其后逻辑与上1使之变为0xA1即可。
I2C协议读写AT24C02
在我们知道了这么多关于该芯片的信息后,就可以开始写代码了,首先说明我自己使用的单片机是STM32F103C8T6,开发环境为Keil5。下面软件I2C代码实现为B站up主“江协科技”开源的工程代码,器件读写部分代码是自己写的,这里用作分享。
#include "stm32f10x.h" // Device header
#include "Delay.h"
/*引脚配置层*/
/**
* 函 数:I2C写SCL引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
*/
void MyI2C_W_SCL(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue); //根据BitValue,设置SCL引脚的电平
Delay_us(10); //延时10us,防止时序频率超过要求
}
/**
* 函 数:I2C写SDA引脚电平
* 参 数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~0xFF
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue非0时,需要置SDA为高电平
*/
void MyI2C_W_SDA(uint8_t BitValue)
{
GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue); //根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
Delay_us(10); //延时10us,防止时序频率超过要求
}
/**
* 函 数:I2C读SDA引脚电平
* 参 数:无
* 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
* 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
*/
uint8_t MyI2C_R_SDA(void)
{
uint8_t BitValue;
BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11); //读取SDA电平
Delay_us(10); //延时10us,防止时序频率超过要求
return BitValue; //返回SDA电平
}
/**
* 函 数:I2C初始化
* 参 数:无
* 返 回 值:无
* 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化
*/
void MyI2C_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB10和PB11引脚初始化为开漏输出
/*设置默认电平*/
GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11); //设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}
/*协议层*/
/**
* 函 数:I2C起始
* 参 数:无
* 返 回 值:无
*/
void MyI2C_Start(void)
{
MyI2C_W_SDA(1); //释放SDA,确保SDA为高电平
MyI2C_W_SCL(1); //释放SCL,确保SCL为高电平
MyI2C_W_SDA(0); //在SCL高电平期间,拉低SDA,产生起始信号
MyI2C_W_SCL(0); //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
/**
* 函 数:I2C终止
* 参 数:无
* 返 回 值:无
*/
void MyI2C_Stop(void)
{
MyI2C_W_SDA(0); //拉低SDA,确保SDA为低电平
MyI2C_W_SCL(1); //释放SCL,使SCL呈现高电平
MyI2C_W_SDA(1); //在SCL高电平期间,释放SDA,产生终止信号
}
/**
* 函 数:I2C发送一个字节
* 参 数:Byte 要发送的一个字节数据,范围:0x00~0xFF
* 返 回 值:无
*/
void MyI2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i ++) //循环8次,主机依次发送数据的每一位
{
MyI2C_W_SDA(Byte & (0x80 >> i)); //使用掩码的方式取出Byte的指定一位数据并写入到SDA线
MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间读取SDA
MyI2C_W_SCL(0); //拉低SCL,主机开始发送下一位数据
}
}
/**
* 函 数:I2C接收一个字节
* 参 数:无
* 返 回 值:接收到的一个字节数据,范围:0x00~0xFF
*/
uint8_t MyI2C_ReceiveByte(void)
{
uint8_t i, Byte = 0x00; //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送
for (i = 0; i < 8; i ++) //循环8次,主机依次接收数据的每一位
{
MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDA
if (MyI2C_R_SDA() == 1){Byte |= (0x80 >> i);} //读取SDA数据,并存储到Byte变量
//当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
MyI2C_W_SCL(0); //拉低SCL,从机在SCL低电平期间写入SDA
}
return Byte; //返回接收到的一个字节数据
}
/**
* 函 数:I2C发送应答位
* 参 数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答
* 返 回 值:无
*/
void MyI2C_SendAck(uint8_t AckBit)
{
MyI2C_W_SDA(AckBit); //主机把应答位数据放到SDA线
MyI2C_W_SCL(1); //释放SCL,从机在SCL高电平期间,读取应答位
MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块
}
/**
* 函 数:I2C接收应答位
* 参 数:无
* 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答
*/
uint8_t MyI2C_ReceiveAck(void)
{
uint8_t AckBit; //定义应答位变量
MyI2C_W_SDA(1); //接收前,主机先确保释放SDA,避免干扰从机的数据发送
MyI2C_W_SCL(1); //释放SCL,主机机在SCL高电平期间读取SDA
AckBit = MyI2C_R_SDA(); //将应答位存储到变量里
MyI2C_W_SCL(0); //拉低SCL,开始下一个时序模块
return AckBit; //返回定义应答位变量
}
up主“江协科技”开源的源码,详情见【STM32入门教程-2023版 细致讲解 中文字幕】 https://www.bilibili.com/video/BV1th411z7sn/?p=33&share_source=copy_web&vd_source=359b3f2dc619c35ff01b522b4203c10d
#include "stm32f10x.h" // Device header
#include "MyI2C.h"
#include "Delay.h"
#define AT24C02_ADDRESS 0xA0
/*
函数:AT24C02芯片初始化
参数:无
返回值:无
*/
void AT24C02_Init()
{
MyI2C_Init();
}
/*
函数:AT24C02指定地址写
参数1:用户指定的地址
参数2:用户需要写入的数据
返回值:无
*/
void AT24C02_WriteByte(uint8_t WordAddress,uint8_t Data)
{
MyI2C_Start();
MyI2C_SendByte(AT24C02_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(WordAddress);
MyI2C_ReceiveAck();
MyI2C_SendByte(Data);
MyI2C_ReceiveAck();
MyI2C_Stop();
Delay_ms(5);//延时一下满足AT24C02的写循环的最小周期
}
/*
函数:AT24C02读取数据
参数:指定数据的地址
返回值:在该地址下读出的数据
*/
uint8_t AT24C02_ReadByte(uint8_t WordAddress)
{
uint8_t Data;
MyI2C_Start();
MyI2C_SendByte(AT24C02_ADDRESS);
MyI2C_ReceiveAck();
MyI2C_SendByte(WordAddress);
MyI2C_ReceiveAck();
MyI2C_Start();
MyI2C_SendByte(AT24C02_ADDRESS|0x01);
MyI2C_ReceiveAck();
Data = MyI2C_ReceiveByte();
MyI2C_SendAck(1);
MyI2C_Stop();
Delay_ms(5);
return Data;
}
作者自己敲的读写代码,注意要满足AT24C02的最小写周期
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AT24C02.h"
uint8_t Data[6] = {0x01,0x02,0x03,0x04}; //给定一个数组
int main(void)
{
OLED_Init();
AT24C02_Init();
OLED_ShowString(2,1,"Read :");
while (1)
{
AT24C02_WriteByte(0x10,Data[0]);
AT24C02_WriteByte(0x11,Data[1]);
AT24C02_WriteByte(0x12,Data[2]);
AT24C02_WriteByte(0x13,Data[3]);//将数组写入AT24C02模块中
OLED_ShowString(1,1,"WriteOK!");
OLED_ShowHexNum(3,1,AT24C02_ReadByte(0x10),2);
OLED_ShowHexNum(3,4,AT24C02_ReadByte(0x11),2);
OLED_ShowHexNum(3,7,AT24C02_ReadByte(0x12),2);
OLED_ShowHexNum(3,10,AT24C02_ReadByte(0x13),2);//将写入的数据读出
}
}
项目简单例程,OLED也是基于江协的工程的
功能拓展
另外芯片手册还描述了页写和应答查询等操作,感兴趣的可以下载该芯片手册来实现,这里附上链接:https://pan.baidu.com/s/12f3qixoLsml13Zd0ZDGFig 提取码:mbde