CH58x 芯片 I2C 使用(SHT20/SHT40示例)

复制代码
CH58x 芯片 I2C 的使用说明     ...... 矜辰所致

前言

抽空写一篇基础文章,说明一下 CH58x 硬件 I2C,软件I2C 的使用,同时给出常用温湿度传感器 SHT20/SHT40 的示例代码。

简而言之本文就是讲一下 CH58x 芯片 I2C 接口的使用。

我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!

目录

  • 前言
  • [一、 I2C 协议基础](#一、 I2C 协议基础)
  • [二、硬件 I2C](#二、硬件 I2C)
    • [2.1 硬件 I2C 流程](#2.1 硬件 I2C 流程)
    • [2.2 地址说明](#2.2 地址说明)
    • [2.3 硬件 I2C 示例](#2.3 硬件 I2C 示例)
      • [2.3.1 SHT40 Demo](#2.3.1 SHT40 Demo)
      • [2.3.2 SHT20 Demo](#2.3.2 SHT20 Demo)
  • [三、软件 I2C](#三、软件 I2C)
    • [2.1 软件 I2C 实现](#2.1 软件 I2C 实现)
    • [2.2 软件 I2C 示例](#2.2 软件 I2C 示例)
  • 四、示例代码下载
  • 结语

一、 I2C 协议基础

I2C 的基础知识,不懂的小伙伴自行上网查看,简单说明一下注意点:

  1. I²C 总线只需要两条线,SCL (时钟线)和 SDA(数据线),数据线需要配置为开漏模式,必须接上拉电阻;

  2. 速率:标准 100 kHz 快速 400 kHz;

  3. 数据传输

    起始信号:当SCL为高电平时,SDA从高到低跳变表示开始

    结束信号:当SCL为高电平时,SDA从低到高跳变表示结束

    ACK :第 9 个时钟 SDA 被拉低

    NACK :第 9 个时钟 SDA 保持高

  4. 上拉电阻,一般标准速率模式 3.3V 选 4.7K 左右,10K内都是可以的,速度越高电阻要相对选小一些。

对于 CH585 来说,支持的功能可以直接查看手册:

包括 I2C 的使用方式流程,芯片手册里面也都写了,比如基础的时序图 (具体资料大家自行查看手册):

需要说明的一点就是对于CH58x 系列芯片来说,它不支持手动设置开漏输出,我们需要把它们设置为上拉输入,芯片使用 I2C 的时候会自动开漏输出,但是它的内部上拉是弱上拉,外面还是需要正常接上拉电阻的!

本文我们讨论常用的模式:芯片做主机模式 ,读取温湿度传感器的数据。至于从机模式的话,官方示例起始也实现了,大家需要可以自己测试。

二、硬件 I2C

官方提供的 I2C 例程为硬件 I2C ,我们本文主要看一下主机模式,在示例上需要把主机宏定义打开,从机宏定义注销:

c 复制代码
#define I2C_MODE      IIC_HOST_MODE
// #define I2C_MODE      IIC_SLAVE_MODE

CH585 上默认 I2C 的引脚为:

PB12 SDA

PB13 SCL

可以重映射为:

PB20 SDA

PB21 SCL

2.1 硬件 I2C 流程

这里流程我们根据代码来说明,作为应用来说一般正常流程操作,配置正确,都不会有什么问题(实际应用中常见的问题,有一部分是配置问题,一部分是地址问题,还有一部分是速率问题)。

  1. 配置对应 I2C 引脚为上拉输入:

    ex:"GPIOB_ModeCfg(GPIO_Pin_12 | GPIO_Pin_13, GPIO_ModeIN_PU);

  2. I2C 初始化,使用 I2C_Init 配置模式,速率,地址位等:

    ex:I2C_Init(I2C_Mode_I2C, 400000, I2C_DutyCycle_16_9, I2C_Ack_Enable, I2C_AckAddr_7bit, MASTER_ADDR);

  3. 初始化后,每次操作 I2C 前需要等待标志位,不同的操作等待的标志位不一样,这个具体参照示例:

    ex:while(I2C_GetFlagStatus(I2C_FLAG_BUSY) != RESET);

  4. 发送起始信号:

    ex:
    while(I2C_GetFlagStatus(I2C_FLAG_BUSY) != RESET); 等待总线处于空闲状态
    I2C_GenerateSTART(ENABLE);

  5. 发送7bit 从机地址设置 I2C 方向,设置为写状态:

    ex:
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));等待IIC接口设置为主模式,等待起始信号发送完成
    I2C_Send7bitAddress(SLAVE_ADDR, I2C_Direction_Transmitter);

  6. 开始发送数据:

    ex:
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
    if(I2C_GetFlagStatus(I2C_FLAG_TXE) != RESET)
    {
    I2C_SendData(SHT40_CMD_HIGH_PRECISION);
    }

  7. 如果不需要接收数据,发送停止信号:

    ex:
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    I2C_GenerateSTOP(ENABLE);

  8. 如果需要读取从机返回的数据,等待一定时间(这个时间一般是有从机决定,比如传感器手册上面会写发送数据读取命令以后,需要等待多久才能读取数据)。

    ex:DelayMs(20);

  9. 读数据之前又得重新发送起始信号:

    ex:I2C_GenerateSTART(ENABLE);

  10. 发送7bit 从机地址设置 I2C 方向,设置为读状态:

    ex:
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
    I2C_Send7bitAddress(SHT40_I2C_ADDR, I2C_Direction_Receiver);

  11. 开始接收数据:

    ex:
    while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
    if(I2C_GetFlagStatus(I2C_FLAG_RXNE) != RESET)
    {
    RxData[i] = I2C_ReceiveData();
    }

  12. 接收完毕发送停止信号:

    ex: I2C_GenerateSTOP(ENABLE);

2.2 地址说明

有个关键点需要特殊说明一下:

就是函数 I2C_Send7bitAddress 传入的设备地址的问题,需要自己左移一位。

比如,对于 SHT40 ,手册上它的地址为 0x44:

我们使用硬件 I2C 的函数的时候,需要如下定义:

c 复制代码
#define SHT40_I2C_ADDR      0x44<<1 
I2C_Send7bitAddress(SHT40_I2C_ADDR, I2C_Direction_Transmitter);

这是使用库函数需要注意的一个点,在官方示例中,有应用层自己封装的 i2c_write_to i2c_read_from 函数,里面的地址需要注意一下,这是官方写的应用层的示例,我们自己写的时候可以参考,也可以直接使用库函数,但是记住使用库函数时传入的地址需要左移一位 。

2.3 硬件 I2C 示例

基本说明介绍完毕,下面就直接上一下传感器示例:

2.3.1 SHT40 Demo

SHT40 的代码很简单,传感器手册里面也写了,以前在 STM32 上的代码可以参考我的文章:

KEIL中编译51程序 算法计算异常的疑问

I2C 初始化代码:

c 复制代码
void my_iic_init(uint8_t num){
  if(1== num){
      GPIOB_ModeCfg(GPIO_Pin_12 | GPIO_Pin_13, GPIO_ModeIN_PU);
  }
  else if(2 == num){
      GPIOPinRemap(ENABLE, RB_PIN_I2C);
      GPIOB_ModeCfg(GPIO_Pin_21 | GPIO_Pin_20, GPIO_ModeIN_PU);
  }
  I2C_Init(I2C_Mode_I2C, 100000, I2C_DutyCycle_16_9, I2C_Ack_Enable, I2C_AckAddr_7bit,0);
}

主函数:

c 复制代码
...
PRINT("test begin!\r\n");
my_iic_init(1);
while(1)
 {
     DelayMs(1000);
     sht40_read();
 }

SHT40传感器读取函数:

c 复制代码
void sht40_read()
{
    PRINT("this is  sht40 test\n");
    uint8_t userRegister;

    while(I2C_GetFlagStatus(I2C_FLAG_BUSY) != RESET);

    I2C_GenerateSTART(ENABLE);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(SHT40_I2C_ADDR, I2C_Direction_Transmitter);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    if(I2C_GetFlagStatus(I2C_FLAG_TXE) != RESET)
    {
        I2C_SendData(SHT40_CMD_HIGH_PRECISION);
    }

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
    //需不需要 STOP
    DelayMs(20);

    uint16_t tem, hum;
    uint8_t checksum;

    I2C_GenerateSTART(ENABLE);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(SHT40_I2C_ADDR, I2C_Direction_Receiver);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

    uint8_t RxData[6],i=0;

    while(i < 6)
    {
        if(I2C_GetFlagStatus(I2C_FLAG_RXNE) != RESET)
        {
            RxData[i] = I2C_ReceiveData();
            i++;
            if(i==5)
            {
//                I2C_AcknowledgeConfig(DISABLE);
                I2C_GenerateSTOP(ENABLE);
            }
        }

    }

    tem = ((RxData[0]<<8)|RxData[1]);
    hum = ((RxData[3]<<8)|RxData[4]);

    float temperature = tem * (175.0 / 65536.0);
    temperature = temperature - 45.0;

    float humidity = hum * (125.0 / 65536.0);
    humidity = humidity - 6.0;

    printf("sht40 humi:%.2f %%\r\n",humidity);
    printf("sht40 tem:%.2f ℃\r\n",temperature);

}

测试结果:

2.3.2 SHT20 Demo

SHT20 的示例如下,资料自行网上查看,这里也上一下代码(SHT 20 的代码是以前学习的时候网上找的):

主函数:

c 复制代码
...
PRINT("test begin!\r\n");
my_iic_init(1);
prepare_sht20(USER_REGISTER_HEATER_ENABLED);
while(1)
 {
     DelayMs(1000);
     sht20_read();
 }

SHT20传感器读取函数:

c 复制代码
void prepare_sht20(uint8_t val)
{

    while(I2C_GetFlagStatus(I2C_FLAG_BUSY) != RESET);

    I2C_GenerateSTART(ENABLE);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(SHT20_I2C_ADDR, I2C_Direction_Transmitter);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    if(I2C_GetFlagStatus(I2C_FLAG_TXE) != RESET)
    {
        I2C_SendData(WRITE_USER_REG);
    }

    if(I2C_GetFlagStatus(I2C_FLAG_TXE) != RESET)
    {
        I2C_SendData(val);
    }

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    I2C_GenerateSTOP(ENABLE);

}

/*
 * SHT20_I2C_ADDR 0x40 需要左移一位 变成 0x80 放入函数
 */
uint16_t readValue(uint8_t cmd)
{
    uint8_t userRegister;

    while(I2C_GetFlagStatus(I2C_FLAG_BUSY) != RESET);

    I2C_GenerateSTART(ENABLE);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(SHT20_I2C_ADDR, I2C_Direction_Transmitter);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

    if(I2C_GetFlagStatus(I2C_FLAG_TXE) != RESET)
    {
        I2C_SendData(cmd);
    }

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));

    uint8_t msb, lsb, checksum;

    I2C_GenerateSTART(ENABLE);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));

    I2C_Send7bitAddress(SHT20_I2C_ADDR, I2C_Direction_Receiver);

    while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));

    uint8_t RxData[3],i=0;

    while(i < 3)
    {
        if(I2C_GetFlagStatus(I2C_FLAG_RXNE) != RESET)
        {
            RxData[i] = I2C_ReceiveData();
            i++;
            if(i==2)
            {
//                I2C_AcknowledgeConfig(DISABLE);
                I2C_GenerateSTOP(ENABLE);
            }
        }

    }

    msb = RxData[0];
    lsb = RxData[1];
    checksum = RxData[2];
    uint16_t rawValue = ((uint16_t) msb << 8) | (uint16_t) lsb;
    if(checkCRC(rawValue, checksum) != 0){
        return (ERROR_BAD_CRC);
    }
    return rawValue & 0xFFFC;
}

float readHumidity(void)
{
    uint16_t rawHumidity = readValue(TRIGGER_HUMD_MEASURE_HOLD);
    if(rawHumidity == ERROR_I2C_TIMEOUT || rawHumidity == ERROR_BAD_CRC){
        return(rawHumidity);
    }
    float tempRH = rawHumidity * (125.0 / 65536.0);
    float rh = tempRH - 6.0;
    return (rh);
}

float readTemperature(void)
{
    uint16_t rawTemperature = readValue(TRIGGER_TEMP_MEASURE_HOLD);
    if(rawTemperature == ERROR_I2C_TIMEOUT || rawTemperature == ERROR_BAD_CRC){
        return(rawTemperature);
    }
    float tempTemperature = rawTemperature * (175.72 / 65536.0);
    float realTemperature = tempTemperature - 46.85;
    return (realTemperature);
}

void sht20_read(){

    float temperature = readTemperature();
    float humidity = readHumidity();

    printf("sht20 humi:%.2f %%\r\n",humidity);
    printf("sht20 tem:%.2f ℃\r\n",temperature);
}

测试结果:

三、软件 I2C

软件 I2C 这个以前使用 STM32 的时候是一直使用的,比如正点原子软件 I2C 的驱动原理就可以用,以前我也有博文说明过:STM32L051测试 (三、I2C协议设备的添加测试)

只要理解 I2C 协议基础知识,软件 I2C 相对来说比硬件 I2C 流程看起来更加直观,完全按照 I2C 协议的通信流程人为的控制进度,软件 I2C 不限制固定的 IO 口,可以使用任意的两个 IO 口实现,而且可以跨平台实现,以前 STM32 ,51单片机上面的驱动直接改一下引脚配置、匹配一下延时函数就可以使用了。

这个具体的基础知识网上太多了,不懂得小伙伴自己补充一下,这里我们测试一下软件 I2C 的效果。

2.1 软件 I2C 实现

软件实现代码可参考博文: https://www.cnblogs.com/risc5-ble/p/18663782

上面博文实现的是 SHT20/21 的驱动代码,博主后面上的代码也是用此驱动测试过 SHT40 的。

只是上面博文中的接收函数,没有回 ACK 和 NACK 部分,测试修改如下:

c 复制代码
/* ack=1 发 ACK,ack=0 发 NACK */
uint8_t IIC_ReadByte(uint8_t ack)
{
    uint8_t i = 8;
    uint8_t ReceiveByte = 0;
    IIC_SDA_H();
    while (i--)
    {
        ReceiveByte <<= 1;
        IIC_SCL_L();
        IIC_Delay();
        IIC_SCL_H();
        IIC_Delay();
        if (SDA_read())
            ReceiveByte |= 0x01;
    }
    IIC_SCL_L();        // 先拉低 SCL
    if (ack) IIC_SDA_L(); else IIC_SDA_H();
    IIC_Delay();
    IIC_SCL_H();        // 拉高 SCL,从设备采样 ACK/NACK
    IIC_Delay();
    IIC_SCL_L();        // 结束
    // IIC_SDA_H();
    return ReceiveByte;
}

其他部分参考原文,当然博主下面也把本文所有示例代码都打包,大家可以下载。

2.2 软件 I2C 示例

大家可自行下载代码查看,代码下载链接在下面。

四、示例代码下载

本问测试的示例工程如下:

示例中有2个宏定义:

大家下载后是需要放到 CH585 EVT 原 I2C 程序目录下面去测试的,因为我没有把此工程独立出来,需要使用 EVT 里面的库文件。

我把代码放到 Gitee ,大家可以自己去下载:

矜辰所致的 Gitee 仓库

或者直接在 CSDN 资源下载也可以,后面等我上传。

结语

本文主要分别介绍了 CH58x 芯片硬件 I2C 和软件 I2C 的使用,给出了两款常用温湿度传感器的使用示例。只要搞清楚流程,所有的 I2C 设备按照规定的协议方式通信,基本都是类似的。

好了,本文就到这里。谢谢大家!

相关推荐
wxmtwfx11 天前
Linux内核时钟芯片DS3232驱动源码分析
linux·驱动开发·spi·i2c·ds3232
wotaifuzao14 天前
I2C通信--深度解析与未来发展
单片机·嵌入式硬件·物联网·信息与通信·i2c
来鸟 鸣间16 天前
i2c_add_driver关键流程
linux·i2c
Terasic友晶科技24 天前
4-DE10-Nano的HDMI方块移动案例——I2C通信协议
fpga开发·i2c·hdmi·de10-nano·i2c通信协议
xiaohai@Linux1 个月前
ESP32 IDF v5.3.1 驱动 CST816T 触摸芯片(I2C 协议)
单片机·嵌入式硬件·触摸·i2c·cst816t
一个平凡而乐于分享的小比特1 个月前
I2C、SPI、CAN、串口通信详细对比
can·uart·spi·i2c
一个平凡而乐于分享的小比特1 个月前
I²C时钟拉伸与总线仲裁机制详解
i2c·时钟拉伸·总线仲裁
一个平凡而乐于分享的小比特1 个月前
I²C通信协议详解
通信协议·i2c
SEP50101 个月前
STM32 Bit-Bang I2C
stm32·i2c·bit-bang