今日学习移植MSP432的IIC总线协议,并用此驱动AT24C02芯片实现写入以及读取的功能,然后实现打印开机复位次数的效果。
文章贴出测试工程,测试截图,测试代码~
其实是实在看不懂MSP432有关于FLASH存储操作相关的英文手册与例程,没法实现掉电保护数据等功能,才想到用AT24C02进行代替~
AT24C02基础知识:
下图就是AT24C02,旁边是比它记性更好,更睿智的大哥:AT24C32:
它们通过IIC总线通信,可以实现数据的写入与读取,
24c02简介
24C02是一个2Kbit(0~255个字节)的串行EEPROM存储芯片,可存储256个字节数据。工作电压范围为1.8V到6.0V,具有低功耗CMOS技术,自定时擦写周期,1000000次编程/擦除周期,可保存数据100年。24C02有一个16字节的页写缓冲器和一个写保护功能。通过I2C总线通讯读写芯片数据,通讯时钟频率可达400KHz。
可以通过存储IC的型号来计算芯片的存储容量是多大;
比如24C02后面的02表示的是可存储2Kbit的数据,转换为字节的存储量为21024/8 = 256byte;
有比如24C04后面的04表示的是可存储4Kbit的数据,转换为字节的储存量为41024/8 = 512byte;以此来类推其它型号的存储空间。
IIC主机轮询设备用从机地址,读写数据用存储区地址24C02共256字节数据,那么存储区域地址就是00H-FFH
24C02有两种工作模式:
字节写入模式:
结合技术文档我认为该模式是这样工作的:首先是可以再任意的地址(0x00~0xFF)写入一个字节,也可以在某一地址连续的写入N字节,而且不需要翻页,从技术手册得知,答题时说字节写入模式下,页指针根写入数据的多少来自动增加实现翻页功能,不用自己在程序里边实现;
页写入模式:
页写入模式下,手册上写着,一页可以存8字节,当存储的数据大于8时,则会覆盖先前保存的数据,例如,有16个数据 uchar data[16]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15},从24C02的0x00地址开始存放,当存完一页(8个)时,第9个数据会保存在0x00,覆盖掉开始保存的1,后边的数据一次类推,这样的现象叫做“翻转”,如果想写完一页后将剩余的数据保存在下一页,页指针需要自己来设定。
注意:
在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。
写数据的时候需要注意,E2PROM是先写到缓冲区,然后再“搬运到”到掉电非易失区。所以这个过程需要一定的时间,AT24C02这个过程是不超过5ms!
所以,当我们在写多个字节时,写入一个字节之后,再写入下一个字节之前,必须延时5ms才可以.
AT24C32与AT24C02的关系:
AT24C02和AT24C32都是EEPROM(Electrically Erasable Programmable Read-Only Memory)芯片,用于存储数据。它们的主要区别在于存储容量和寻址能力。
AT24C02是一种2K位的EEPROM,存储容量为256字节(每个字节包含8位)。它可以支持8位地址总线,因此可以寻址256个不同的存储位置。
AT24C32是一种32K位的EEPROM,存储容量为4千字节(每个字节包含8位)。它可以支持16位地址总线,因此可以寻址65,536个不同的存储位置,比AT24C02大了256倍。
因此,AT24C32相对于AT24C02有更大的存储容量和更大的寻址能力。这意味着AT24C32可以存储更多的数据,并且可以更灵活地访问不同的存储位置。然而,AT24C32也更昂贵一些。(差不多贵不到五毛钱哈哈哈)
IIC通信代码移植:
以下为我移植的IIC通信代码,用软件方式模拟:
#include "IIC.h"
/// @brief iic初始化
/// @param iic
/// @author wyy
void I2c_Init(soft_iic_struct *iic)
{
gpio_init(iic->scl,GPO,1);
gpio_init(iic->sda,GPO,1);
}
/// @brief iic起始信号
/// @param iic
/// @author wyy
void I2c_Start(soft_iic_struct *iic)
{
gpio_init(iic->scl,GPO,1);
gpio_init(iic->sda,GPO,1);
delay_us(2);
gpio_set(iic->sda,0); // START:when CLK is high,DATA change form high to low
delay_us(2);
gpio_set(iic->scl,0);
}
// 产生IIC停止信号
void I2c_Stop(soft_iic_struct *iic)
{
gpio_set(iic->scl,0);
gpio_init(iic->sda,GPO,0);// STOP:when CLK is high DATA change form low to high
delay_us(2);
gpio_set(iic->scl,1);
gpio_set(iic->sda,1); // 发送I2C总线结束信号
delay_us(2);
}
// 等待应答信号到来
// 返回值:1,接收应答失败
// 0,接收应答成功
u8 I2c_WaitAck(soft_iic_struct *iic) // 测数据信号的电平
{
u8 ucErrTime = 0;
gpio_init(iic->sda,GPI,0);// 切换为输入模式
gpio_set(iic->scl,1);
delay_us(2);
gpio_set(iic->sda,1);
delay_us(2);
while (gpio_get_level(iic->sda))
{
ucErrTime++;
if (ucErrTime > 250)
{
I2c_Stop(iic);
return 1;
}
}
gpio_set(iic->scl,0);
return 0;
}
/// @brief 产生ACK应答
/// @param iic
/// @author wyy
void I2C_Ack(soft_iic_struct *iic)
{
gpio_set(iic->scl,0);
gpio_init(iic->sda,GPO,0); // sda输出模式
delay_us(2);
gpio_set(iic->scl,1);
delay_us(2);
gpio_set(iic->scl,0);
}
/// @brief 不产生ACK应答
/// @param iic
/// @author wyy
void I2C_NAck(soft_iic_struct *iic)
{
gpio_set(iic->scl,0);
gpio_init(iic->sda,GPO,1); // sda输出模式
delay_us(2);
gpio_set(iic->scl,1);
delay_us(2);
gpio_set(iic->scl,0);
}
/// @brief 写入一个字节
/// @param iic 通信对象
/// @param dat 写入的数据
/// @author wyy
void I2C_Send_Byte(soft_iic_struct *iic,uint8_t dat)
{
u8 i;
gpio_init(iic->sda,GPO,1);
gpio_set(iic->scl,0);
delay_us(2);
for (i = 0; i < 8; i++)
{
if (dat & 0x80) // 将dat的8位从最高位依次写入
{
gpio_set(iic->sda,1);
delay_us(2);
}
else
{
gpio_set(iic->sda,0);
delay_us(2);
}
dat <<= 1;
gpio_set(iic->scl,1); // 将时钟信号设置为高电平,sda不允许变化
delay_us(4);
gpio_set(iic->scl,0);
delay_us(2);
}
delay_us(2);
}
/// @brief 读1个字节,ack=1时,发送ACK,ack=0,发送nACK
/// @param iic 通信对象
/// @param ack 是否发送ACK
/// @return 读到的字节\
/// @author wyy
u8 I2C_Read_Byte(soft_iic_struct *iic,u8 ack)
{
u8 i = 0;
u8 dat = 0;
gpio_init(iic->sda,GPI,0); // SDA设置为输入
for (i = 0; i < 8; i++)
{
gpio_set(iic->scl,0);
delay_us(2);
gpio_set(iic->scl,1);
dat <<= 1;
if (gpio_get_level(iic->sda))
dat++;
delay_us(2);
}
if (!ack)
{
I2C_NAck(iic);
}
else
{
I2C_Ack(iic);
}
return dat;
}
/// @brief IIC写指定设备 指定寄存器的一个值
/// @param iic 通信对象
/// @param Addr 目标设备地址
/// @param reg 目标寄存器
/// @param dat 写入的数据
/// @return
/// @author wyy
u8 I2C_Write_data(soft_iic_struct *iic,u8 Addr, u8 reg, u8 dat)
{
I2c_Start(iic);
I2C_Send_Byte(iic,(Addr << 1) | 0); // 发送器件地址+写命令
if (I2c_WaitAck(iic))
{
I2c_Stop(iic);
return 1;
}
I2C_Send_Byte(iic,reg); // 写寄存器地址
I2c_WaitAck(iic); // 等待应答
I2C_Send_Byte(iic,dat); // 发送数据
if (I2c_WaitAck(iic)) // 等待ACK
{
return 1;
}
I2c_Stop(iic);
return 0;
}
/// @brief IIC连续读
/// @param iic
/// @param addr 器件地址
/// @param reg 要读取的寄存器地址
/// @param len 要读取的长度
/// @param buf 读取到的数据存储区
/// @return 0,正常,其他,错误代码
u8 I2C_Read_Len(soft_iic_struct *iic,u8 addr, u8 reg, u8 len, u8 *buf)
{
I2c_Start(iic);
I2C_Send_Byte(iic,(addr << 1) | 0); // 发送器件地址+写命令
if (I2c_WaitAck(iic)) // 等待应答
{
I2c_Stop(iic);
return 1;
}
I2C_Send_Byte(iic,reg); // 写寄存器地址
I2c_WaitAck(iic); // 等待应答
I2c_Start(iic);
I2C_Send_Byte(iic,(addr << 1) | 1); // 发送器件地址+读命令
I2c_WaitAck(iic); // 等待应答
while (len)
{
if (len == 1)
*buf = I2C_Read_Byte(iic,0); // 读数据,发送nACK
else
*buf = I2C_Read_Byte(iic,1); // 读数据,发送ACK
len--;
buf++;
}
I2c_Stop(iic); // 产生一个停止条件
return 0;
}
u8 I2c_Write_Len(soft_iic_struct *iic,u8 addr,u8 reg,u8 len,u8 *buf)
{
u8 i;
I2c_Start(iic);
I2C_Send_Byte(iic,(addr<<1)|0);//发送器件地址+写命令 iic地址是7位+读写(0/1)操作位
if(I2c_WaitAck(iic)) //等待应答
{
I2c_Stop(iic);
return 1;
}
I2C_Send_Byte(iic,reg); //写寄存器地址
I2c_WaitAck(iic); //等待应答
for(i=0;i<len;i++)
{
I2C_Send_Byte(iic,buf[i]); //发送数据
if(I2c_WaitAck(iic)) //等待ACK
{
I2c_Stop(iic);
return 1;
}
}
I2c_Stop(iic);
return 0;
}
#ifndef _IIC_h_
#define _IIC_h_
#include <ti/devices/msp432p4xx/driverlib/driverlib.h>
#include "delay.h"
#define sda BITBAND_PERI(P1OUT,6)
#define scl BITBAND_PERI(P1OUT,7)
void init_SCL_SDA(void); //初始化总线引脚
//以下为IIC必要的软件模拟:
static void I2C_Delay(unsigned char n);
void I2CStart(void);
void I2CStop(void);
void I2CSendByte(unsigned char byt);
unsigned char I2CReceiveByte(void);
unsigned char I2CWaitAck(void);
void I2CSendAck(unsigned char ackbit);
#endif
AT24C02_EEPROM操作函数的编写:
/*******************************************************************************
* 函 数 名 : at24c02_write_one_byte
* 函数功能 : 在AT24CXX指定地址写入一个数据
* 输 入 : addr:写入数据的目的地址
dat:要写入的数据
* 输 出 : 无
*******************************************************************************/
void at24c02_save_one_byte(u8 addr, u8 dat)
{
I2c_Start(&at24c02);
I2C_Send_Byte(&at24c02, 0XA0); // 发送写命令
I2c_WaitAck(&at24c02);
I2C_Send_Byte(&at24c02, addr); // 发送写地址
I2c_WaitAck(&at24c02);
I2C_Send_Byte(&at24c02, dat); // 发送写地址
I2c_WaitAck(&at24c02);
I2c_Stop(&at24c02);
}
/*******************************************************************************
* 函 数 名 : at24c02_read_one_byte
* 函数功能 : 在AT24CXX指定地址读出一个数据
* 输 入 : addr:开始读数的地址
* 输 出 : 读到的数据
*******************************************************************************/
u8 at24c02_read_one_byte(u8 addr)
{
u8 temp = 0;
I2c_Start(&at24c02);
I2C_Send_Byte(&at24c02, 0XA0); // 发送写命令
I2c_WaitAck(&at24c02);
I2C_Send_Byte(&at24c02, addr); // 发送写地址
I2c_WaitAck(&at24c02);
I2c_Start(&at24c02);
I2C_Send_Byte(&at24c02, 0XA1); // 进入接收模式
I2c_WaitAck(&at24c02);
temp = I2C_Read_Byte(&at24c02, 0); // 读取字节
I2c_Stop(&at24c02); // 产生一个停止条件
return temp; // 返回读取的数据
}
主函数测试:
在主函数对移植代码测试,按下复位键储存开机次数:
#include "main.h"
char i=0;
int main(void)
{
inint_all(); //初始化所有模块
i=at24c02_read_one_byte(0x02);
// i=0;
printf("i=%d\r\n",i);
i++;
at24c02_save_one_byte(0x02,i);
while (1)
{
}
}
//初始化所有模块
void inint_all(void)
{
SysInit(); //时钟配置
delay_init(); //delay_ms函数配置
uart_init(115200);
At24c02_Init();
printf("Hello,MSP432!\r\n"); //串口打印测试字符
MAP_Interrupt_enableMaster(); // 开启总中断
}
测试效果:
图中我手中的板子是淘宝购买的MSP432P401R红色Launch_pad仿制版,全部功能,全部代码实现与红板子一样,只是便宜以及下载程序不方便:
测试工程下载:
文章只贴出关键代码,全部代码在测试工程提供下载:
https://download.csdn.net/download/qq_64257614/88114613