当前位置: 首页 > news >正文

AT24C02C-SSHM-T用法

下面的方案结合了提供的 PLC 程序、硬件原理图和 AT24C02C 数据手册,给出了在动平衡机项目中 EEPROM 的存储规划以及完整的驱动示例。AT24C02C 总容量 256 字节,分成 32 个 8 字节的页。因此写入时必须遵守 8 字节页边界,写操作完成后需要等待芯片内部自写周期完成。

需要存储的数据

动平衡机上采用 Delta A3 伺服控制转子,PLC 程序中有大量运行参数和调节量。对于 MCU 版本,为了断电后能够恢复这些参数,可将以下内容持久化到 AT24C02C 中:

类别参数说明
伺服通信参数stationID、baudRate、timeout从站地址、Modbus 波特率和超时。便于在上电时重新初始化 RS‑485。
伺服机械参数encoderResolution、gearRatio、maxSpeed、maxAccel、maxDecel编码器分辨率、减速比和速度/加减速度上限。决定电机运动范围。
动平衡特有参数balanceSpeed、searchSpeed平衡测试速度和寻找基准速度(rpm)。
positionAccuracy定位精度(度),用于 OK/NG 判断。
angleOffset安装基准与传感器基准的角度偏移(度)。
secondPosAngle、secondPosSpeed“二次定位”时的角度和速度,适用于某些转子需要二次修正。
photoSensorAlarmTime光电异常报警时的等待时间,单位 100 ms。
传感器选择sensorType0 为光电传感器,1 为接近开关。
验证字段magic、version、crc用于标识有效数据和校验完整性。

根据这些字段定义了一个 EEPROM_Settings_t 结构体(见下),约 55 字节,可一次性写入 EEPROM 的前一段地址。若还要持久化其他整定参数,只需在结构体末尾继续添加即可。

驱动和存取实现

下面给出一个完整的 AT24C02C 驱动和配置存取函数,基于 HAL I2C,已适配 MCU F103 的 hi2c1

头文件 at24c02c.h

#ifndef __AT24C02C_H
#define __AT24C02C_H#ifdef __cplusplus
extern "C" {
#endif#include "main.h"
#include <stdint.h>
#include <stdbool.h>/* A0~A2 接地时从机地址 0x50,HAL 需左移一位 */
#ifndef AT24C02C_I2C_ADDRESS
#define AT24C02C_I2C_ADDRESS    (0x50u << 1)
#endif#define AT24C02C_PAGE_SIZE   8u
#define AT24C02C_TOTAL_SIZE  256uHAL_StatusTypeDef AT24C02C_Write(uint8_t mem_addr,const uint8_t *data,uint16_t len);
HAL_StatusTypeDef AT24C02C_Read(uint8_t mem_addr,uint8_t *data,uint16_t len);/* 单字节封装 */
static inline HAL_StatusTypeDef AT24C02C_WriteByte(uint8_t mem_addr, uint8_t value) {return AT24C02C_Write(mem_addr, &value, 1u);
}
static inline HAL_StatusTypeDef AT24C02C_ReadByte(uint8_t mem_addr, uint8_t *value) {return AT24C02C_Read(mem_addr, value, 1u);
}/* 配置结构:包含伺服和动平衡所有参数 */
typedef struct __attribute__((packed)) {uint16_t magic;                 // 0xA3A3 表示有效数据uint8_t  version;               // 结构版本uint8_t  reserved0;/* 伺服通信参数 */uint8_t  stationID;uint32_t baudRate;uint16_t timeout;/* 伺服机械参数 */uint32_t encoderResolution;float    gearRatio;float    maxSpeed;float    maxAccel;float    maxDecel;/* 动平衡参数 */float    balanceSpeed;float    searchSpeed;float    positionAccuracy;float    angleOffset;float    secondPosAngle;float    secondPosSpeed;uint16_t photoSensorAlarmTime;uint8_t  sensorType;            // 0:光电,1:接近开关uint8_t  reserved1[3];uint16_t crc;                   // CRC16 校验
} EEPROM_Settings_t;/* CRC16/CCITT 计算 */
uint16_t EEPROM_CalcCRC16(const uint8_t *data, uint16_t len);/* 保存/读取整个配置 */
HAL_StatusTypeDef EEPROM_SaveSettings(const EEPROM_Settings_t *settings);
HAL_StatusTypeDef EEPROM_LoadSettings(EEPROM_Settings_t *settings);#ifdef __cplusplus
}
#endif
#endif /* __AT24C02C_H */

源文件 at24c02c.c

#include "at24c02c.h"
#include <string.h>extern I2C_HandleTypeDef hi2c1;/* 等待 EEPROM 自写完成 */
static HAL_StatusTypeDef AT24C02C_WaitReady(void) {for (uint32_t i = 0; i < 50u; i++) {if (HAL_I2C_IsDeviceReady(&hi2c1, AT24C02C_I2C_ADDRESS, 1, 5) == HAL_OK) {return HAL_OK;}HAL_Delay(1);}return HAL_ERROR;
}/* 分页写 */
HAL_StatusTypeDef AT24C02C_Write(uint8_t mem_addr,const uint8_t *data,uint16_t len) {if (!data || len == 0) return HAL_ERROR;if ((uint16_t)mem_addr + len > AT24C02C_TOTAL_SIZE) return HAL_ERROR;HAL_StatusTypeDef status = HAL_OK;while (len > 0 && status == HAL_OK) {uint8_t page_offset   = mem_addr & (AT24C02C_PAGE_SIZE - 1u);uint8_t space_in_page = AT24C02C_PAGE_SIZE - page_offset;uint16_t chunk        = (len < space_in_page) ? len : space_in_page;status = HAL_I2C_Mem_Write(&hi2c1, AT24C02C_I2C_ADDRESS,mem_addr, I2C_MEMADD_SIZE_8BIT,(uint8_t*)data, chunk, HAL_MAX_DELAY);if (status != HAL_OK) return status;status = AT24C02C_WaitReady();if (status != HAL_OK) return status;mem_addr += chunk;data    += chunk;len     -= chunk;}return status;
}/* 连续读 */
HAL_StatusTypeDef AT24C02C_Read(uint8_t mem_addr,uint8_t *data,uint16_t len) {if (!data || len == 0) return HAL_ERROR;if ((uint16_t)mem_addr + len > AT24C02C_TOTAL_SIZE) return HAL_ERROR;return HAL_I2C_Mem_Read(&hi2c1, AT24C02C_I2C_ADDRESS,mem_addr, I2C_MEMADD_SIZE_8BIT,data, len, HAL_MAX_DELAY);
}/* CRC16/CCITT */
uint16_t EEPROM_CalcCRC16(const uint8_t *data, uint16_t len) {uint16_t crc = 0xFFFFu;for (uint16_t i = 0; i < len; i++) {crc ^= ((uint16_t)data[i] << 8);for (uint8_t bit = 0; bit < 8u; bit++) {crc = (crc & 0x8000u) ? (crc << 1) ^ 0x1021u : (crc << 1);}}return crc;
}/* 保存配置到地址 0 */
HAL_StatusTypeDef EEPROM_SaveSettings(const EEPROM_Settings_t *settings) {if (!settings) return HAL_ERROR;EEPROM_Settings_t temp;memcpy(&temp, settings, sizeof(temp));temp.magic = 0xA3A3u;/* 计算 CRC,覆盖除 magic 外所有字段 */temp.crc = EEPROM_CalcCRC16(((const uint8_t *)&temp) + offsetof(EEPROM_Settings_t, version),sizeof(EEPROM_Settings_t)- offsetof(EEPROM_Settings_t, version) - sizeof(uint16_t));return AT24C02C_Write(0u, (const uint8_t *)&temp, sizeof(EEPROM_Settings_t));
}/* 读取并验证配置 */
HAL_StatusTypeDef EEPROM_LoadSettings(EEPROM_Settings_t *settings) {if (!settings) return HAL_ERROR;EEPROM_Settings_t temp;if (AT24C02C_Read(0u, (uint8_t*)&temp, sizeof(temp)) != HAL_OK) {return HAL_ERROR;}if (temp.magic != 0xA3A3u) {return HAL_ERROR;  // 没有有效数据}uint16_t calc_crc = EEPROM_CalcCRC16(((const uint8_t *)&temp) + offsetof(EEPROM_Settings_t, version),sizeof(EEPROM_Settings_t)- offsetof(EEPROM_Settings_t, version) - sizeof(uint16_t));if (calc_crc != temp.crc) {return HAL_ERROR;  // CRC 校验失败}memcpy(settings, &temp, sizeof(EEPROM_Settings_t));return HAL_OK;
}

使用示例

#include "at24c02c.h"
#include "tigerservo.h"// 启动时读取配置
void LoadConfig(void) {EEPROM_Settings_t cfg;if (EEPROM_LoadSettings(&cfg) == HAL_OK) {// 用 EEPROM 的参数覆盖默认值g_servoController.params.stationID         = cfg.stationID;g_servoController.params.baudRate          = cfg.baudRate;g_servoController.params.timeout           = cfg.timeout;g_servoController.params.encoderResolution = cfg.encoderResolution;g_servoController.params.gearRatio         = cfg.gearRatio;g_servoController.params.maxSpeed          = cfg.maxSpeed;g_servoController.params.maxAccel          = cfg.maxAccel;g_servoController.params.maxDecel          = cfg.maxDecel;g_servoController.balanceSpeed     = cfg.balanceSpeed;g_servoController.searchSpeed      = cfg.searchSpeed;g_servoController.positionAccuracy = cfg.positionAccuracy;g_servoController.angleOffset      = cfg.angleOffset;// 其它参数同理...g_tigerServoController.referenceSel = (cfg.sensorType == 0) ? PHOTOSENSOR : PROXIMITYSWITCH;} else {// EEPROM 没有有效数据,使用默认参数}
}// 参数修改后保存
void SaveConfig(void) {EEPROM_Settings_t cfg = {0};cfg.version              = 1;cfg.stationID            = g_servoController.params.stationID;cfg.baudRate             = g_servoController.params.baudRate;cfg.timeout              = g_servoController.params.timeout;cfg.encoderResolution    = g_servoController.params.encoderResolution;cfg.gearRatio            = g_servoController.params.gearRatio;cfg.maxSpeed             = g_servoController.params.maxSpeed;cfg.maxAccel             = g_servoController.params.maxAccel;cfg.maxDecel             = g_servoController.params.maxDecel;cfg.balanceSpeed         = g_servoController.balanceSpeed;cfg.searchSpeed          = g_servoController.searchSpeed;cfg.positionAccuracy     = g_servoController.positionAccuracy;cfg.angleOffset          = g_servoController.angleOffset;cfg.secondPosAngle       = /* 单位度 */;cfg.secondPosSpeed       = /* 单位 rpm */;cfg.photoSensorAlarmTime = /* 单位 100 ms */;cfg.sensorType           = (g_tigerServoController.referenceSel == PHOTOSENSOR) ? 0 : 1;EEPROM_SaveSettings(&cfg);
}

在主程序初始化时调用 LoadConfig() 可从 EEPROM 恢复参数;用户通过串口/HMI 调整参数后再调用 SaveConfig() 存入 EEPROM。这样就能在 F103 + RS‑485 项目中完全替代 PLC 方案,实现伺服上电参数、动平衡调节量的长期保存。


好,我用“图解 + 要点”把 AT24C02C 的容量、分页、地址与读写流程一次讲清。可以对照那张原理图一起看。


存储组织(2 Kbit = 256 B)

总体:256 字节
分页:32 页 × 8 字节/页
地址:8 位字节地址 0x00 ~ 0xFF
0x00 ┌──────── Page 0  ────────┐ 0x07
0x08 ├──────── Page 1  ────────┤ 0x0F
0x10 ├──────── Page 2  ────────┤ 0x17
0x18 ├──────── Page 3  ────────┤ 0x1F... │           ...           │ ...
0xE8 ├──────── Page 29 ────────┤ 0xEF
0xF0 ├──────── Page 30 ────────┤ 0xF7
0xF8 └──────── Page 31 ────────┘ 0xFF
  • 页号 = addr >> 3(因为每页 8 字节)
  • 页内偏移 = addr & 0x07

关键规则:一次“页写(Page Write)”最多 8 字节,不能跨页。若写入跨过页尾,芯片会在该页内回卷覆盖(wrap-around)。


I²C 从地址(7 位)

AT24C02C 的 7 位地址格式:1010 A2 A1 A0

  • 原理图里 A0/A1/A2 接地 ⇒ 7 位地址 = 0b1010 000 = 0x50
  • 在 STM32 HAL 里要左移一位(加上 R/W 位),所以用 0x50 << 1

基本时序(简图)

写(Page Write / Byte Write)

START
[0x50|W]  ACK
[word_addr] ACK      ← 0x00~0xFF
[data0]    ACK
[data1]    ACK
...(同页内最多 8 字节)
STOP
  • 之后芯片进入自写周期(~5 ms 典型)。
  • 需做 ACK 轮询:反复发 [0x50|W],直到应答,表示写完可继续下一次操作。

读(Random/Sequential Read)

随机读常见做法:

START
[0x50|W]   ACK
[word_addr] ACK
REPEATED START
[0x50|R]   ACK
[data0]    主机回 ACK(若还要继续读)
[data1]    主机回 ACK
... 最后一个字节 主机回 NACK
STOP

顺序读(Sequential)可跨页连续读;只有写才受 8 字节页限制


与原理图的核对

  • 上拉:SCL/SDA 各有 4.7 kΩ 上拉到 3.3 V ✅

  • 去耦:VCC 旁并 100 nF

  • A0/A1/A2 接地 ⇒ 地址 0x50 ✅

  • WP(写保护,脚 7):建议固定接 GND(允许写)。

    • 图里看起来 WP 的连线靠近 I²C 总线,请确认不要接到 SCL/SDA
    • 若接到 VCC = 永久写保护;若漂浮或跟随时钟,会出现随机写失败

代码里怎么用这个组织(要点)

  • 计算一次能写的字节数,避免跨页:

    uint8_t page_off = addr & 0x07;
    uint8_t space    = 8 - page_off;
    uint16_t chunk   = MIN(len, space);  // 本次写 chunk
    
  • 每次页写后做 ACK 轮询,直到 HAL_I2C_IsDeviceReady() 返回 OK。

  • 连续读不受页限制,直接 HAL_I2C_Mem_Read() 就行。

我给驱动里已经按这个规则做了:AT24C02C_Write() 会自动分页写等待写完成;读写接口分别是 AT24C02C_Read/Write,总容量用 AT24C02C_TOTAL_SIZE = 256,页大小 AT24C02C_PAGE_SIZE = 8


小例子(写 10 字节到 0x1C)

  • 地址 0x180x1F 是同一页,0x200x27 是下一页。

  • 从 0x1C 开始写 10 字节:

    • 第 1 次写:0x1C ~ 0x1F(4 字节,填满当前页)
    • 轮询就绪
    • 第 2 次写:0x20 ~ 0x25(剩余 6 字节,下一页)

如果一次把 10 字节直接丢给芯片,它会 在 0x18~0x1F 内回卷覆盖,导致后 6 字节写到 0x18~0x1D ——这就是“跨页回卷”的坑。


放什么数据最合适(结合项目)

建议把“会改、且要掉电保存”的参数集中做成一个结构体(几十字节),比如:

  • 通信:stationID、baudRate、timeout
  • 伺服:maxSpeed/maxAccel/maxDecel、gearRatio、encoderResolution
  • 动平衡:balanceSpeed、searchSpeed、positionAccuracy、angleOffset、secondPosAngle/secondPosSpeed、photoSensorAlarmTime、sensorType
  • 末尾加 CRC16 做校验,前面加 magicversion 做兼容

一次 EEPROM_SaveSettings() 全写;上电 EEPROM_LoadSettings() 校验通过就覆盖默认参数。


如果愿意,我也可以把当前参数和默认值直接按“起始地址 + 字段长度”再画一张地址分配表,或者帮把结构体映射到每个 EEPROM 地址段的“工程图”(类似内存地图)。更喜欢哪种展示方式?


太好了!我给你把 **AT24C02C 的“内存地图”**画清楚,并把每个参数放到具体地址。这样你在代码里能一眼对照、也方便联调。

存储组织快速图

容量:256 B = 32 页 × 8 B/页         地址:0x00 ~ 0xFF
页号 = addr >> 3    页内偏移 = addr & 0x070x00 ┌──────── Page 0 ────────┐ 0x07
0x08 ├──────── Page 1 ────────┤ 0x0F
0x10 ├──────── Page 2 ────────┤ 0x17... │          …             │ ...
0xF0 ├──────── Page 30 ───────┤ 0xF7
0xF8 └──────── Page 31 ───────┘ 0xFF

地址分配表(主副本各一份,便于容错)

主副本 A:0x00–0x3B;CRC:0x3A–0x3B
备份 B:0x40–0x7B;CRC:0x7A–0x7B(可选;不开启就留空)

单位说明:Speed=rpm;Angle=°;photoSensorAlarmTime=100 ms 步进。

起始结束长度名称类型备注 / 默认
0x000x012magicu160xA3A3
0x020x021versionu81
0x030x031stationIDu81
0x040x074baudRateu3238400
0x080x092timeoutu16ms,500
0x0A0x0D4encoderResolutionu3210000(例)
0x0E0x114gearRatiofloat1.0
0x120x154maxSpeedfloat3000
0x160x194maxAccelfloat1000
0x1A0x1D4maxDecelfloat1000
0x1E0x214balanceSpeedfloat600
0x220x254searchSpeedfloat200
0x260x294positionAccuracyfloat1.0
0x2A0x2D4angleOffsetfloat0.0
0x2E0x314secondPosAnglefloat0.0
0x320x354secondPosSpeedfloat200
0x360x372photoSensorAlarmTimeu16×100 ms,10
0x380x381sensorTypeu80=光电/1=接近
0x390x391reservedu8保留
0x3A0x3B2crc16u16CCITT

备份 B 完全同布局,起始地址换为 0x40

落在各页的边界(便于页写):

  • Page0: 0x00–0x07(magic/version/stationID/baudRate)
  • Page1: 0x08–0x0F(timeout/encoderRes/gearRatio 前 2B)
  • Page2: 0x10–0x17(gearRatio 尾/maxSpeed 前 2B)
  • …(驱动里已自动分页,无需手动切分,只是给你对齐参考)

读/写流程(建议)

  1. 加载(开机)

    • 先读 A(0x00–0x3B),校验 magic 与 CRC;
    • 若 A 失败,再读 B(0x40–0x7B);
    • 都失败 ⇒ 加载默认值。
  2. 保存(用户改参后)

    • 先写 A:按页写(8 B/次)→ ACK 轮询 → 全部写完 → 最后写 CRC 页;
    • 需要容错时再写 B(同样流程)。
    • 建议:参数未变化不写;写前先比较旧值。
  3. 注意

    • 写保护 WP 脚接 GND
    • 禁止跨页写(驱动已处理);
    • 浮点 float小端 存(STM32 默认),与 MCU 内存一致。

你已有驱动如何对应

  • AT24C02C_Write():已按 8 B 页自动切块并做 ACK 轮询;
  • EEPROM_SaveSettings():一次性把上表的数据结构写到 0x00
  • EEPROM_LoadSettings():从 0x00 读并校验 magic + CRC,失败再尝试 0x40(如果你启用备份)。

最小调用

void LoadConfig(void) {EEPROM_Settings_t cfg;if (EEPROM_LoadSettings(&cfg) == HAL_OK) {// 用 cfg 覆盖 g_servoController / g_tigerServoController} else {// 载入默认,并可立即 EEPROM_SaveSettings(&defaults);}
}void SaveConfig(void) {EEPROM_Settings_t cfg = {0};// 从当前运行参数填充 cfg ...EEPROM_SaveSettings(&cfg);
}

如果你希望强一致 + 断电保护,我可以把保存流程改成:
写 B → 校验 B → 写 A → 校验 A(带 1 字节“计数器”用于选择较新副本)。
需要的话告诉我,我直接把你现有 EEPROM_SaveSettings/LoadSettings 改成“主备容错版”。

http://www.lryc.cn/news/617944.html

相关文章:

  • leecode875 爱吃香蕉的珂珂
  • 每日一题:2的幂数组中查询范围内的乘积;快速幂算法
  • 工业数采引擎-通信协议(Modbus/DTU/自定义协议)
  • 【Linux】重生之从零开始学习运维之防火墙
  • C++ 限制类对象数量的技巧与实践
  • AcWing 6479. 点格棋
  • ​费马小定理​
  • 前端组件库双雄对决:Bootstrap vs Element UI 完全指南
  • Unknown collation: ‘utf8mb4_0900_ai_ci‘
  • 软考 系统架构设计师系列知识点之杂项集萃(121)
  • mysql基础(二)五分钟掌握全量与增量备份
  • OCSSA-VMD-Transformer轴承故障诊断,特征提取+编码器!
  • 视频剪辑的工作流程
  • socket编程TCP
  • 自然语言处理实战:用LSTM打造武侠小说生成器
  • 银河通用招人形机器人强化学习算法工程师了
  • IoT/透过oc_lwm2m/boudica150 源码中的AT指令序列,分析NB-IoT接入华为云物联网平台IoTDA的工作机制
  • openpnp - 顶部相机环形灯光DIY
  • Godot ------ 平滑拖动03
  • 企业高性能 Web 服务部署实践(基于 RHEL 9)
  • Jupyter lab保姆级教程和自动补齐功能实现
  • VMware 安装Ubuntu server 20.04
  • IPCP(IP Control Protocol,IP控制协议)
  • Rust 库开发全面指南
  • 《C++中 type_traits 的深入解析与应用》
  • 10种经典学习方法的指令化应用
  • 使用docker compose 部署dockge
  • 训推一体 | 暴雨X8848 G6服务器 x Intel®Gaudi® 2E AI加速卡
  • 【k近邻】 K-Nearest Neighbors算法k值的选择
  • es基本概念-自学笔记