【实测分享】STM32驱动BH1750:软件I2C通信、光学窗口补偿(附开源代码)
摘要
BH1750FVI 是 ROHM 公司推出的一款数字环境光传感器,通过 I²C 接口直接输出 16 位光照强度数值(单位:lux)。与传统光敏电阻或模拟传感器不同,它内置 ADC 和逻辑电路,无需外接元件即可实现近似人眼的光谱响应,测量范围覆盖 1~65535 lux,广泛应用于手机背光调节、智能家居照明控制及户外亮度监测等场景。
本文将首先介绍如何使用 BH1750 。
开源链接
下面是我的代码在 gitee 和 github 的开源链接。
| 项 | 参数 |
|---|---|
| github主页 | https://github.com/snqx-lqh |
| github项目地址 | https://github.com/snqx-lqh/DeviceDrivers |
| gitee项目地址 | https://gitee.com/snqx-lqh/DeviceDrivers |
| 作者 VX | Qinghua_Li7 |
在我的这个项目中,找到 Example 目录下的 02_BH1750 。需要注意的是,复制工程记得还要复制源码。不要光把工程复制出去了。
BH1750 工作流程
BH1750 的工作流程如下图。图中有两种线。一种实线,一种虚线。实线代表上一步执行完成后会自动跳转到下一步,虚线代表需要使用 I2C 发送指令才会跳转到下一步。

- 上电后。芯片处于 Power Down(断电) 模式。这时候芯片并未进入工作模式。
- 下一步我们需要发送指令让芯片处于 Power On 模式(工作模式)。
- 接下来发送 Measurement Command (测量指令)。让芯片进入 One Time Measurement(单次测量状态)或者 Continuous Measurement(连续测量状态)
- 读取测量结果并且转换成光照值。
- 单次测量模式,在测量完成后,又会自动回到 Power Down (断电)的模式。需要重新开启工作模式。但是连续的模式不会。
I2C流程
BH1750的I2C处理,比较简易。他没有寄存器这个概念,就是发送地址后直接发指令或者读数据。该芯片支持两种地址。
ADDR 接 GND: 0100011(0x23)
ADDR 接 VCC: 1011100(0x5c)
数据发送
数据发送(指令发送)I2C处理:

可以看到,流程就是
发送起始信号->发送(地址7位+1位读写位(0代表写))->等待ACK->发送数据(这里一般就是填相关指令)->等待ACK->发送停止信号。
数据接收
数据接收I2C处理:

可以看到,流程就是
发送起始信号->发送(地址7位+1位读写位(1代表读))->等待ACK->读高8位->发送ACK响应->读低8位->发送NACK响应->发送停止信号。
指令功能
该芯片各个操作的具体指令如下,后面用到再介绍。
| 指令 | 操作码 | 说明 |
|---|---|---|
| 断电 | 0000_0000 | 无活动状态。 |
| 通电 | 0000_0001 | 等待测量命令。 |
| 复位 | 0000_0111 | 复位数据寄存器值。复位命令在断电模式下不接受。 |
| 连续 H-Resolution 模式 | 0001_0000 | 以 1 lx 分辨率开始测量。测量时间通常为 120 ms。 |
| 连续 H-Resolution Mode2 | 0001_0001 | 以 0.5 lx 分辨率开始测量。测量时间通常为 120 ms。 |
| 连续 L-Resolution 模式 | 0001_0011 | 以 4 lx 分辨率开始测量。测量时间通常为 16 ms。 |
| 单次 H-Resolution 模式 | 0010_0000 | 以 1 lx 分辨率开始测量。测量时间通常为 120 ms。测量后自动设置为断电模式。 |
| 单次 H-Resolution Mode2 | 0010_0001 | 以 0.5 lx 分辨率开始测量。测量时间通常为 120 ms。测量后自动设置为断电模式。 |
| 单次 L-Resolution 模式 | 0010_0011 | 以 4 lx 分辨率开始测量。测量时间通常为 16 ms。测量后自动设置为断电模式。 |
| 更改测量时间(高位) | 01000_MT[7,6,5] | 更改测量时间。※请参阅"针对光学窗口影响调整测量结果"。 |
| 更改测量时间(低位) | 011_MT[4,3,2,1,0] | 更改测量时间。※请参阅"针对光学窗口影响调整测量结果"。 |
基础代码设计
首先,使用的前提是我们已经有了 I2C 传输数据的函数,这部分不做详细说明。我直接放 API。具体的代码我再结尾贴上,也可以去我的开源链接中查看。
uint8_t soft_i2c_write(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,const uint8_t *buf,uint8_t len);
uint8_t soft_i2c_read(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,uint8_t *buf,uint8_t len);
在.h文件中做基础定义:
#ifndef _BH_1750_H
#define _BH_1750_H
#include <stdint.h>
#include <stdint.h>
// I2C 地址(根据 ADDR 引脚电平)
#define BH1750_ADDR_L 0x23
#define BH1750_WRITE_ADDR_L 0x23
#define BH1750_WRITE_ADDR_H 0x5C
// 命令定义(数据手册第5页)
#define BH1750_POWER_DOWN 0x00
#define BH1750_POWER_ON 0x01
#define BH1750_RESET 0x07 // 仅复位数据寄存器,不是阈值
#define BH1750_CONT_H_RES 0x10 // 连续 H 分辨率(1 lx)
#define BH1750_CONT_H_RES2 0x11 // 连续 H 分辨率模式2(0.5 lx)
#define BH1750_CONT_L_RES 0x13 // 连续 L 分辨率(4 lx,16ms)
#define BH1750_ONE_TIME_H 0x20 // 单次 H 分辨率(测量后自动断电)
#define BH1750_ONE_TIME_H2 0x21 // 单次 H 分辨率模式2
#define BH1750_ONE_TIME_L 0x23 // 单次 L 分辨率
// MTreg 命令前缀(数据手册第11页)
#define BH1750_MT_H 0x40 // 01000_MT[7,6,5]
#define BH1750_MT_L 0x60 // 011_MT[4,3,2,1,0]
/* API */
uint8_t bh1750_init(uint8_t addr, uint8_t mode);
int bh1750_read_lux(uint8_t addr, uint8_t mode, float *lux);
int bh1750_read_lux_single(uint8_t addr,uint8_t mode, float *lux);
uint8_t bh1750_set_mtreg(uint8_t addr, uint8_t mt_val);
int bh1750_read_lux_ex(uint8_t addr, uint8_t mode, float *lux, uint8_t mtreg);
int bh1750_read_lux_single_ex(uint8_t addr, uint8_t mode, float *lux, uint8_t mtreg);
#endif
我把这个函数封装成了BH1750专门使用的函数。
/* 静态函数:发送命令 */
static uint8_t bh1750_send_cmd(uint8_t addr, uint8_t cmd)
{
// 注意:确保 addr 是写地址(7位地址左移1位,最低位为0)
// 我的 写函数中自动处理了 7位地址左移 所以这里直接把 7位地址传进来就行了。
return soft_i2c_write(SOFT_I2C1, addr, &cmd, 1);
}
/* 静态函数:读取原始数据 */
static uint8_t bh1750_read_raw(uint8_t addr, uint8_t raw_data[2])
{
return soft_i2c_read(SOFT_I2C1, addr, raw_data, 2);
}
然后是初始化。初始化我们包含启动芯片,设置测量模式。我的代码多加了一个复位寄存器的指令。理论上这个初始化最好只用于连续测量,单次测量也可以。但是建议单次测量单开函数,然后每次都走完整流程。不然会很容易忘了重新进入工作模式。
uint8_t bh1750_init(uint8_t addr, uint8_t mode)
{
uint8_t ret;
// 1. Power On(要先让设备进入工作模式,不然后续处理无效)
ret = bh1750_send_cmd(addr, BH1750_POWER_ON);
if (ret) return ret;
// 2. Reset(清零数据寄存器,用于清除之前的测量结果)
ret = bh1750_send_cmd(addr, BH1750_RESET);
if (ret) return ret;
// 3. 设置测量模式
ret = bh1750_send_cmd(addr, mode);
if (ret) return ret;
// 4. 等待第一次测量完成(最大180ms)
// 如果是单次模式,不需要在这里等,而是在 read 前等
if ((mode == BH1750_CONT_H_RES) || (mode == BH1750_CONT_H_RES2)) {
delay_ms(180); // 等待首次测量完成
} else if (mode == BH1750_CONT_L_RES) {
delay_ms(24); // L分辨率最大24ms
}
return 0;
}
连续工作模式,初始化后,就可以读取照度了。主要操作就是读出数据值后,做一个简单的解算。转换公式:lux = raw / 1.2。记得如果是0.5lx分辨率的那两个,计算的时候还要再除以2。
int bh1750_read_lux(uint8_t addr, uint8_t mode, float *lux)
{
uint8_t ret;
uint8_t raw_data[2];
uint16_t raw;
ret = bh1750_read_raw(addr, raw_data);
if (ret) return -1;
raw = (raw_data[0] << 8) | raw_data[1];
// 使用 float 防止整数溢出
*lux = (float)raw / 1.2f;
// 0.5lx分辨率 需要除以2
if(mode == BH1750_CONT_H_RES2 || mode == BH1750_ONE_TIME_H2){
*lux = *lux/2;
}
return 0;
}
关于单次测量,建议单开函数,设定完整流程,比较适合低功耗场景。
int bh1750_read_lux_single(uint8_t addr,uint8_t mode, float *lux)
{
uint8_t ret;
// 1. Power On
ret = bh1750_send_cmd(addr, BH1750_POWER_ON);
if (ret) return -1;
// 2. 发送单次测量命令
ret = bh1750_send_cmd(addr, mode);
if (ret) return -1;
// 3. 等待测量完成(最大180ms)
delay_ms(180);
// 4. 读取数据(传感器会自动进入 Power Down)
return bh1750_read_lux(addr, mode, lux);
}
然后就可以做两个示例了,关于连续读取和单次读取。连续读取,打开一次工作模式就可以一直读取了,但是单次模式每次都要重新打开工作模式。
void exp_continuous_measure()
{
bh1750_init(BH1750_WRITE_ADDR_L,BH1750_CONT_H_RES);
while(1)
{
if (bh1750_read_lux(BH1750_WRITE_ADDR_L, BH1750_CONT_H_RES, &lux) == 0)
{
printf("continuous Light = %.2f lux\r\n", lux);
}
/* 高分辨率模式下,每次测量间隔至少 120ms */
delay_ms(1000); // 请替换为您的延时函数
}
}
void exp_single_measure()
{
while(1)
{
if (bh1750_read_lux_single(BH1750_WRITE_ADDR_L, BH1750_ONE_TIME_H, &lux) == 0)
{
printf("single Light = %.2f lux\r\n", lux);
}
/* 高分辨率模式下,每次测量间隔至少 120ms */
delay_ms(1000); // 请替换为您的延时函数
}
}
可能的BUG
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| I2C 无 ACK | ADDR 引脚悬空/接错 | 接 GND(0x23)或 VCC(0x5C) |
| I2C 无 ACK | I2C 写地址,7位地址和8位地址 | 看看API函数需要你传入的是7位地址还是把7位地址左移一位并带上读写位的8位地址 |
| 数据始终为 0 | 没等 180ms 首次转换 | delay_ms(180) 后再读取 |
传感器灵敏度调整
这个功能一般用在传感器放在有遮挡的环境下。一般课设可能用不上。但是可以了解。
BH1750FVI 可以改变传感器灵敏度。通过使用此功能,可以消除光学窗口的影响(有/无光学窗口的差异)。通过更改测量时间进行调整。例如,当光学窗口的透过率为 50%(如果设置光学窗口,测量结果变为 0.5 倍)时,通过将传感器灵敏度从默认值更改为 2 倍,可以忽略光学窗口的影响。
通过更改 MTreg(测量时间寄存器)的值来偏移传感器灵敏度。如果目标传感器灵敏度为 2 倍,则 MTreg 值必须设置为 2 倍。当 MTreg 值从默认值更改为 2 倍时,测量时间也设置为 2 倍。
想象你把 BH1750 装在一个带玻璃罩的盒子里面测光线。玻璃会挡住一半的光(透过率 50%),传感器就以为环境很暗,读数只有实际值的一半。这时候你需要告诉传感器:"光线被玻璃挡住了一半,你要变得更敏感一些,把数值乘回去"。
更改测量灵敏度代码如下。可以看上面的指令表最后两项。可以发现,这两个指令,前面都会有一点固定值,然后设定值高3位放在一个指令中,低5位放在另一个指令中。这个窗口值的默认值是69。这个设置后,每次的测量时间延时也得修改。比如默认 69 延时 120ms 就能得到结果,那改到 69*2 就得需要 240ms。
uint8_t bh1750_set_mtreg(uint8_t addr, uint8_t mt_val)
{
uint8_t ret;
uint8_t high = BH1750_MT_H | ((mt_val >> 5) & 0x07); // MT[7:5]
uint8_t low = BH1750_MT_L | (mt_val & 0x1F); // MT[4:0]
ret = bh1750_send_cmd(addr, high);
if (ret) return ret;
ret = bh1750_send_cmd(addr, low);
return ret;
}
读取值得代码如下。主要是做了个测量比。
int bh1750_read_lux_ex(uint8_t addr, uint8_t mode, float *lux, uint8_t mtreg)
{
uint8_t ret;
uint8_t raw_data[2];
uint16_t raw;
ret = bh1750_read_raw(addr, raw_data);
if (ret) return -1;
raw = (raw_data[0] << 8) | raw_data[1];
// 根据 MTreg 计算实际照度(数据手册第11页公式)
// 默认 mtreg=69 时,系数为 1/1.2 ≈ 0.833
// 使用 float 防止整数溢出
*lux = (float)raw / 1.2f * (69.0f / (float)mtreg);
if(mode == BH1750_CONT_H_RES2 || mode == BH1750_ONE_TIME_H2){
*lux = *lux/2;
}
return 0;
}
int bh1750_read_lux_single_ex(uint8_t addr, uint8_t mode, float *lux, uint8_t mtreg)
{
uint8_t ret;
// 1. Power On
ret = bh1750_send_cmd(addr, BH1750_POWER_ON);
if (ret) return -1;
// 2. 发送单次测量命令
ret = bh1750_send_cmd(addr, mode);
if (ret) return -1;
// 3. 等待测量完成(最大180ms)
delay_ms(180);
// Step 4: 读取数据(传感器会自动进入 Power Down)
return bh1750_read_lux_ex(addr, mode, lux, mtreg);
}
附录 soft_i2c实现
这里放上我的 i2c 实现。
bsp_soft_i2c.c
/**
******************************************************************************
* @file bsp_soft_i2c.c
* @author liqinghua <liqinghuaxx@163.com>
* @version V1.0.0
* @date 2026-01-29
* @brief 软件I2C驱动模块
*
* @copyright (c) 2026 liqinghua
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @details
* 本模块实现基于GPIO的软件I2C通信,支持多个I2C总线。
* 支持的功能:
* - I2C初始化
* - I2C总线控制(起始、停止、应答等)
* - 单字节读写
* - 多字节读写
*
* @warning 引脚初始化时必须使用开漏输出模式(GPIO_Mode_Out_OD)
*/
#include "bsp_soft_i2c.h"
/****************** user port area start ****************/
#include "bsp_delay.h"
#include "main.h"
/**
* @brief I2C延时函数(微秒级)
* @note 延时时间用于控制I2C总线的时钟频率
* @param 无
* @retval 无
*/
void soft_i2c_delay_us()
{
delay_us(4);
}
/**
* @brief I2C模块初始化
* @note 注意:引脚初始化一定要是开漏模式(GPIO_Mode_Out_OD)
* SCL: PB6, SDA: PB7
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @retval 无
*/
void soft_i2c_init(SOFT_I2C_TypeDef soft_i2c)
{
if(soft_i2c == SOFT_I2C1){
GPIO_InitTypeDef GPIO_InitStructure;
//开启端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//配置SCL、SDA引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //开漏输出
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6);
GPIO_SetBits(GPIOB,GPIO_Pin_7); //所有设备空闲,总线拉高电平
}else if(soft_i2c == SOFT_I2C2){
}
}
/**
* @brief 设置SCL引脚电平
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @param level: 目标电平
* @arg 0: 低电平
* @arg 1: 高电平
* @retval 无
*/
void soft_i2c_set_scl_level(SOFT_I2C_TypeDef soft_i2c,uint8_t level)
{
if(soft_i2c == SOFT_I2C1){
if(level == 0) GPIO_ResetBits(GPIOB,GPIO_Pin_6);
else GPIO_SetBits(GPIOB,GPIO_Pin_6);
}else if(soft_i2c == SOFT_I2C2){}
}
/**
* @brief 设置SDA引脚电平
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @param level: 目标电平
* @arg 0: 低电平
* @arg 1: 高电平
* @retval 无
*/
void soft_i2c_set_sda_level(SOFT_I2C_TypeDef soft_i2c,uint8_t level)
{
if(soft_i2c == SOFT_I2C1){
if(level == 0) GPIO_ResetBits(GPIOB,GPIO_Pin_7);
else GPIO_SetBits(GPIOB,GPIO_Pin_7);
}else if(soft_i2c == SOFT_I2C2){}
}
/**
* @brief 读取SDA引脚电平
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @retval SDA引脚电平值 (0: 低电平, 1: 高电平)
*/
uint8_t soft_i2c_get_sda_level(SOFT_I2C_TypeDef soft_i2c)
{
uint8_t level;
if(soft_i2c == SOFT_I2C1){
level = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7);
}else if(soft_i2c == SOFT_I2C2){}
return level;
}
/****************** user port area end ****************/
/**
* @brief I2C产生起始信号(START)
* @details 起始条件:SCL高时,SDA从高变低
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @retval 无
*/
void soft_i2c_start(SOFT_I2C_TypeDef soft_i2c)
{
soft_i2c_set_sda_level(soft_i2c,1);
soft_i2c_set_scl_level(soft_i2c,1);
soft_i2c_delay_us();
soft_i2c_set_sda_level(soft_i2c,0);
soft_i2c_delay_us();
soft_i2c_set_scl_level(soft_i2c,0);
}
/**
* @brief I2C产生停止信号(STOP)
* @details 停止条件:SCL高时,SDA从低变高
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @retval 无
*/
void soft_i2c_stop(SOFT_I2C_TypeDef soft_i2c)
{
soft_i2c_set_scl_level(soft_i2c,0);
soft_i2c_set_sda_level(soft_i2c,0);
soft_i2c_delay_us();
soft_i2c_set_scl_level(soft_i2c,1);
soft_i2c_set_sda_level(soft_i2c,1);
soft_i2c_delay_us();
}
/**
* @brief 等待I2C应答信号到来
* @details 在第9个时钟周期时,从机拉低SDA表示应答
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @retval 0: 接收应答成功
* @retval 1: 接收应答失败(超时)
*/
uint8_t soft_i2c_wait_ack(SOFT_I2C_TypeDef soft_i2c)
{
uint8_t ucErrTime=0;
soft_i2c_set_sda_level(soft_i2c,1);
soft_i2c_delay_us();
soft_i2c_set_scl_level(soft_i2c,1);
soft_i2c_delay_us();
while(soft_i2c_get_sda_level(soft_i2c))
{
ucErrTime++;
if(ucErrTime>250)
{
soft_i2c_stop(soft_i2c);
return 1;
}
}
soft_i2c_set_scl_level(soft_i2c,0);
return 0;
}
/**
* @brief 主机产生I2C应答信号(ACK)
* @details 拉低SDA,在SCL高期间保持
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @retval 无
*/
void soft_i2c_ack(SOFT_I2C_TypeDef soft_i2c)
{
soft_i2c_set_sda_level(soft_i2c, 0); // 拉低 SDA(ACK 信号)
soft_i2c_delay_us();
soft_i2c_set_scl_level(soft_i2c, 1); // 拉高 SCL(从机读取 ACK)
soft_i2c_delay_us();
soft_i2c_set_scl_level(soft_i2c, 0); // 拉低 SCL(结束第 9 个时钟周期)
// 【关键】释放 SDA,设为高电平(或输入模式),让从机可以发送下一个字节
soft_i2c_set_sda_level(soft_i2c, 1);
soft_i2c_delay_us();
}
/**
* @brief 主机产生I2C非应答信号(NACK)
* @details 保持SDA高电平,在SCL高期间释放
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @retval 无
*/
void soft_i2c_nack(SOFT_I2C_TypeDef soft_i2c)
{
soft_i2c_set_sda_level(soft_i2c, 1); // 保持 SDA 高电平(NACK)
soft_i2c_delay_us();
soft_i2c_set_scl_level(soft_i2c, 1); // 拉高 SCL
soft_i2c_delay_us();
soft_i2c_set_scl_level(soft_i2c, 0); // 拉低 SCL
// NACK 后通常跟着 STOP,所以这里可以不操作 SDA,由 stop 函数处理
}
/**
* @brief I2C发送一个字节数据
* @details 高位先发(MSB)
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @param txd: 待发送字节数据
* @retval 无
*/
void soft_i2c_send_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t txd)
{
uint8_t i;
soft_i2c_set_scl_level(soft_i2c,0);
for (i = 0; i < 8; i++)
{
if((txd&0x80)>>7)
soft_i2c_set_sda_level(soft_i2c,1);
else
soft_i2c_set_sda_level(soft_i2c,0);
txd<<=1;
soft_i2c_set_scl_level(soft_i2c,1);
soft_i2c_delay_us();
soft_i2c_set_scl_level(soft_i2c,0);
soft_i2c_delay_us();
}
}
/**
* @brief I2C读取一个字节数据
* @details 高位先读(MSB)
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @param ack: 读取后是否产生应答
* @arg 1: 产生应答(继续接收)
* @arg 0: 不产生应答(结束接收)
* @retval 读取的字节数据
*/
uint8_t soft_i2c_read_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t ack)
{
uint8_t i,receive = 0;
for (i = 0; i < 8; i++)
{
soft_i2c_set_scl_level(soft_i2c,0);
soft_i2c_delay_us();
soft_i2c_set_scl_level(soft_i2c,1);
receive <<= 1;
if (soft_i2c_get_sda_level(soft_i2c))
{
receive++;
}
soft_i2c_delay_us();
}
soft_i2c_set_scl_level(soft_i2c, 0);
soft_i2c_delay_us();
if (!ack)
soft_i2c_nack(soft_i2c); //非应答
else
soft_i2c_ack(soft_i2c); //应答
return receive;
}
/**
* @brief 从指定I2C设备的寄存器读取一个字节
* @details I2C通信流程:START -> 发送从机地址(写) -> 发送寄存器地址 ->
* 重复START -> 发送从机地址(读) -> 读取数据 -> NACK -> STOP
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @param addr: I2C设备地址(7位,不包含读写位)
* @param reg: 设备寄存器地址
* @param data: 指向数据保存缓冲区的指针
* @retval 0: 读取成功
* @retval 1: 读取失败
*/
uint8_t soft_i2c_read_dev_one_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,uint8_t reg,uint8_t *data)
{
soft_i2c_start(soft_i2c);
soft_i2c_send_byte(soft_i2c,(addr<<1)|0);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
soft_i2c_send_byte(soft_i2c,reg);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
soft_i2c_start(soft_i2c);
soft_i2c_send_byte(soft_i2c,(addr<<1)|1);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
*data = soft_i2c_read_byte(soft_i2c,0);
soft_i2c_stop(soft_i2c);
return 0;
}
/**
* @brief 向指定I2C设备的寄存器写入一个字节
* @details I2C通信流程:START -> 发送从机地址(写) -> 发送寄存器地址 ->
* 发送数据字节 -> STOP
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @param addr: I2C设备地址(7位,不包含读写位)
* @param reg: 设备寄存器地址
* @param data: 待写入的字节数据
* @retval 0: 写入成功
* @retval 1: 写入失败
*/
uint8_t soft_i2c_write_dev_one_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,uint8_t reg,uint8_t data)
{
soft_i2c_start(soft_i2c);
soft_i2c_send_byte(soft_i2c,(addr<<1)|0);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
soft_i2c_send_byte(soft_i2c,reg);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
soft_i2c_send_byte(soft_i2c,data);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
soft_i2c_stop(soft_i2c);
return 0;
}
/**
* @brief 从指定I2C设备的寄存器读取多个字节
* @details I2C通信流程:START -> 发送从机地址(写) -> 发送寄存器地址 ->
* 重复START -> 发送从机地址(读) -> 连续读取len个字节 ->
* 最后一个字节后NACK -> STOP
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @param addr: I2C设备地址(7位,不包含读写位)
* @param reg: 设备寄存器地址
* @param len: 读取字节数
* @param buf: 指向数据保存缓冲区的指针
* @retval 0: 读取成功
* @retval 1: 读取失败
*/
uint8_t soft_i2c_read_dev_len_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,uint8_t reg,uint8_t len,uint8_t *buf)
{
soft_i2c_start(soft_i2c);
soft_i2c_send_byte(soft_i2c,(addr<<1)|0);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
soft_i2c_send_byte(soft_i2c,reg);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
soft_i2c_start(soft_i2c);
soft_i2c_send_byte(soft_i2c,(addr<<1)|1);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
while(len)
{
if(len==1)*buf=soft_i2c_read_byte(soft_i2c,0);
else *buf=soft_i2c_read_byte(soft_i2c,1);
len--;
buf++;
}
soft_i2c_stop(soft_i2c);
return 0;
}
/**
* @brief 向指定I2C设备的寄存器写入多个字节
* @details I2C通信流程:START -> 发送从机地址(写) -> 发送寄存器地址 ->
* 连续发送len个字节,每个字节后等待应答 -> STOP
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @param addr: I2C设备地址(7位,不包含读写位)
* @param reg: 设备寄存器地址
* @param len: 写入字节数
* @param buf: 指向待写入数据的缓冲区指针
* @retval 0: 写入成功
* @retval 1: 写入失败
*/
uint8_t soft_i2c_write_dev_len_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,uint8_t reg,uint8_t len,uint8_t *buf)
{
uint8_t i;
soft_i2c_start(soft_i2c);
soft_i2c_send_byte(soft_i2c,(addr<<1)|0);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
soft_i2c_send_byte(soft_i2c,reg);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
for(i=0; i<len; i++)
{
soft_i2c_send_byte(soft_i2c,buf[i]);
if(soft_i2c_wait_ack(soft_i2c))
{
soft_i2c_stop(soft_i2c);
return 1;
}
}
soft_i2c_stop(soft_i2c);
return 0;
}
/**
* @brief 向I2C设备写入数据(简化接口)
* @details I2C通信流程:START -> 发送从机地址(写) -> 连续发送len个字节 ->
* 每个字节后等待应答 -> STOP
* 相比 soft_i2c_write_dev_len_byte,此函数不需要指定寄存器地址
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @param addr: I2C设备地址(7位,不包含读写位)
* @param buf: 指向待写入数据的缓冲区指针
* @param len: 写入字节数
* @retval 0: 写入成功
* @retval 1: 写入失败
*/
uint8_t soft_i2c_write(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,const uint8_t *buf,uint8_t len)
{
uint8_t i;
soft_i2c_start(soft_i2c);
soft_i2c_send_byte(soft_i2c, (addr << 1) | 0);
if (soft_i2c_wait_ack(soft_i2c))
goto err;
for (i = 0; i < len; i++)
{
soft_i2c_send_byte(soft_i2c, buf[i]);
if (soft_i2c_wait_ack(soft_i2c))
goto err;
}
soft_i2c_stop(soft_i2c);
return 0;
err:
soft_i2c_stop(soft_i2c);
return 1;
}
/**
* @brief 从I2C设备读取数据(简化接口)
* @details I2C通信流程:START -> 发送从机地址(读) -> 连续读取len个字节 ->
* 最后一个字节后NACK -> STOP
* 相比 soft_i2c_read_dev_len_byte,此函数不需要指定寄存器地址
* @param soft_i2c: I2C编号
* @arg SOFT_I2C1: 软件I2C1
* @arg SOFT_I2C2: 软件I2C2
* @param addr: I2C设备地址(7位,不包含读写位)
* @param buf: 指向数据保存缓冲区的指针
* @param len: 读取字节数(必须大于0)
* @retval 0: 读取成功
* @retval 1: 读取失败或长度为0
* @note 当len为0时返回失败
*/
uint8_t soft_i2c_read(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,uint8_t *buf,uint8_t len)
{
uint8_t i;
if (len == 0)
return 1;
soft_i2c_start(soft_i2c);
soft_i2c_send_byte(soft_i2c, (addr << 1) | 1);
if (soft_i2c_wait_ack(soft_i2c))
goto err;
for (i = 0; i < len; i++)
{
if (i == (len - 1))
buf[i] = soft_i2c_read_byte(soft_i2c, 0); // NACK
else
buf[i] = soft_i2c_read_byte(soft_i2c, 1); // ACK
}
soft_i2c_stop(soft_i2c);
return 0;
err:
soft_i2c_stop(soft_i2c);
return 1;
}
bsp_soft_i2c.h
#ifndef _BSP_SOFT_I2C_H
#define _BSP_SOFT_I2C_H
#include <stdint.h>
typedef enum {
SOFT_I2C1 = 0,
SOFT_I2C2 ,
}SOFT_I2C_TypeDef;
void soft_i2c_init(SOFT_I2C_TypeDef soft_i2c);
void soft_i2c_start(SOFT_I2C_TypeDef soft_i2c);
void soft_i2c_stop(SOFT_I2C_TypeDef soft_i2c);
uint8_t soft_i2c_wait_ack(SOFT_I2C_TypeDef soft_i2c);
void soft_i2c_ack(SOFT_I2C_TypeDef soft_i2c);
void soft_i2c_nack(SOFT_I2C_TypeDef soft_i2c);
void soft_i2c_send_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t txd);
uint8_t soft_i2c_read_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t ack);
uint8_t soft_i2c_write_dev_one_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,uint8_t reg,uint8_t data);
uint8_t soft_i2c_read_dev_one_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,uint8_t reg,uint8_t *data);
uint8_t soft_i2c_write_dev_len_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,uint8_t reg,uint8_t len,uint8_t *buf);
uint8_t soft_i2c_read_dev_len_byte(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,uint8_t reg,uint8_t len,uint8_t *buf);
uint8_t soft_i2c_write(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,const uint8_t *buf,uint8_t len);
uint8_t soft_i2c_read(SOFT_I2C_TypeDef soft_i2c,uint8_t addr,uint8_t *buf,uint8_t len);
#endif

浙公网安备 33010602011771号