记录一次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中断使能
}