STM32F103 读写铁电MB85RC16V
最近测试STM32与铁电读取数据,记录如下:
1. 说明:
1)软件:STM32CubeMX + Keil v5;
2) 硬件:STM32F103VCT6 + MD85RC16V
2. 采用I2C通讯连接,硬件如下:

3. 铁电存储器说明:

设备地址组成:

各部分详解
-
设备类型码
-
固定为
1010,用于标识这是一颗富士通的FRAM存储器。
-
-
存储器高地址码
-
这3位 (A2, A1, A0) 不是 通过芯片外部引脚设置的从机地址选择位。
-
它们实际上是 11位存储器地址的最高3位。
-
因此,这个芯片的I²C总线地址从设备地址字的角度看是固定的,但实际访问的存储单元由这3位和后续发送的8位低地址共同决定。
-
也就是说,这款芯片的设备地址不可编辑,采用HAL库时,直接输入固定的0xA0写入设备地址。
-
最终的设备地址是 固定的4位类型码
1010+ 3位存储器地址高三位 (A2, A1, A0) + 1位0 组成(读写HAL库函数自己处理)。
-
如果需要安装多颗铁电存储芯片,就需要采取不同的I2C端口,或者换其他型号。
4. STM32CubeMX配置
直接选取对应I2C,我这里连接的是I2C2,

配置完直接创建代码即可。
5. 读写函数
eeprom.h
#ifndef __EEPROM_h__
#define __EEPROM_h__
#include "i2c.h"
#include "stdint.h"
#define FALSE 0X00
#define TRUE 0X01
// 读取多个字节从FRAM
extern unsigned char FRAM_ReadStr(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int read_length);
// 写入多个字节到FRAM
extern unsigned char FRAM_WriteStr(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int write_length);
#endif
eeprom.c
#include "eeprom.h"
#define MB85RC16
/**
* @brief 读取FRAM数据
* @param device_address: I2C设备地址
* @param memory_address: FRAM内存地址
* @param buffer_point: 数据接收缓冲区
* @param read_length: 读取数据长度
* @retval 0 - 读取失败, 1 - 读取成功
*/
unsigned char FRAM_ReadStr(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int read_length)
{
HAL_StatusTypeDef status;
uint8_t mem_addr_high = (memory_address >> 8) & 0x07;
unsigned char dev_addr = (unsigned char)(device_address | (mem_addr_high << 1));
status = HAL_I2C_Mem_Read(&hi2c2, dev_addr, memory_address & 0xFF, I2C_MEMADD_SIZE_8BIT, buffer_point, read_length, 100);
return (status == HAL_OK) ? TRUE : FALSE;
}
/**
* @brief 写入FRAM数据
* @param device_address: I2C设备地址
* @param memory_address: FRAM内存地址
* @param buffer_point: 数据发送缓冲区
* @param write_length: 写入数据长度
* @retval 0 - 写入失败, 1 - 写入成功
*/
unsigned char FRAM_WriteStr(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int write_length)
{
HAL_StatusTypeDef status;
uint8_t mem_addr_high = (memory_address >> 8) & 0x07;
unsigned char dev_addr = (unsigned char)(device_address | (mem_addr_high << 1));
status = HAL_I2C_Mem_Write(&hi2c2, dev_addr, memory_address & 0xFF, I2C_MEMADD_SIZE_8BIT, buffer_point, write_length, 100);
return (status == HAL_OK) ? TRUE : FALSE;
}
/**
* @brief 写入多个字节到FRAM
* @param device_address: I2C设备地址
* @param memory_address: FRAM内存地址
* @param buffer_point: 数据发送缓冲区
* @param write_length: 写入数据长度
* @param verify_type: 校验类型
* @retval 0 - 写入失败, 1 - 写入成功
*/
unsigned char EE_WriteNByte(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int write_length, unsigned char verify_type)
{
return FRAM_WriteStr(device_address, memory_address, buffer_point, write_length);
}
/**
* @brief 读取多个字节从FRAM
* @param device_address: I2C设备地址
* @param memory_address: FRAM内存地址
* @param buffer_point: 数据接收缓冲区
* @param read_length: 读取数据长度
* @retval 0 - 读取失败, 1 - 读取成功
*/
unsigned char EE_ReadNByte(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int read_length)
{
return FRAM_ReadStr(device_address, memory_address, buffer_point, read_length);
}
调用参考:
![]()
![]()
其中,0xA0是铁电需要传入的固定地址,实际传入到HAL_I2C_Mem_Read()的地址计算如下:
uint8_t mem_addr_high = (memory_address >> 8) & 0x07;
unsigned char dev_addr = (unsigned char)(0xA0 | (mem_addr_high << 1));
其中,memory_adress是传入的需要读取或写入的内存地址,实际地址需要1010 + 内存地址的高8位(实际就三位,因为最大存储是2048个字节(2^11=2048,11=8+3))。
最后实际测试读写均正常。
为什么不用模拟I2C?
-
效率:硬件I2C由专门的硬件电路实现,通常使用DMA或中断,不占用CPU太多时间。而模拟I2C需要CPU不断翻转GPIO,占用大量CPU资源。
-
稳定性:硬件I2C的时序由硬件保证,非常精确,不受其他中断或任务影响。模拟I2C的时序由软件延时实现,容易受中断和其他任务干扰。
-
复杂性:使用HAL库可以简化编程,只需调用库函数即可完成I2C通信。而模拟I2C需要自己实现起始条件、停止条件、应答、数据读写等所有时序。
-
功能完整性:硬件I2C通常支持I2C协议的所有特性,如时钟延展、多主机仲裁等。模拟I2C实现这些功能较为复杂。
-
可维护性:使用标准的HAL库函数,代码更易于理解和维护。模拟I2C的代码需要自己维护,且可能因不同处理器平台或引脚而异。
-
性能:硬件I2C通常可以支持更高的时钟频率(如1MHz),而模拟I2C由于软件延时限制,通常速度较低。
如果,你的硬件引脚被占用,或者你就想纯手搓I2C,可以参考以下模拟I2C。模拟I2C不需要在STM32CubeMX中配置I2C,硬件上只需要连接任意GPIO口即可。
eeprom.h
#ifndef __EEPROM_h__
#define __EEPROM_h__
#include "main.h"
// #include "adc.h"
// #include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
#define FALSE 0X00
#define TRUE 0X01
#define SDA_OUT \
{ \
GPIO_InitTypeDef GPIO_InitStruct = {0}; \
GPIO_InitStruct.Pin = EE_SDA_Pin; \
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD; \
GPIO_InitStruct.Pull = GPIO_PULLUP; \
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; \
HAL_GPIO_Init(EE_SDA_GPIO_Port, &GPIO_InitStruct); \
SDA_H; \
}
#define SDA_IN \
{ \
GPIO_InitTypeDef GPIO_InitStruct = {0}; \
GPIO_InitStruct.Pin = EE_SDA_Pin; \
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; \
GPIO_InitStruct.Pull = GPIO_NOPULL; \
HAL_GPIO_Init(EE_SDA_GPIO_Port, &GPIO_InitStruct); \
}
#define SCL_OUT \
{ \
GPIO_InitTypeDef GPIO_InitStruct = {0}; \
GPIO_InitStruct.Pin = EE_SCL_Pin; \
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; \
GPIO_InitStruct.Pull = GPIO_PULLUP; \
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; \
HAL_GPIO_Init(EE_SCL_GPIO_Port, &GPIO_InitStruct); \
SCL_H; \
}
#define SDA_L \
{ \
HAL_GPIO_WritePin(EE_SDA_GPIO_Port, EE_SDA_Pin, GPIO_PIN_RESET); \
}
#define SDA_H \
{ \
HAL_GPIO_WritePin(EE_SDA_GPIO_Port, EE_SDA_Pin, GPIO_PIN_SET); \
}
#define SCL_L \
{ \
HAL_GPIO_WritePin(EE_SCL_GPIO_Port, EE_SCL_Pin, GPIO_PIN_RESET); \
}
#define SCL_H \
{ \
HAL_GPIO_WritePin(EE_SCL_GPIO_Port, EE_SCL_Pin, GPIO_PIN_SET); \
}
#define READ_SDA_EE HAL_GPIO_ReadPin(EE_SDA_GPIO_Port, EE_SDA_Pin)
extern unsigned char EE_WriteNByte(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int write_length, unsigned char verify_type);
// 读取多个字节从FRAM
extern unsigned char FRAM_ReadStr(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int read_length);
// 写入多个字节到FRAM
extern unsigned char FRAM_WriteStr(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int write_length);
// IIC初始化
extern void I2C_Init(void);
extern void Start_I2c(void);
extern void Stop_I2c(void);
#endif
eeprom.c
#include "eeprom.h"
#define MB85RC16
void delay_ms(unsigned int t)
{
unsigned int i = 0;
unsigned int j = 0;
for (i = t; i > 0; i--)
{
j = 100;
while (j--)
;
}
}
void delay_us(unsigned int t)
{
unsigned int i = 0;
unsigned int j = 0;
for (i = t; i > 0; i--)
{
j = 10;
while (j--)
;
}
}
unsigned char ack; /*应答标志位*/
/**
* @brief 产生I2C总线启动信号
* @note SCL高电平时,SDA由高变低
*/
void Start_I2c()
{
SDA_OUT;
SDA_H; // SDA线拉高
delay_us(1);
SCL_H;
delay_us(5); // 起始信号持续时间约为4.7us,建议延时
SDA_L; // 产生起始信号
delay_us(5); // 起始信号持续时间约为4us
SCL_L; // 释放I2C总线,准备接收从机应答
delay_us(2);
}
/**
* @brief 产生I2C总线停止信号
* @note SCL高电平时,SDA由低变高
*/
void Stop_I2c()
{
SDA_OUT;
SCL_L;
delay_us(2);
SDA_L; // 产生停止信号
delay_us(3); // 停止信号持续时间约为4us
SCL_H; // 释放总线时钟信号约为4us
delay_us(5);
SDA_H; // 释放I2C总线
delay_us(4);
}
/**
* @brief 产生I2C_Ack
* @note 该函数用于产生I2C应答信号
* @param None
* @retval None
*/
void I2C_Ack(void) // IIC应答信号
{
SDA_OUT;
SDA_L;
delay_us(3);
SCL_H;
delay_us(5);
SCL_L;
delay_us(2);
SDA_H;
}
/**
* @brief 产生I2C_NoAck
* @note 该函数用于产生I2C非应答信号
* @param None
* @retval None
*/
void I2C_NoAck(void) // IIC非应答信号
{
SDA_OUT;
SDA_H;
delay_us(3);
SCL_H;
delay_us(5);
SCL_L;
delay_us(2);
}
/**
* @brief 发送一个字节
* @param c: 要发送的字节
* @retval None
*/
void SendByte(unsigned char c)
{
unsigned char BitCnt;
SDA_OUT;
SCL_L;
for (BitCnt = 0; BitCnt < 8; BitCnt++) // 发送的字节长度为8位
{
if (c & 0x80)
{
SDA_H; // 判断高位
}
else
{
SDA_L;
}
delay_us(6);
SCL_H; // 产生时钟信号,通知从机准备接收数据
delay_us(6); // 确保时钟线稳定在高电平
SCL_L;
delay_us(6);
c = c << 1;
}
SDA_H;
SDA_IN;
// 8位数据接收准备,等待应答位
delay_us(1);
SCL_H;
delay_us(5);
if (READ_SDA_EE == 1)
{
ack = 0; // 0应答位1非应答位
}
else
{
ack = 1; // 判断是否收到应答信号
}
SCL_L;
delay_us(2);
}
/**
* @brief 接收一个字节
* @param None
* @retval 接收到的字节
*/
unsigned char RcvByte()
{
unsigned char retc, j;
unsigned char BitCnt;
retc = 0; // 接收数据
SDA_IN; // SDA线设置为输入模式
SDA_H;
for (BitCnt = 0; BitCnt < 8; BitCnt++)
{
SCL_H; // 产生时钟信号,确保数据有效
delay_us(5);
retc = retc << 1;
SDA_IN;
j = READ_SDA_EE;
if (j != 0)
{
retc = retc | 0x01; // 判断最低位, 如果接收到高电平则retc |= 0x01
}
delay_us(2);
SCL_L;
delay_us(2);
}
return (retc);
}
/**
* @brief 产生I2C应答信号
* @param a: 0 - 发送应答信号, 1 - 发送非应答信号
* @retval None
*/
void Ack_I2c(unsigned char a)
{
SDA_OUT;
if (a == 0)
{
SDA_L; // 产生应答信号
}
else
{
SDA_H;
}
delay_us(4);
SCL_H;
delay_us(7); // 确保时钟线稳定在高电平
SCL_L; // 释放时钟信号,准备接收I2C从机的应答
delay_us(3);
}
/**
* @brief 读取FRAM数据
* @param device_address: I2C设备地址
* @param memory_address: FRAM内存地址
* @param buffer_point: 数据接收缓冲区
* @param read_length: 读取数据长度
* @retval 0 - 读取失败, 1 - 读取成功
*/
unsigned char FRAM_ReadStr(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int read_length)
{
unsigned int i;
I2C_Init();
Start_I2c(); // 产生起始信号
SendByte((unsigned char)(device_address | ((memory_address >> 7) & 0x0E))); // 发送设备地址
if (ack == 0)
{
Stop_I2c();
return (0);
}
SendByte(memory_address & 0xFF); // 发送内存地址低8位
if (ack == 0)
{
Stop_I2c();
return (0);
}
delay_us(10);
Start_I2c(); // 产生起始信号
SendByte((unsigned char)(device_address | ((memory_address >> 7) & 0x0E) | 0x01)); // 发送设备地址
if (ack == 0)
{
Stop_I2c();
return (0);
}
for (i = 0; i < read_length - 1; i++)
{
*buffer_point = RcvByte(); // 接收数据
Ack_I2c(0); // 发送应答信号
buffer_point++;
}
*buffer_point = RcvByte(); // 接收数据
Ack_I2c(1); // 发送非应答信号
delay_us(5);
Stop_I2c(); // 产生停止信号
return (1);
}
/**
* @brief 写入FRAM数据
* @param device_address: I2C设备地址
* @param memory_address: FRAM内存地址
* @param buffer_point: 数据发送缓冲区
* @param write_length: 写入数据长度
* @retval 0 - 写入失败, 1 - 写入成功
*/
unsigned char FRAM_WriteStr(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int write_length)
{
unsigned int i;
I2C_Init();
Start_I2c(); // 产生起始信号
SendByte((unsigned char)(device_address | ((memory_address >> 7) & 0x0E))); // 发送设备地址
if (ack == 0)
{
Stop_I2c();
return (FALSE);
}
SendByte(memory_address & 0xFF); // 发送内存地址低8位
if (ack == 0)
{
Stop_I2c();
return (FALSE);
}
for (i = 0; i < write_length; i++)
{
SendByte(*buffer_point); // 发送数据
if (ack == 0)
{
Stop_I2c();
return (FALSE);
}
buffer_point++;
}
Stop_I2c(); // 产生停止信号
return (TRUE);
}
/**
* @brief 写入多个字节到FRAM
* @param device_address: I2C设备地址
* @param memory_address: FRAM内存地址
* @param buffer_point: 数据发送缓冲区
* @param write_length: 写入数据长度
* @param verify_type: 校验类型
* @retval 0 - 写入失败, 1 - 写入成功
*/
unsigned char EE_WriteNByte(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int write_length, unsigned char verify_type)
{
FRAM_WriteStr(device_address, memory_address, buffer_point, write_length);
return 1;
}
/**
* @brief 读取多个字节从FRAM
* @param device_address: I2C设备地址
* @param memory_address: FRAM内存地址
* @param buffer_point: 数据接收缓冲区
* @param read_length: 读取数据长度
* @retval 0 - 读取失败, 1 - 读取成功
*/
unsigned char EE_ReadNByte(unsigned char device_address, unsigned int memory_address, unsigned char *buffer_point, unsigned int read_length)
{
unsigned char check_flag = 0;
FRAM_ReadStr(device_address, memory_address, buffer_point, read_length);
return check_flag;
}
/**
* @brief 初始化IIC
* @param None
* @retval None
*/
void I2C_Init(void) // 初始化IIC
{
SCL_OUT; // SCL线设置为输出
SDA_OUT; // SDA线设置为输出
}
两种函数最终调用时通用。具体请自测。
更多推荐



所有评论(0)