记录一次IIC从机寄存器的数据结构代码
·4 字·1 分钟阅读
嵌入式开发
iic.h
#ifndef __IIC_H_
#define __IIC_H_
/* ===== 寄存器地址定义 ===== */
#define REG_WATER_SAVE 0x10 /* W: 0x01=保存无水值, 0x02=保存有水值 */
#define REG_WATER_STATUS 0x11 /* R: 水箱及水的状态位 */
#define REG_WATER_BASE_L 0x12 /* R: 基准值低8位 */
#define REG_WATER_BASE_H 0x13 /* R: 基准值高8位 */
#define REG_WATER_REAL_L 0x14 /* R: 实时值低8位 */
#define REG_WATER_REAL_H 0x15 /* R: 实时值高8位 */
#define REG_WATER_TEST 0x16 /* R: 机器识别号, 固定 0x77 */
#define REG_WATER_SAVE_STATE 0x17 /* R: 保存状态, 0=未保存, 1=已保存 */
#define REG_MIST_VER 0x18 /* R: 固件版本号 */
#define REG_ADDR_MIN 0x10
#define REG_ADDR_MAX 0x18
#define REG_INVALID 0xFF
/* ===== 寄存器属性位定义 ===== */
#define REG_R 0x01 /* 可读 */
#define REG_W 0x02 /* 可写 */
/* ===== 协议常量 ===== */
#define IIC_SLAVE_ADDR 0xD0 /* 从机地址 */
#define MIST_VER 0x01 /* 固件版本号 */
#define WATER_TEST_VAL 0x77 /* 机器识别号 */
/* ===== 水位阈值(用户提供)=====
touchDiffer < 4000 → 缺水(Bit0=1)
touchDiffer < 3000 → 无水箱(Bit1=1) */
#define THRESH_NO_WATER 4000
#define THRESH_NO_TANK 3000
void IIC_INIT();
extern unsigned char reg_ptr;
#endif
iic.c
#include "iic.h"
#include "common.h"
#include "main.h" /* touchDiffer 外部声明 */
#include "water_check.h" /* sTK 类型定义 */
/* ===== 保存状态标志(iic.c 内部使用)===== */
static unsigned char save_state = 0; /* 0=未保存, 1=已保存 */
static unsigned char save_no_water_ok = 0; /* WATER_STATUS Bit2 */
static unsigned char save_has_water_ok = 0; /* WATER_STATUS Bit3 */
/* ===== IIC 中断内部状态 ===== */
unsigned char reg_ptr; /* 当前寄存器地址(0x10~0x18) 或 REG_INVALID */
unsigned char byte_cnt; /* 本次传输内字节计数 */
static unsigned char write_data; /* 写入数据暂存(等待校验) */
static unsigned char last_read_data; /* 读出数据缓存(保证与校验和一致) */
/* 基准值定义在 main.c */
extern sTK sTKRef;
/* ===== 寄存器属性表 =====
索引 = 寄存器地址 - 0x10(即 0..8 对应 0x10..0x18)
修改此表即可调整各寄存器的读/写权限,无需改逻辑代码
存放在 code(ROM)区,节省 RAM */
unsigned char code reg_attr[9] = {
/* 0x10 WATER_SAVE */ REG_W, /* 写专用:保存无水/有水值 */
/* 0x11 WATER_STATUS */ REG_R, /* 读专用:水位状态 */
/* 0x12 WATER_BASE_L */ REG_R, /* 读专用:基准值低字节 */
/* 0x13 WATER_BASE_H */ REG_R, /* 读专用:基准值高字节 */
/* 0x14 WATER_REAL_L */ REG_R, /* 读专用:实时值低字节 */
/* 0x15 WATER_REAL_H */ REG_R, /* 读专用:实时值高字节 */
/* 0x16 WATER_TEST */ REG_R, /* 读专用:机器识别号 */
/* 0x17 WATER_SAVE_STATE */ REG_R, /* 读专用:保存状态 */
/* 0x18 MIST_VER */ REG_R, /* 读专用:固件版本 */
};
/**
* @brief 获取指定寄存器的数据字节
* @param addr 寄存器地址 (0x10~0x18)
* @retval 数据字节
*/
static unsigned char get_reg_data(unsigned char addr)
{
switch (addr)
{
case REG_WATER_STATUS:
{
unsigned char st = 0;
if (touchDiffer < THRESH_NO_WATER) st |= 0x01; /* Bit0: 缺水 */
if (touchDiffer < THRESH_NO_TANK) st |= 0x02; /* Bit1: 无水箱 */
if (save_no_water_ok) st |= 0x04; /* Bit2: 保存无水值成功 */
if (save_has_water_ok) st |= 0x08; /* Bit3: 保存有水值成功 */
return st;
}
case REG_WATER_BASE_L:
return (unsigned char)(THRESH_NO_WATER & 0xFF);
case REG_WATER_BASE_H:
return (unsigned char)((THRESH_NO_WATER >> 8) & 0xFF);
case REG_WATER_REAL_L:
return (unsigned char)(touchDiffer & 0xFF);
case REG_WATER_REAL_H:
return (unsigned char)((touchDiffer >> 8) & 0xFF);
case REG_WATER_TEST:
return WATER_TEST_VAL;
case REG_WATER_SAVE_STATE:
return save_state;
case REG_MIST_VER:
return MIST_VER;
default:
return 0xFF;
}
}
/**
* @brief 计算校验和
* @note checksum = (从机地址 + 寄存器地址 + 数据) & 0xFF
*/
static unsigned char calc_checksum(unsigned char addr, unsigned char data1)
{
return (unsigned char)((IIC_SLAVE_ADDR + addr + data1) & 0xFF);
}
/**
* @brief 判断寄存器是否可读(查 reg_attr 属性表)
*/
static unsigned char is_readable(unsigned char addr)
{
if (addr < REG_ADDR_MIN || addr > REG_ADDR_MAX)
return 0;
return (reg_attr[addr - REG_ADDR_MIN] & REG_R) != 0;
}
/**
* @brief 判断寄存器是否可写(查 reg_attr 属性表)
*/
static unsigned char is_writable(unsigned char addr)
{
if (addr < REG_ADDR_MIN || addr > REG_ADDR_MAX)
return 0;
return (reg_attr[addr - REG_ADDR_MIN] & REG_W) != 0;
}
/**
* @brief IIC 中断服务函数
* 处理外部主机对本芯片寄存器的读/写访问
*
* 协议(SH8912检水板 IIC 从设备通信协议 20260903):
* 从机地址: 0xD0, 时钟: 50KHz
* 每次读/写均含 2 字节 [data][checksum]
* checksum = (0xD0 + reg_addr + data) & 0xFF
*
* 写时序:[START][slave+W][reg_addr][data][checksum] [STOP]
* 仅 0x10 可写:0x01=保存无水值, 0x02=保存有水值
*
* 读时序:[START][slave+W][reg_addr] [RESTART][slave+R][data][checksum] [STOP]
* 0x11~0x18 可读,0x10 不可读(返回 0xFF)
*
* 中断 3 分支(独立 if,非 else if):
* 分支1: 地址阶段(Bit1=0) → 复位 byte_cnt
* 分支2: 数据写入(Bit1=1,Bit0=0) → 处理 reg_addr / 写数据校验
* + early preload: 读寄存器在此预装 IICTX0(字节1=数据)
* 分支3: 读出(Bit0=1) → 预装 IICTX0(字节2=校验和)
* 此 MCU 无 IIC 发送中断,slave+R 是读阶段唯一中断:
* 硬件在中断前已加载移位寄存器(字节1),中断中预装 IICTX0(字节2)
*
* @param None
* @retval None
*/
void UART_RX() interrupt 6
{
if (!IICIF)
{
return;
}
/* 分支1:地址阶段(slave+W 或 slave+R 匹配,Bit1=0)→ 复位 byte_cnt */
if ((IICFLAG & 0x02) == 0x00)
{
byte_cnt = 0;
}
/* 分支2:数据写入(Bit1=1 数据,Bit0=0 写) */
if ((IICFLAG & 0x03) == 0x02)
{
if (byte_cnt == 0)
{
/* 第一字节 = 寄存器地址 */
unsigned char addr = IICRX0;
if (addr >= REG_ADDR_MIN && addr <= REG_ADDR_MAX)
{
reg_ptr = addr;
/* ponytail: 读寄存器在此预装 IICTX0(字节1=数据),比 slave+R
中断早得多。此 MCU 无 IIC 发送中断,slave+R 是读阶段唯一中断,
硬件在 slave+R 中断前已将 IICTX0 加载到移位寄存器,
所以 early preload 的值就是主机读到的字节1 */
if (is_readable(addr))
{
last_read_data = get_reg_data(addr);
IICTX0 = last_read_data;
}
else
{
IICTX0 = 0xFF; /* 0x10 写专用,不可读 */
}
}
else
{
reg_ptr = REG_INVALID; /* 地址超出范围 */
IICTX0 = 0xFF;
}
}
else if (is_writable(reg_ptr))
{
/* 写操作:[data][checksum] */
if (byte_cnt == 1)
{
write_data = IICRX0; /* 暂存数据,等待校验 */
}
else if (byte_cnt == 2)
{
unsigned char cs = IICRX0;
/* 校验通过才执行保存操作 */
if (cs == calc_checksum(reg_ptr, write_data))
{
if (write_data == 0x01)
{
save_no_water_ok = 1; /* 保存无水值成功 */
save_state = 1;
}
else if (write_data == 0x02)
{
save_has_water_ok = 1; /* 保存有水值成功 */
save_state = 1;
}
}
}
/* byte_cnt >= 3 的多余字节忽略 */
}
byte_cnt++;
}
/* 分支3:读出(Bit0=1,覆盖 slave+R 匹配与后续读字节)
ponytail: 此 MCU 无 IIC 发送中断,slave+R 地址匹配是读阶段唯一中断。
字节1(数据)已由分支2的 early preload 预装,硬件在中断触发前
已将 IICTX0 加载到移位寄存器。此处预装字节2(校验和),
硬件读完字节1后从 IICTX0 重新加载移位寄存器发送字节2 */
if ((IICFLAG & 0x01) == 0x01)
{
if (is_readable(reg_ptr))
{
/* 预装第二字节 = 校验和(使用 early preload 缓存的 last_read_data) */
IICTX0 = calc_checksum(reg_ptr, last_read_data);
}
else
{
/* 不可读 / 未定义 / 0x10 写专用 */
IICTX0 = 0xFF;
}
byte_cnt++;
}
IICIF = 0; /* 清除中断标志位 */
}
/**
* @brief IIC 相关初始化配置
* @param None
* @retval None
*/
void IIC_INIT()
{
/* 寄存器内部状态复位 */
reg_ptr = REG_INVALID; /* 无效寄存器指针 */
byte_cnt = 0;
IICC0 = Bin(11000000); // IIC模式,正常uart模式,偶校验,8位模式,1位stop,接收发送使能
// Bit7:通讯模式选择位
// 0 = UART模式
// 1 = IIC模式
// Bit6:IIC使能控制位
// 1:使能
// 0:禁止
// Bit5 - Bit3:保留
// Bit2:IIC ACK发送
// 1:NACK
// 0:ACK
// Bit1 - Bit0:保留
IICFLAG = Bin(11000000); // 时钟SCLK
// Bit7~Bit6:IIC时钟选择
// 00 = SCLK
// 01 = HRC/2
// 10 = LRC
// 11 = SCLK
// Bit5~Bit4:保留
// Bit3:IIC停止位标志位
// 1:检测到停止位
// 0:无停止位
// Bit2:IIC起始位标志位
// 1:检测到起始位
// 0:无起始位
// Bit1:数据地址标志位
// 1:数据
// 0:地址
// Bit0:读写标志位
// 1:读从机
// 0:写从机
IICADDR = (0xD0 >> 1);
// Bit7 ADRMD ADR地址模式
// 1:仅需匹配高三位地址即地址命中
// 0:7位地址完全匹配
// Bit6~Bit0:IICADDR IIC从机地址
IICFLT = 20;
// Bit7~Bit4:保留
// Bit3~Bit0:IICFLT 滤波时间SCLK* (IICFLT+1)
// IICRX0
UCIE = Bin(00000001); // IIC接收中断使能
// Bit1:UART发送中断使能
// 0 = 禁止
// 1 = 使能
// Bit0:IIC接收中断使能
// 0 = 禁止
// 1 = 使能
E6IE = 1; // IIC中断使能
}