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 的基础知识,不懂的小伙伴自行上网查看,简单说明一下注意点:
-
I²C 总线只需要两条线,SCL (时钟线)和 SDA(数据线),数据线需要配置为开漏模式,必须接上拉电阻;
-
速率:标准 100 kHz 快速 400 kHz;
-
数据传输
起始信号:当SCL为高电平时,SDA从高到低跳变表示开始
结束信号:当SCL为高电平时,SDA从低到高跳变表示结束
ACK :第 9 个时钟 SDA 被拉低
NACK :第 9 个时钟 SDA 保持高
-
上拉电阻,一般标准速率模式 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 流程
这里流程我们根据代码来说明,作为应用来说一般正常流程操作,配置正确,都不会有什么问题(实际应用中常见的问题,有一部分是配置问题,一部分是地址问题,还有一部分是速率问题)。
-
配置对应 I2C 引脚为上拉输入:
ex:"
GPIOB_ModeCfg(GPIO_Pin_12 | GPIO_Pin_13, GPIO_ModeIN_PU); -
I2C 初始化,使用
I2C_Init配置模式,速率,地址位等:ex:
I2C_Init(I2C_Mode_I2C, 400000, I2C_DutyCycle_16_9, I2C_Ack_Enable, I2C_AckAddr_7bit, MASTER_ADDR); -
初始化后,每次操作 I2C 前需要等待标志位,不同的操作等待的标志位不一样,这个具体参照示例:
ex:
while(I2C_GetFlagStatus(I2C_FLAG_BUSY) != RESET); -
发送起始信号:
ex:
while(I2C_GetFlagStatus(I2C_FLAG_BUSY) != RESET);等待总线处于空闲状态
I2C_GenerateSTART(ENABLE); -
发送7bit 从机地址设置 I2C 方向,设置为写状态:
ex:
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));等待IIC接口设置为主模式,等待起始信号发送完成
I2C_Send7bitAddress(SLAVE_ADDR, I2C_Direction_Transmitter); -
开始发送数据:
ex:
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
if(I2C_GetFlagStatus(I2C_FLAG_TXE) != RESET)
{
I2C_SendData(SHT40_CMD_HIGH_PRECISION);
} -
如果不需要接收数据,发送停止信号:
ex:
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
I2C_GenerateSTOP(ENABLE); -
如果需要读取从机返回的数据,等待一定时间(这个时间一般是有从机决定,比如传感器手册上面会写发送数据读取命令以后,需要等待多久才能读取数据)。
ex:
DelayMs(20); -
读数据之前又得重新发送起始信号:
ex:
I2C_GenerateSTART(ENABLE); -
发送7bit 从机地址设置 I2C 方向,设置为读状态:
ex:
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(SHT40_I2C_ADDR, I2C_Direction_Receiver); -
开始接收数据:
ex:
while(!I2C_CheckEvent(I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
if(I2C_GetFlagStatus(I2C_FLAG_RXNE) != RESET)
{
RxData[i] = I2C_ReceiveData();
} -
接收完毕发送停止信号:
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 上的代码可以参考我的文章:

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 ,大家可以自己去下载:
或者直接在 CSDN 资源下载也可以,后面等我上传。
结语
本文主要分别介绍了 CH58x 芯片硬件 I2C 和软件 I2C 的使用,给出了两款常用温湿度传感器的使用示例。只要搞清楚流程,所有的 I2C 设备按照规定的协议方式通信,基本都是类似的。
好了,本文就到这里。谢谢大家!