#include "database.h" // 内部 IAP 命令寄存器指令定义 #define IAP_CMD_READ 1 // IAP 字节读指令 #define IAP_CMD_WRITE 2 // IAP 字节写指令 #define IAP_CMD_ERASE 3 // IAP 扇区擦除指令 // 定义法语传感器默认名称常量数组,分别对应类型 0~4 char *code sensor_names_fr[5] = { "PORTE", // 0: 门磁 (法语: 门) "PIR", // 1: 红外探测器 "FUMEE", // 2: 烟雾传感器 (法语: 烟雾) "GAZ", // 3: 燃气泄漏传感器 (法语: 燃气) "EAU" // 4: 漏水/水浸传感器 (法语: 水) }; /** * @brief 关闭 IAP 寄存器控制并清理地址 * @details 每次 IAP 闪存操作后调用,确保安全,防止意外指针写入损坏 EEPROM 中的关键配置数据。 */ void IAP_Disable(void) { IAP_CONTR = 0; // 关闭 IAP 功能,禁用触发 IAP_CMD = 0; // 清除内部命令 IAP_TRIG = 0; // 清零触发寄存器 IAP_ADDRH = 0xff; // 地址指针高八位置无效,防止意外指向 IAP_ADDRL = 0xff; // 地址指针低八位置无效 } /** * @brief 读取指定 EEPROM 偏置地址处的 1 字节数据 * @param addr IAP 操作的目标物理地址 * @return u8 读取到的数据 */ u8 IAP_ReadByte(u16 addr) { IAP_CONTR = 0x80; // 开启 IAP/EEPROM 功能,设置等待时间 IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); // 设置闪存读写等待时间参数 (以MHz为单位,例如24MHz即24) IAP_CMD = IAP_CMD_READ; // 发送读字节命令 IAP_ADDRL = (u8)addr; // 加载目标低八位地址 IAP_ADDRH = (u8)(addr >> 8); // 加载目标高八位地址 IAP_ADDRE = 0; // 重置拓展物理地址 (STC32G内部EEPROM通常不需要) IAP_TRIG = 0x5a; // 触发写序列第一步密码 IAP_TRIG = 0xa5; // 触发写序列第二步密码 (必须连续送入,才能使能擦写) _nop_(); _nop_(); _nop_(); _nop_(); // CPU 等待 4 个周期直至读取操作完成 IAP_Disable(); // 读完后立即关闭 IAP 控制 return IAP_DATA; // 返回读取出的数据寄存器字节 } /** * @brief 在 EEPROM 指定地址写入 1 字节数据 * @param addr 目标物理地址 * @param dat 要写入的字节 */ void IAP_WriteByte(u16 addr, u8 dat) { IAP_CONTR = 0x80; // 开启 IAP/EEPROM 功能 IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); // 匹配工作频率对应的读写周期 IAP_CMD = IAP_CMD_WRITE; // 发送写字节命令 IAP_ADDRL = (u8)addr; // 送入低字节地址 IAP_ADDRH = (u8)(addr >> 8); // 送入高字节地址 IAP_ADDRE = 0; // 送入拓展地址 IAP_DATA = dat; // 装载待写入的字节数据 IAP_TRIG = 0x5a; // 送入触发密码 0x5A IAP_TRIG = 0xa5; // 送入触发密码 0xA5 启动硬件写 _nop_(); _nop_(); _nop_(); _nop_(); // 等待写操作完成 IAP_Disable(); // 关闭 IAP 控制器 } /** * @brief 擦除指定的 512 字节 EEPROM 物理扇区 * @param addr 扇区首地址 */ void IAP_EraseSector(u16 addr) { IAP_CONTR = 0x80; // 开启 IAP 功能 IAP_TPS = (u8)(MAIN_Fosc / 1000000UL); // 匹配工作频率的擦除时间周期 IAP_CMD = IAP_CMD_ERASE; // 发送扇区擦除指令 IAP_ADDRL = (u8)addr; // 送入低字节地址 IAP_ADDRH = (u8)(addr >> 8); // 送入高字节地址 IAP_ADDRE = 0; // 拓展地址清零 IAP_TRIG = 0x5a; // 触发密码 0x5A IAP_TRIG = 0xa5; // 触发密码 0xA5 启动硬件擦除 _nop_(); _nop_(); _nop_(); _nop_(); // 等待擦除完成 (扇区全部内容被初始化为 0xFF) IAP_Disable(); // 关闭 IAP 操作以保护数据 } /** * @brief 从 EEPROM 的扇区 254 中加载数据库 * @details 逐字节读取 Flash 物理数据,并直接通过指针拷贝装入 RAM 中的全局 `sensor_list` 数组。 */ void Load_Database(void) { u16 addr = 0; // EEPROM 内部偏移地址从 0 开始 u8 *ptr = (u8 *)&sensor_list; // 获取传感器 RAM 数组的首地址指针 u16 size = sizeof(sensor_list); // 获取结构体数组的全部占用尺寸 u16 i; for (i = 0; i < size; i++) { ptr[i] = IAP_ReadByte(addr++); // 循环读取字节并还原到 RAM } } /** * @brief 将 RAM 中的最新传感器列表数据持久化保存到 EEPROM * @details 因为 Flash 写操作不能把 0 写回 1,所以必须先调用 `IAP_EraseSector` 擦除整个扇区, * 接着遍历整个数据结构,逐字节写入最新的 RAM 内容。 */ void Save_Database(void) { u16 addr = 0; // EEPROM 物理首地址 u8 *ptr = (u8 *)&sensor_list; // RAM 首指针 u16 size = sizeof(sensor_list); // 总字节量 u16 i; IAP_EraseSector(0); // 擦除首扇区 (扇区 254,大小 512 字节足够存放) for (i = 0; i < size; i++) { IAP_WriteByte(addr++, ptr[i]); // 循环写入字节数据 } } /** * @brief 新增或更新已对码的传感器,并保存数据库 * @param addr 24位射频地址 * @param type 传感器类型索引 (0~4) * @param zone 自定义分配的防区号 (1~99, 0) */ void Add_Sensor_With_Zone(u32 addr, u8 type, u8 zone) { u8 i; u8 slot_to_use = 15; // 默认使用最后一槽 // 遍历现有的 16 个槽位,寻找匹配的地址或者空闲的槽位 for (i = 0; i < 16; i++) { if (sensor_list[i].is_used == 0x01) { // 解析该槽位中存储的 24 位传感器地址 u32 existing_addr = ((u32)sensor_list[i].addr[0] << 16) | ((u32)sensor_list[i].addr[1] << 8) | sensor_list[i].addr[2]; // 如果地址已经存在,说明这是要修改现有槽位的防区或类型 if (existing_addr == addr) { slot_to_use = i; // 使用该槽位进行更新 break; } } else { // 如果找到未启用的空闲槽位,暂定使用它 slot_to_use = i; break; } } // 配置传感器槽位数据 sensor_list[slot_to_use].is_used = 0x01; // 标记槽位为已启用状态 sensor_list[slot_to_use].addr[0] = (u8)(addr >> 16); // 存入地址高字节 sensor_list[slot_to_use].addr[1] = (u8)(addr >> 8); // 存入地址中字节 sensor_list[slot_to_use].addr[2] = (u8)addr; // 存入地址低字节 sensor_list[slot_to_use].type = type; // 记录传感器硬件类型 sensor_list[slot_to_use].zone = zone; // 记录对应的防区号 // 生成该槽位传感器的法语名称 for (i = 0; i < 15; i++) { // 从默认名称常量数组中复制对应名字的字符 sensor_list[slot_to_use].name_gbk[i] = sensor_names_fr[type][i]; if (sensor_names_fr[type][i] == '\0') break; // 遇零终止 } sensor_list[slot_to_use].name_gbk[15] = '\0'; // 尾部安全截断,防止字符越界 Save_Database(); // 调用保存函数,强制持久化写回 IAP Flash } /** * @brief 检测输入的 24 位射频地址是否已被当前绑定的任何槽位匹配 * @param addr 传入待匹配的 24 位射频地址 * @param out_slot_index 若匹配成功,输出槽位的索引号指针 * @return bit 1-匹配成功,0-该地址尚未被本系统学习绑定 */ bit Check_Sensor_ID(u32 addr, u8 *out_slot_index) { u8 i; for (i = 0; i < 16; i++) { // 仅比对正在使用中的槽位 if (sensor_list[i].is_used == 0x01) { u32 slot_addr = ((u32)sensor_list[i].addr[0] << 16) | ((u32)sensor_list[i].addr[1] << 8) | sensor_list[i].addr[2]; if (slot_addr == addr) { *out_slot_index = i; // 写入输出变量 return 1; // 匹配成功返回真 } } } return 0; // 遍历结束未找到,返回假 }