HT16C21 驱动模拟I2C实现

HT16C21 是一款功能不错的LCD驱动控制器,通过I2C接口与微控制器通信,能够驱动最多20×4或16×8点的LCD显示。由于DSP28335可能没有硬件I2C资源,或者硬件I2C配置不够灵活,软件模拟I2C(Software I2C) 就成了一个实用的选择。

HT16C21 驱动模拟I2C实现

软件I2C关键实现要点

软件模拟I2C的核心在于通过GPIO引脚精确模拟I2C协议的时序,包括起始条件、停止条件、数据读写和应答信号。

  1. 引脚定义与初始化:需要定义两个GPIO引脚分别作为SDA(数据线)和SCL(时钟线),并将它们初始化为输出模式,初始状态通常设置为高电平。
  2. 时序模拟 :严格按照I2C协议的时序要求,通过控制GPIO引脚的高低电平时序来产生起始信号、停止信号、数据有效等。需要注意的是,I2C协议规定SCL为高电平时,SDA的数据必须稳定
  3. 数据收发:逐位读写数据,每个时钟脉冲传输一位数据。写数据时,先传输最高位(MSB);读数据时,同样先读取最高位。
  4. 应答处理:每传输完一个字节(8位)后,接收方(无论是主机还是从机)需要发送一个应答信号(ACK)或非应答信号(NACK)。

DSP28335 模拟I2C驱动HT16C21代码

1. 宏定义与引脚控制

首先定义SDA和SCL引脚,以及基本的引脚操作宏:

c 复制代码
// 假设使用GPIO31作为SDA, GPIO32作为SCL
#define I2C_SDA_GPIOPIN   31
#define I2C_SCL_GPIOPIN   32

// 控制SDA和SCL引脚输出高电平
#define I2C_SDA_HIGH      GpioDataRegs.GPASET.bit.GPIO31 = 1
#define I2C_SCL_HIGH      GpioDataRegs.GPASET.bit.GPIO32 = 1

// 控制SDA和SCL引脚输出低电平
#define I2C_SDA_LOW       GpioDataRegs.GPACLEAR.bit.GPIO31 = 1
#define I2C_SCL_LOW       GpioDataRegs.GPACLEAR.bit.GPIO32 = 1

// 设置SDA引脚为输入方向(用于读取)或输出方向(用于写入)
#define I2C_SDA_INPUT     GpioCtrlRegs.GPADIR.bit.GPIO31 = 0
#define I2C_SDA_OUTPUT    GpioCtrlRegs.GPADIR.bit.GPIO31 = 1

// 读取SDA引脚的电平状态
#define I2C_READ_SDA      GpioDataRegs.GPADAT.bit.GPIO31

// 简单的延时函数,需要根据CPU时钟频率调整
void I2C_Delay(void)
{
    Uint16 i;
    for (i = 0; i < 100; i++);
}

2. I2C基本信号模拟

模拟I2C的起始信号、停止信号、应答信号等:

c 复制代码
// I2C起始信号:SCL高电平期间,SDA从高变低
void I2C_Start(void)
{
    I2C_SDA_OUTPUT; // 设置SDA为输出
    I2C_SDA_HIGH;
    I2C_SCL_HIGH;
    I2C_Delay();
    I2C_SDA_LOW;
    I2C_Delay();
    I2C_SCL_LOW; // 保持SCL低电平,准备发送数据
    I2C_Delay();
}

// I2C停止信号:SCL高电平期间,SDA从低变高
void I2C_Stop(void)
{
    I2C_SDA_OUTPUT; // 设置SDA为输出
    I2C_SDA_LOW;
    I2C_Delay();
    I2C_SCL_HIGH;
    I2C_Delay();
    I2C_SDA_HIGH;
    I2C_Delay();
}

// 发送应答信号(ACK):SCL低电平期间,SDA拉低;SCL产生一个脉冲
void I2C_SendAck(void)
{
    I2C_SDA_OUTPUT;
    I2C_SDA_LOW;
    I2C_Delay();
    I2C_SCL_HIGH;
    I2C_Delay();
    I2C_SCL_LOW;
    I2C_Delay();
}

// 发送非应答信号(NACK):SCL低电平期间,SDA拉高;SCL产生一个脉冲
void I2C_SendNack(void)
{
    I2C_SDA_OUTPUT;
    I2C_SDA_HIGH;
    I2C_Delay();
    I2C_SCL_HIGH;
    I2C_Delay();
    I2C_SCL_LOW;
    I2C_Delay();
}

// 检查从机应答信号
// 返回值:0-应答成功,1-无应答
Uint16 I2C_CheckAck(void)
{
    Uint16 ack;

    I2C_SDA_INPUT; // 设置SDA为输入,准备读取从机应答
    I2C_Delay();
    I2C_SCL_HIGH;  // SCL拉高,在此期间读取SDA状态
    I2C_Delay();

    if (I2C_READ_SDA) {
        ack = 1;   // SDA为高,表示无应答(NACK)
    } else {
        ack = 0;   // SDA为低,表示应答(ACK)
    }

    I2C_SCL_LOW;
    I2C_Delay();
    I2C_SDA_OUTPUT; // 将SDA重新设置为输出,以便后续操作

    return ack;
}

3. I2C字节读写函数

实现通过I2C读写一个字节的数据:

c 复制代码
// 通过I2C写入一个字节数据
void I2C_WriteByte(Uint8 data)
{
    Uint8 i;
    I2C_SDA_OUTPUT;

    for (i = 0; i < 8; i++) {
        if (data & 0x80) {   // 先传输最高位(MSB)
            I2C_SDA_HIGH;
        } else {
            I2C_SDA_LOW;
        }
        I2C_Delay();
        I2C_SCL_HIGH; // SCL上升沿,数据有效
        I2C_Delay();
        I2C_SCL_LOW;
        I2C_Delay();
        data <<= 1;   // 左移,准备发送下一位
    }

    // 发送完8位数据后,读取从机应答
    I2C_CheckAck();
}

// 通过I2C读取一个字节数据
Uint8 I2C_ReadByte(void)
{
    Uint8 i, data = 0;
    I2C_SDA_INPUT; // 设置SDA为输入,准备读取数据

    for (i = 0; i < 8; i++) {
        data <<= 1;   // 左移,准备接收下一位
        I2C_Delay();
        I2C_SCL_HIGH; // SCL拉高,在此期间读取SDA状态
        I2C_Delay();
        if (I2C_READ_SDA) {
            data |= 0x01; // SDA为高,该位为1
        }
        I2C_SCL_LOW;
    }

    I2C_SDA_OUTPUT; // 将SDA重新设置为输出,以便后续发送应答

    return data;
}

4. HT16C21初始化与显示控制

HT16C21需要正确的初始化才能正常工作,以下是一个初始化示例:

c 复制代码
// HT16C21初始化
void HT16C21_Init(void)
{
    // 发送起始信号
    I2C_Start();

    // 发送HT16C21的I2C地址(7位地址为0x70,左移一位后写操作为0xE0)
    I2C_WriteByte(0x70 << 1); // 假设最后一位为0,表示写操作

    // 发送配置命令
    // 设置系统振荡器开、显示开
    I2C_WriteByte(0x84); // 系统振荡器开、显示开 的命令字节
    // 具体配置值需参考数据手册,例如0x84可能表示:1000 0100,即开启振荡器和显示

    // 设置显示参数,如占空比(Duty)、偏置(Bias)
    I2C_WriteByte(0x82); // 假设0x82是设置Duty和Bias的命令
    I2C_WriteByte(0x00); // 具体的配置数据,例如1/4 Duty, 1/3 Bias

    // 设置帧频率
    I2C_WriteByte(0x86);
    I2C_WriteByte(0x01); // 例如设置帧频为160Hz

    // 关闭闪烁
    I2C_WriteByte(0x88);
    I2C_WriteByte(0x00); // 关闭闪烁

    // VLCD电压调整设置(如果需要)
    I2C_WriteByte(0x8A);
    I2C_WriteByte(0x10); // 具体的电压调整值,需参考数据手册

    // 发送停止信号
    I2C_Stop();
}

5. 向HT16C21写入显示数据

初始化完成后,可以向其显示RAM写入数据:

c 复制代码
// 向HT16C21的指定地址写入显示数据
void HT16C21_WriteData(Uint8 address, Uint8 data)
{
    // 发送起始信号
    I2C_Start();

    // 发送HT16C21的I2C地址(写操作)
    I2C_WriteByte(0x70 << 1); // 7位器件地址为0x70,左移一位,最低位为0表示写

    // 发送写显示RAM的命令(0x80)和地址
    // HT16C21的显示RAM读写命令通常为0x80,与地址组合
    I2C_WriteByte(0x80 | address); // 组合命令和地址

    // 写入显示数据
    I2C_WriteByte(data);

    // 发送停止信号
    I2C_Stop();
}

使用示例

在主函数中,你可以这样使用:

c 复制代码
void main(void)
{
    // 初始化系统控制、GPIO等
    InitSysCtrl();
    InitGpio(); // 确保初始化了用于I2C的GPIO引脚

    // 初始化软件I2C
    // (GPIO方向设置等已在宏定义中体现,可根据需要额外初始化)

    // 初始化HT16C21
    HT16C21_Init();

    // 向HT16C21的显示RAM地址0x00写入数据0xAB
    HT16C21_WriteData(0x00, 0xAB);

    while(1)
    {
        // 主循环,可以不断更新显示内容
    }
}

参考代码 HTC16C21 驱动模拟I2C www.youwenfan.com/contentcsh/56960.html

重要参考:HT16C21常用命令字节

根据HT16C21的数据手册,以下是一些常用的命令字节概要:

命令字节 (Command Byte) 功能描述 典型数据字节 (Data Byte) 说明
0x82 设置Duty和Bias 0x00 例如:1/4 Duty, 1/3 Bias
0x84 系统振荡器与显示开关控制 0x03 开启系统振荡器和显示
0x86 设置帧频率 (Frame Frequency) 0x01 例如:160Hz
0x88 设置闪烁模式 (Blinking) 0x00 关闭闪烁
0x8A VLCD电压调整 (Voltage Adjust) 0x10 具体的电压调整值,需参考数据手册
0x80 显示RAM读写命令 (结合地址使用) - 与地址组合,如`0x80

希望以上代码和说明能帮助你在DSP28335上成功驱动HT16C21。软件模拟I2C虽然会占用一些CPU时间,但提供了很好的灵活性。调试时最好用示波器或逻辑分析仪观察I2C时序,这能帮你快速定位问题。

相关推荐
自信的小螺丝钉2 小时前
Leetcode 148. 排序链表 归并排序
算法·leetcode·链表·归并
listhi5203 小时前
基于梯度下降、随机梯度下降和牛顿法的逻辑回归MATLAB实现
算法·matlab·逻辑回归
机器视觉知识推荐、就业指导3 小时前
STM32 外设驱动模块:旋转编码器
stm32·单片机·嵌入式硬件
熊猫_豆豆3 小时前
目前顶尖AI所用算法,包含的数学内容,详细列举
人工智能·算法
野犬寒鸦3 小时前
从零起步学习Redis || 第二章:Redis中数据类型的深层剖析讲解(下)
java·redis·后端·算法·哈希算法
java1234_小锋3 小时前
Scikit-learn Python机器学习 - 回归分析算法 - 弹性网络 (Elastic-Net)
python·算法·机器学习
hn小菜鸡4 小时前
LeetCode 2570.合并两个二维数组-求和法
数据结构·算法·leetcode
hn小菜鸡4 小时前
LeetCode 524.通过删除字母匹配到字典里最长单词
算法·leetcode·职场和发展
Greedy Alg4 小时前
LeetCode 226. 翻转二叉树
算法