目录
一 背景说明
使用小华(华大)的MCU HC32F07X实现使用I2C外设读取LM75温度传感器数据。
二 原理分析
I2C的实际使用需要了解三个部分,I2C协议原理、从机设备(LM75传感器)的使用说明、主机设备(HX32F07X的I2C外设)。
【1】I2C协议原理:
关于 I2C协议,这边推荐B站博主 工科男孙老师 的几段视频,深入浅出讲解得非常详细:
- UART那么好用,为什么单片机还需要I2C和SPI?_哔哩哔哩_bilibili
- 单片机I2C通信入门(上):硬件部分有哪些注意点?_哔哩哔哩_bilibili
- 单片机I2C通信入门(下):三份文件搞清楚I2C通信协议_哔哩哔哩_bilibili
I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。
主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。
SDA(串行数据线)和SCL(串行时钟线)都是双向I/O线,接口电路为开漏输出。需通过上拉电阻接电源VCC。当总线空闲时,两根线都是高电平,连接总线的外同器件都是CMOS器件,输出级也是开漏电路。在总线上消耗的电流很小,因此,总线上扩展的器件数量主要由电容负载来决定,因为每个器件的总线接口都有一定的等效电容。而线路中电容会影响总线传输速度。当电容过大时,有可能造成传输错误。所以,其负载能力为400pF,因此可以估算出总线允许长度和所接器件数量。
主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件。在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件。然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下。主机负责产生定时时钟和终止数据传送。
其读写时序如下,注意几点:
(1) I2C是通过 从机地址(7位) + R/W读写标志(1位) 来进行读写操作区分的;
(2)等待应答ASK的时间不能忘;
【2】LM75说明书(LM75BDP-HXY -PDF数据手册-参考资料-立创商城):
(1)产品概述如下:
(2)典型电路如下,注意上拉10K电阻(因总线上串接的I2C设备数量来做取值,过大可能会导致信号失真,过小可能会浪费能量或者无法将总线电压拉低到低电平,甚至可能会烧毁电路):
(3)如果A0/A1/A2均接地的话,其逻辑地址为0x48:
(3)只需要读取温度寄存器的内容,所以从机内存地址为0x00:
(4)采样温度与实际温度的对应关系如下:
【3】HC32F07X的I2C外设说明书(HC32F072PATA-LQFP100 -PDF数据手册-参考资料-立创商城)
(1)I2C总线概述:
(2)状态码表述:
这边特别注意HC32F07X上提供了一个状态寄存器 I2Cx_STAT ,可以实时查询该寄存器的值,以便知晓当前I2C总线通讯处于一个什么状态:
三 代码实现
根据上面三方面的内容,可以写代码:
【1】官方提供的驱动库 HC32F072_DDL_Rev1.2.0 ,调用里面的 i2c.c / i2c.h 驱动支持:
i2c.c如下:
/******************************************************************************
* Copyright (C) 2021, Xiaohua Semiconductor Co., Ltd. All rights reserved.
*
* This software component is licensed by XHSC under BSD 3-Clause license
* (the "License"); You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************/
/******************************************************************************
* @file i2c.c
*
* @brief Source file for I2C functions
*
* @author MADS Team
*
******************************************************************************/
/******************************************************************************/
/* Include files */
/******************************************************************************/
#include "i2c.h"
/**
*******************************************************************************
** \addtogroup I2cGroup
******************************************************************************/
//@{
/******************************************************************************/
/* Local function prototypes ('static') */
/******************************************************************************/
/**
******************************************************************************
** \brief I2C设置波特率配置寄存器
**
** \param [in] u8Tm 波特率配置值
**
** \retval enRet 成功或失败
**
******************************************************************************/
en_result_t I2C_SetBaud(M0P_I2C_TypeDef* I2Cx, uint8_t u8Tm)
{
en_result_t enRet = Error;
I2Cx->TM = u8Tm;
enRet = Ok;
return enRet;
}
/**
******************************************************************************
** \brief I2C功能设置相关函数
**
** \param [in] enFunc功能参数
**
** \retval enRet 成功或失败
**
******************************************************************************/
en_result_t I2C_SetFunc(M0P_I2C_TypeDef* I2Cx, en_i2c_func_t enFunc)
{
en_result_t enRet = Error;
SetBit((uint32_t)&I2Cx->CR, enFunc, TRUE);
enRet = Ok;
return enRet;
}
/**
******************************************************************************
** \brief I2C功能清除相关函数
**
** \param [in] enFunc功能参数
**
** \retval enRet 成功或失败
**
******************************************************************************/
en_result_t I2C_ClearFunc(M0P_I2C_TypeDef* I2Cx, en_i2c_func_t enFunc)
{
en_result_t enRet = Error;
SetBit((uint32_t)&I2Cx->CR, enFunc, FALSE);
enRet = Ok;
return enRet;
}
/**
******************************************************************************
** \brief I2C获取中断标记函数
**
** \param 无
**
** \retval bIrq中断标记
**
******************************************************************************/
boolean_t I2C_GetIrq(M0P_I2C_TypeDef* I2Cx)
{
if(I2Cx->CR&0x8)
{
return TRUE;
}
else
{
return FALSE;
}
}
/**
******************************************************************************
** \brief I2C清除中断标记函数
**
** \param 无
**
** \retval bIrq中断标记
**
******************************************************************************/
en_result_t I2C_ClearIrq(M0P_I2C_TypeDef* I2Cx)
{
en_result_t enRet = Error;
I2Cx->CR &= ~0x8u;
enRet = Ok;
return enRet;
}
/**
******************************************************************************
** \brief I2C获取相关状态
**
** \param 无
**
** \retval I2C状态
**
******************************************************************************/
uint8_t I2C_GetState(M0P_I2C_TypeDef* I2Cx)
{
uint8_t u8State = 0;
u8State = I2Cx->STAT;
return u8State;
}
/**
******************************************************************************
** \brief 字节数据写函数
**
** \param u8Data写数据
**
** \retval 写数据是否成功
**
******************************************************************************/
en_result_t I2C_WriteByte(M0P_I2C_TypeDef* I2Cx, uint8_t u8Data)
{
en_result_t enRet = Error;
I2Cx->DATA = u8Data;
enRet = Ok;
return enRet;
}
/**
******************************************************************************
** \brief 字节数据读函数
**
** \param 无
**
** \retval 读取数据
**
******************************************************************************/
uint8_t I2C_ReadByte(M0P_I2C_TypeDef* I2Cx)
{
uint8_t u8Data = 0;
u8Data = I2Cx->DATA;
return u8Data;
}
/**
******************************************************************************
** \brief I2C模块初始化
**
** \param pstcI2CCfg初始化配置结构体
**
** \retval 初始化是否成功
**
******************************************************************************/
en_result_t I2C_Init(M0P_I2C_TypeDef* I2Cx, stc_i2c_cfg_t *pstcI2CCfg)
{
en_result_t enRet = Error;
uint8_t u8Tm;
if(M0P_I2C0 == I2Cx)
{
M0P_RESET->PERI_RESET0 &= ~(uint32_t)0x10u;
M0P_RESET->PERI_RESET0 |= (uint32_t)0x10u;
}
else
{
M0P_RESET->PERI_RESET0 &= ~(uint32_t)0x20u;
M0P_RESET->PERI_RESET0 |= (uint32_t)0x20u;
}
I2Cx->CR = 0;
I2Cx->CR = pstcI2CCfg->enMode;
if((pstcI2CCfg->u32Baud<<4) > pstcI2CCfg->u32Pclk)
{
return Error;
}
if(I2cMasterMode == pstcI2CCfg->enMode)
{
I2Cx->TMRUN = TRUE;
///< Fsck = Fpclk/8*(Tm+1)
u8Tm = ((pstcI2CCfg->u32Pclk / pstcI2CCfg->u32Baud) >> 3) - 1;
if(9 > u8Tm)
{
I2C_SetFunc(I2Cx,I2cHlm_En);
}
enRet = I2C_SetBaud(I2Cx, u8Tm);
}
else
{
I2Cx->TMRUN = FALSE;
pstcI2CCfg->u8SlaveAddr = (uint8_t)(((uint32_t)pstcI2CCfg->u8SlaveAddr<<1)|(pstcI2CCfg->bGc));
I2Cx->ADDR = pstcI2CCfg->u8SlaveAddr;
}
return enRet;
}
//@} // I2cGroup
i2c.h如下:
/******************************************************************************
* Copyright (C) 2021, Xiaohua Semiconductor Co., Ltd. All rights reserved.
*
* This software component is licensed by XHSC under BSD 3-Clause license
* (the "License"); You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************/
/******************************************************************************
* @file i2c.h
*
* @brief Header file for I2C functions
*
* @author MADS Team
*
******************************************************************************/
#ifndef __I2C_H__
#define __I2C_H__
#include "ddl.h"
/**
*******************************************************************************
** \defgroup I2cGroup Inter-Integrated Circuit (I2C)
**
**
******************************************************************************/
//@{
/******************************************************************************/
/* Global pre-processor symbols/macros ('#define') */
/******************************************************************************/
/******************************************************************************
* Global type definitions
******************************************************************************/
/**
******************************************************************************
** \brief I2C功能配置
*****************************************************************************/
typedef enum en_i2c_func
{
I2cModule_En = 6u, ///<I2C模块使能
I2cStart_En = 5u, ///<开始信号
I2cStop_En = 4u, ///<结束信号
I2cAck_En = 2u, ///<应答信号
I2cHlm_En = 0u, ///<高速使能
}en_i2c_func_t;
/**
******************************************************************************
** \brief I2C模式配置
*****************************************************************************/
typedef enum en_i2c_mode
{
I2cMasterMode = 0x40u, ///<I2C主机模式
I2cSlaveMode = 0x44u, ///<I2C从机模式
}en_i2c_mode_t;
/**
******************************************************************************
** \brief I2C初始化配置结构
*****************************************************************************/
typedef struct stc_i2c_cfg
{
uint32_t u32Pclk; ///<Pclk 设置(Hz)
uint32_t u32Baud; ///<I2C通信波特率(Hz)
en_i2c_mode_t enMode; ///<I2C主从模式配置
uint8_t u8SlaveAddr; ///<从机地址配置(如果需要)
boolean_t bGc; ///<广播地址使能(如果需要)
}stc_i2c_cfg_t;
/******************************************************************************
* Global variable declarations ('extern', definition in C source)
*****************************************************************************/
/******************************************************************************
* Global function prototypes (definition in C source)
*****************************************************************************/
//I2C初始化函数
en_result_t I2C_Init(M0P_I2C_TypeDef* I2Cx,stc_i2c_cfg_t *pstcI2CCfg);
//设置波特率配置寄存器
en_result_t I2C_SetBaud(M0P_I2C_TypeDef* I2Cx,uint8_t u8Tm);
//I2C功能设置函数
en_result_t I2C_SetFunc(M0P_I2C_TypeDef* I2Cx,en_i2c_func_t enFunc);
//I2C功能清除函数
en_result_t I2C_ClearFunc(M0P_I2C_TypeDef* I2Cx,en_i2c_func_t enFunc);
//获取中断标记SI
boolean_t I2C_GetIrq(M0P_I2C_TypeDef* I2Cx);
//清除中断标记SI
en_result_t I2C_ClearIrq(M0P_I2C_TypeDef* I2Cx);
//获取状态
uint8_t I2C_GetState(M0P_I2C_TypeDef* I2Cx);
//字节写函数
en_result_t I2C_WriteByte(M0P_I2C_TypeDef* I2Cx,uint8_t u8Data);
//字节读函数
uint8_t I2C_ReadByte(M0P_I2C_TypeDef* I2Cx);
//@} // I2cGroup
#ifdef __cplusplus
#endif
#endif /* __I2C_H__ */
/******************************************************************************
* EOF (not truncated)
*****************************************************************************/
【2】GPIO以及I2C外设初始化:
//LM75温度传感器
#define I2C1_LM75_SCL_PORT GpioPortB
#define I2C1_LM75_SCL_PIN GpioPin13
#define I2C1_LM75_SDA_PORT GpioPortB
#define I2C1_LM75_SDA_PIN GpioPin14
#define LM75_ADDRESS 0x48 // LM75的I2C地址
#define LM75_REG_ADDRESS 0x00 // LM75的读取寄存器地址
/**************************************************************************
* 函数名称: I2C_Tmp_Init
* 功能描述: I2C温度传感器初始化
**************************************************************************/
void I2C_Tmp_Init(void)
{
stc_gpio_cfg_t stcGpioCfg;
DDL_ZERO_STRUCT(stcGpioCfg); ///< 初始化结构体变量的值为0
Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE); ///< 开启GPIO时钟门控
stcGpioCfg.enDir = GpioDirOut; ///< 端口方向配置->输出
stcGpioCfg.enOD = GpioOdEnable; ///< 端口开漏输出配置->开漏输出使能
stcGpioCfg.enPu = GpioPuEnable; ///< 端口上拉配置->使能
stcGpioCfg.enPd = GpioPdDisable; ///< 端口下拉配置->禁止
stcGpioCfg.bOutputVal = TRUE; ///< 默认高电平
Gpio_Init(I2C1_LM75_SCL_PORT, I2C1_LM75_SCL_PIN, &stcGpioCfg); ///< 初始化SCL
Gpio_Init(I2C1_LM75_SDA_PORT, I2C1_LM75_SDA_PIN, &stcGpioCfg); ///< 初始化SDA
Gpio_SetAfMode(I2C1_LM75_SCL_PORT, I2C1_LM75_SCL_PIN, GpioAf2); ///< 配置SCL
Gpio_SetAfMode(I2C1_LM75_SDA_PORT, I2C1_LM75_SDA_PIN, GpioAf2); ///< 配置SDA
}
/**************************************************************************
* 函数名称: I2C_Tmp_Conf
* 功能描述: I2C温度传感器配置
**************************************************************************/
void I2C_Tmp_Conf(void)
{
stc_i2c_cfg_t stcI2cCfg;
DDL_ZERO_STRUCT(stcI2cCfg); ///< 初始化结构体变量的值为0
Sysctrl_SetPeripheralGate(SysctrlPeripheralI2c1,TRUE); ///< 开启I2C1时钟门控
stcI2cCfg.u32Pclk = Sysctrl_GetPClkFreq(); ///< 获取PCLK时钟
stcI2cCfg.u32Baud = 100000; ///< 波特率100kHz
stcI2cCfg.enMode = I2cMasterMode; ///< I2C主机模式
stcI2cCfg.u8SlaveAddr = LM75_ADDRESS; ///< 从地址,主模式无效
stcI2cCfg.bGc = FALSE; ///< 广播地址应答使能关闭,主模式无效
I2C_Init(M0P_I2C1, &stcI2cCfg); ///< 模块初始化
}
【3】传感器值I2C读取与转换:
/**************************************************************************
* 函数名称: LM75_ReadTemperature
* 功能描述: I2C温度传感器值读取
**************************************************************************/
en_result_t LM75_ReadTemperature(M0P_I2C_TypeDef* I2Cx, uint8_t u8Addr,uint8_t *pu8Data,uint32_t u32Len)
{
en_result_t enRet = Error;
uint8_t u8i=0,u8State;
I2C_SetFunc(I2Cx,I2cStart_En);
while(1)
{
while(0 == I2C_GetIrq(I2Cx))
{}
u8State = I2C_GetState(I2Cx);
switch(u8State)
{
case 0x08: ///< 已发送起始条件,将发送SLA+W
I2C_ClearFunc(I2Cx,I2cStart_En);
I2C_WriteByte(I2Cx,(LM75_ADDRESS << 1));
break;
case 0x18: ///< 已发送SLA+W,并接收到ACK
I2C_WriteByte(I2Cx,u8Addr); ///< 发送从机内存地址
break;
case 0x28: ///< 已发送数据,接收到ACK, 此处是已发送从机内存地址u8Addr并接收到ACK
I2C_SetFunc(I2Cx,I2cStart_En); ///< 发送重复起始条件
break;
case 0x10: ///< 已发送重复起始条件
I2C_ClearFunc(I2Cx,I2cStart_En);
I2C_WriteByte(I2Cx,(LM75_ADDRESS << 1)|0x01);///< 发送SLA+R,开始从从机读取数据
break;
case 0x40: ///< 已发送SLA+R,并接收到ACK
if(u32Len>1)
{
I2C_SetFunc(I2Cx,I2cAck_En); ///< 使能主机应答功能
}
break;
case 0x50: ///< 已接收数据字节,并已返回ACK信号
pu8Data[u8i++] = I2C_ReadByte(I2Cx);
if(u8i==u32Len-1)
{
I2C_ClearFunc(I2Cx,I2cAck_En); ///< 已接收到倒数第二个字节,关闭ACK应答功能
}
break;
case 0x58: ///< 已接收到最后一个数据,NACK已返回
pu8Data[u8i++] = I2C_ReadByte(I2Cx);
I2C_SetFunc(I2Cx,I2cStop_En); ///< 发送停止条件
break;
case 0x38: ///< 在发送地址或数据时,仲裁丢失
I2C_SetFunc(I2Cx,I2cStart_En); ///< 当总线空闲时发起起始条件
break;
case 0x48: ///< 发送SLA+R后,收到一个NACK
I2C_SetFunc(I2Cx,I2cStop_En); ///< 发送停止条件
I2C_SetFunc(I2Cx,I2cStart_En); ///< 发送起始条件
break;
default:
I2C_SetFunc(I2Cx,I2cStart_En); ///< 其他错误状态,重新发送起始条件
break;
}
I2C_ClearIrq(I2Cx); ///< 清除中断状态标志位
if(u8i==u32Len) ///< 数据全部读取完成,跳出while循环
{
break;
}
}
enRet = Ok;
return enRet;
}
/**************************************************************************
* 函数名称: LM75_ConvertToCelsius
* 功能描述: I2C温度传感器值转换
**************************************************************************/
float LM75_ConvertToCelsius(uint16_t rawData)
{
float temperature;
rawData >>= 7; // 取高 9 位
if (rawData & 0x100) // 判断是否为负数
{
rawData |= 0xFE00; // 符号扩展
}
temperature = (int16_t)rawData * 0.5f; // 转换为实际温度
return temperature;
}
【4】再附一个主机写入的例程(本例中没有用到):
/**
******************************************************************************
** \brief 主机发送函数
**
** \param u8Addr从机内存地址,pu8Data写数据,u32Len写数据长度
**
** \retval 写数据是否成功
**
******************************************************************************/
en_result_t I2C_MasterWriteData(M0P_I2C_TypeDef* I2CX,uint8_t u8Addr,uint8_t *pu8Data,uint32_t u32Len)
{
en_result_t enRet = Error;
uint8_t u8i=0,u8State;
I2C_SetFunc(I2CX,I2cStart_En);
while(1)
{
while(0 == I2C_GetIrq(I2CX))
{;}
u8State = I2C_GetState(I2CX);
switch(u8State)
{
case 0x08: ///< 已发送起始条件
I2C_ClearFunc(I2CX,I2cStart_En);
I2C_WriteByte(I2CX,I2C_SLAVEADDR); ///< 从设备地址发送
break;
case 0x18: ///< 已发送SLA+W,并接收到ACK
I2C_WriteByte(I2CX,u8Addr); ///< 从设备内存地址发送
break;
case 0x28: ///< 上一次发送数据后接收到ACK
I2C_WriteByte(I2CX,pu8Data[u8i++]); ///< 继续发送数据
break;
case 0x20: ///< 上一次发送SLA+W后,收到NACK
case 0x38: ///< 上一次在SLA+读或写时丢失仲裁
I2C_SetFunc(I2CX,I2cStart_En); ///< 当I2C总线空闲时发送起始条件
break;
case 0x30: ///< 已发送I2Cx_DATA中的数据,收到NACK,将传输一个STOP条件
I2C_SetFunc(I2CX,I2cStop_En); ///< 发送停止条件
break;
default:
break;
}
if(u8i>u32Len)
{
I2C_SetFunc(I2CX,I2cStop_En); ///< 此顺序不能调换,出停止条件
I2C_ClearIrq(I2CX);
break;
}
I2C_ClearIrq(I2CX); ///< 清除中断状态标志位
}
enRet = Ok;
return enRet;
}
【5】将其放到 FreeRTOS的子任务中实现:
/**************************************************************************
* 函数名称: TMP_Task
* 功能描述: 状态子任务
**************************************************************************/
uint8_t temp_test[2]={0x00};
float temperature;
void TMP_Task(void* param)
{
//按钮初始化
IN_Key_Init();
//蜂鸣器初始化
OUT_Beep_Init();
//I2C温度传感器初始化
I2C_Tmp_Init();
I2C_Tmp_Conf();
while(1)
{
if(IN_KEY_GET())
{
//检测按钮未按下打印任务栈
UBaseType_t uxUsedStackSize = TMP_TASK_SIZE - uxTaskGetStackHighWaterMark(NULL);
UBaseType_t uxUsagePercentage = (uxUsedStackSize * 100) / TMP_TASK_SIZE;
Dbg_Printf("TMP_Task-->Stack Used: %u/%u (%u%%)\r\n",
(unsigned int)uxUsedStackSize,
(unsigned int)TMP_TASK_SIZE,
(unsigned int)uxUsagePercentage);
Gpio_ClrIO(OUT_BEEP_PORT, OUT_BEEP_PIN); //禁止蜂鸣器
}
else
{
//检测按钮按下打印温度数据
LM75_ReadTemperature(M0P_I2C1, LM75_REG_ADDRESS, temp_test, 2);
temperature = LM75_ConvertToCelsius((temp_test[0] << 8) | temp_test[1]);
Dbg_Printf("Temperature: %.1f C\r\n", temperature);
Gpio_SetIO(OUT_BEEP_PORT, OUT_BEEP_PIN); //使能蜂鸣器
}
vTaskDelay(1000);
}
}
四 测试效果
测试能够通过LM75正常读取数据: