地奇星RA6E2开发板I2C协议详解与BH1750光照传感器驱动实战

最近在做一个智能家居的小项目,需要用到光照传感器,正好手头有地奇星的RA6E2开发板和BH1750传感器模块。很多刚开始接触嵌入式开发的朋友,一看到I2C协议就觉得头大,什么开漏输出、上拉电阻、起始信号,听起来很复杂。其实只要跟着我一步步来,你会发现用I2C驱动一个传感器并没有那么难。今天我就以RA6E2驱动BH1750为例,手把手带你从原理到代码,完整走一遍I2C的实战流程。

1. I2C协议:两线制通信的“交通规则”

1.1 I2C是什么?为什么选它?

I2C,全称Inter-Integrated Circuit,你可以把它理解成芯片之间“聊天”的一种约定好的方式。它最大的优点就是简单,整个“聊天”过程只需要两根线:

  • SDA(串行数据线):专门用来传输数据,相当于“说话的内容”。
  • SCL(串行时钟线):由主设备产生,用来同步数据节奏,相当于“说话的节拍器”。

为什么很多传感器(比如咱们今天用的BH1750)、EEPROM存储器都喜欢用I2C?因为它省引脚啊!一个MCU要是想接好几个传感器,用I2C总线,不管接多少个,都只需要这两根线,大大简化了硬件布线。

在I2C的“聊天室”里,有明确的角色分工:

  • 主设备(Master):通常是咱们的MCU(比如RA6E2)。它负责发起聊天(发送起始信号)、指定跟谁聊(发送从设备地址)、控制聊天节奏(产生时钟),以及结束聊天(发送停止信号)。
  • 从设备(Slave):比如BH1750传感器。它很“被动”,只能等待主设备呼叫自己的“名字”(地址),然后根据主设备的指令回复数据或接收命令。

1.2 I2C的硬件关键:为什么必须是“开漏输出”?

这是理解I2C硬件连接最重要的一点。在配置MCU的I2C引脚时,必须设置为开漏输出模式,而不是常见的推挽输出。为什么呢?我打个比方:

想象一下,I2C总线就像一条大家共用的电话线(SDA)和一条统一的计时器线(SCL)。如果大家都用推挽模式(可以主动输出高或低电平),当主设备想输出高电平(比如3.3V),而某个从设备想输出低电平(0V)时,这两股力量就直接“打架”了,相当于电源正负极短路,很可能烧坏芯片。

为了避免这种“打架”,I2C规定大家都用“开漏输出”。这个模式的特点是:芯片只能把线“拉低”(拉到0V),而不能主动“推高”。当它不想拉低时,就断开内部连接,相当于“放手”。

那么,总线上的高电平从哪里来呢?这就需要我们在SDA和SCL这两条线上,各自接一个上拉电阻到电源(比如3.3V)。当总线上所有设备都“放手”时,上拉电阻就把电压拉到了高电平。当任何一个设备需要发送低电平时,它就去“拉低”这根线。这样,大家就不会“打架”了。

注意:上拉电阻的阻值很关键,一般在2.2kΩ到10kΩ之间。阻值太小,电流大,功耗高;阻值太大,上升沿变慢,影响通信速度。对于咱们开发板这种短距离通信,用4.7kΩ或10kΩ都很常见。

总结一下硬件要点:

  1. MCU的I2C引脚(SDA, SCL)配置为开漏输出
  2. SDA和SCL线上必须连接上拉电阻到电源。
  3. 地奇星RA6E2开发板通常已经把这些上拉电阻集成在板子上了,我们直接用就行。

1.3 I2C的“聊天”流程与信号

I2C通信就像一场有严格礼仪的对话,全靠几个关键信号来组织:

  1. 起始信号(Start):主设备想说“喂,注意了,我要开始说话了!”。它的动作是:在SCL为高电平期间,让SDA线产生一个从高到低的下降沿。
  2. 地址帧(Address + R/W):主设备接着说“我要跟地址是0x23的那位说话,并且我要读它的数据”。这里发送的是一个7位(或10位)的从设备地址,加上1位读写控制位(0表示写,1表示读)。BH1750的地址就是0x23。
  3. 应答信号(ACK):从设备(BH1750)听到自己的地址后,会回应一句“嗯,我在听”。它的动作是在第9个时钟周期,把SDA线拉低。如果地址不对或设备不存在,SDA会保持高,这就是非应答(NACK),告诉主设备“你找错人了”。
  4. 数据帧(Data):接下来就是传输真正的数据内容了,每8位数据后,都会跟一个应答位。
  5. 停止信号(Stop):通信结束,主设备说“好了,我说完了”。它的动作是:在SCL为高电平期间,让SDA线产生一个从低到高的上升沿。

整个流程可以概括为:起始信号 -> 发送从机地址和读写位 -> 等待应答 -> 发送/接收数据(每字节后等待应答) -> 停止信号

地奇星RA6E2的硬件I2C模块支持多种速度模式,我们根据传感器的手册来选择。BH1750支持标准模式(100kbps)和快速模式(400kbps),为了稳定,我们一般先用标准模式。

模式 速率 说明
标准模式 100 kbps 最常用,兼容性好
快速模式 400 kbps 速度较快,需从设备支持
快速模式 Plus 1 Mbps 更高速率
高速模式 3.4 Mbps 最高速率

2. 实战准备:认识BH1750光照传感器

BH1750是一款数字式环境光强度传感器,它直接通过I2C接口输出数字量,省去了我们做模拟量采集和AD转换的麻烦,非常方便。

它的核心特点:

  • 接口:标准的I2C接口,地址为0x23(当ADDR引脚接低电平时)或0x5C(当ADDR引脚接高电平时)。我们的模块通常默认是0x23。
  • 测量范围:1 - 65535 lux(勒克斯,光照度单位)。
  • 工作模式:支持多种分辨率模式,我们常用的是“连续高分辨率模式”(命令字0x10),精度高,测量一次后会自动进行下一次测量。

接线非常简单,只需要四根线:

  • VCC -> 开发板3.3V
  • GND -> 开发板GND
  • SCL -> 开发板I2C时钟引脚(例如P109)
  • SDA -> 开发板I2C数据引脚(例如P110)

3. 地奇星RA6E2的I2C配置(FSP图形化配置)

地奇星RA6E2使用瑞萨的FSP(Flexible Software Package)开发框架,我们可以用图形化工具来配置外设,非常直观。这里假设你已经有一个基础的UART工程(用于打印数据),我们在其基础上添加I2C配置。

3.1 打开I2C时钟与配置引脚

首先,我们需要确保I2C模块的时钟是打开的。在FSP配置界面,找到“Pins”标签页,然后按以下步骤操作:

  1. Peripherals -> Connectivity: I3C/IIC -> I3C/IIC 下,找到你计划使用的I2C通道(例如 IIC0)。
  2. 配置对应的引脚。比如,我们选择 IIC0,将 SDA 分配给 P110,将 SCL 分配给 P109
  3. 关键一步:将 Operation Mode 选择为 Custom,并将 Pin Group Select 设置为 Mode B only。这个模式通常就对应着标准的I2C功能。

3.2 添加并配置I2C Master堆栈

引脚配置好后,我们需要在“Stacks”标签页添加I2C的驱动模块。

  1. 点击“New Stack” -> Connectivity -> I2C Master (r_iic_b_master)。这会创建一个I2C主设备驱动栈。
  2. 点击新生成的 g_i2c_master0 模块,在右侧的属性(Properties)窗口中,我们需要修改几个关键参数:
    • Parameter Checking:可以选择 BSPEnabled,用于调试时检查参数错误。
    • Rate:设置通信速率。我们先设为 Standard (100kbps)。
    • Slave:这里填从设备的7位地址。对于BH1750,地址是0x23,所以这里填 0x23。注意FSP这里通常要求填7位地址,有的驱动也可能要求左移一位后的地址,具体看说明,我们这里先按0x23填。
    • Callback:填写一个回调函数名,比如 BH1750_callback。这个函数用于处理I2C中断事件(如发送完成、接收完成、出错等)。对于简单的读写,我们可以先写个空函数。

配置完成后,记得按下 Ctrl+S 保存,然后点击 Generate Project Content 按钮生成代码。FSP会自动把我们的图形化配置转换成底层初始化代码。

4. 手把手编写BH1750驱动代码

配置完成后,我们开始写代码。为了让工程结构清晰,我们新建两个文件夹来管理代码。

4.1 创建文件与工程结构

src 目录下:

  1. 创建一个 i2c 文件夹,里面放传感器驱动文件:
    • bsp_bh1750.c / bsp_bh1750.h (BH1750的驱动)
  2. 创建一个 Apply 文件夹,里面放应用层文件:
    • app.c / app.h (主应用程序)

最终的工程文件结构看起来像这样:

src/
├── hal_entry.c (主入口,FSP生成)
├── i2c/
│   ├── bsp_bh1750.c
│   └── bsp_bh1750.h
├── Apply/
│   ├── app.c
│   └── app.h
└── uart/ (假设已存在UART驱动)
    ├── bsp_uart.c
    └── bsp_uart.h

4.2 编写驱动层代码 (bsp_bh1750)

首先来看头文件 bsp_bh1750.h,它很简单,就是声明初始化函数和读取函数。

#ifndef __BSP_BH1750_H
#define __BSP_BH1750_H

#include "hal_data.h" // 包含FSP生成的所有硬件抽象层定义
#include "uart/bsp_uart.h" // 为了使用printf调试
#include "stdio.h"

// 函数声明
void BH1750_Init(void);
uint32_t BH1750_read(void);

#endif

接下来是核心的 bsp_bh1750.c 文件。我会逐段解释。

#include "bsp_bh1750.h"

// I2C回调函数。在简单阻塞式读写中,这个函数可能用不到,但必须定义以防止链接错误。
void BH1750_callback(i2c_master_callback_args_t *p_args)
{
    // 防止编译器报“未使用参数”的警告
    (void)p_args;
}

// BH1750初始化函数
void BH1750_Init(void)
{
    uint8_t cmd = 0x10; // BH1750的初始化命令:连续高分辨率模式
    fsp_err_t err = FSP_SUCCESS; // FSP操作的错误码

    // 1. 打开I2C主机控制器,使用FSP自动生成的配置结构体
    err = R_IIC_B_MASTER_Open(&g_i2c_master0_ctrl, &g_i2c_master0_cfg);
    if (err != FSP_SUCCESS) {
        printf("错误:I2C初始化失败,错误码: %d\n", err);
        return; // 初始化失败,直接返回
    }

    // 2. (可选)打印一下配置的从机地址,确认是否正确
    printf("I2C已初始化,从机地址: 0x%02lX\n", g_i2c_master0_cfg.slave);

    // 3. 向BH1750发送模式设置命令
    err = R_IIC_B_MASTER_Write(&g_i2c_master0_ctrl, &cmd, 1, false);
    if (err != FSP_SUCCESS) {
        printf("错误:BH1750模式设置失败,错误码: %d\n", err);
        return;
    }

    // 4. 等待传感器完成初始化。BH1750手册要求上电后至少等待180ms才能读取数据。
    R_BSP_SoftwareDelay(180, BSP_DELAY_UNITS_MILLISECONDS);
    printf("BH1750初始化完成,进入连续测量模式。\n");
}

初始化函数做了四件事:打开I2C外设、打印地址(调试用)、发送模式命令、等待传感器稳定。这里 R_IIC_B_MASTER_Write 函数的最后一个参数是 false,表示发送完这1字节数据后,产生停止信号。为什么?因为对于某些传感器,连续的写操作可能不需要立刻停止。但BH1750单次写命令后可以停止,这里用 falsetrue 通常都可以。

接下来是读取光照值的函数:

uint32_t BH1750_read(void)
{
    uint8_t data[2] = {0}; // 用于存放读回来的两个字节数据
    uint16_t raw_lux = 0;  // 合并后的16位原始数据
    uint32_t lux = 0;      // 计算后的实际光照值
    fsp_err_t err = FSP_SUCCESS;

    // 1. 从BH1750读取2字节光照数据
    // 注意:最后一个参数为false,表示读操作完成后,主设备会发送停止信号。
    err = R_IIC_B_MASTER_Read(&g_i2c_master0_ctrl, data, 2, false);
    if (err != FSP_SUCCESS) {
        printf("错误:BH1750读取失败,错误码: %d\n", err);
        return 0; // 读取失败,返回0
    }

    // 2. 等待一小段时间,确保数据稳定(非必须,但建议保留)
    R_BSP_SoftwareDelay(180, BSP_DELAY_UNITS_MILLISECONDS);

    // 3. 将两个字节数据合并为一个16位整数
    // BH1750的数据格式是高字节在前(MSB),低字节在后(LSB)
    raw_lux = (data[0] << 8) | data[1];

    // 4. 根据BH1750数据手册的公式,将原始数据转换为光照值(单位:lux)
    // 公式:光照值 = 原始数据 / 1.2
    lux = (uint32_t)(raw_lux / 1.2f);

    return lux;
}

这里最关键的是 R_IIC_B_MASTER_Read 函数,它封装了完整的I2C读时序:起始信号 -> 发送设备地址(读)-> 接收数据 -> 停止信号。我们只需要提供存放数据的数组和长度即可。

4.3 编写应用层代码 (app)

应用层代码就非常简洁了,主要就是初始化和循环读取。

app.h 文件:

#ifndef __APP_H
#define __APP_H

#include "hal_data.h"
#include "stdio.h"

void Run(void); // 主运行函数

#endif

app.c 文件:

#include "Apply/app.h"
#include "i2c/bsp_bh1750.h"
#include "uart/bsp_uart.h" // 假设你的UART初始化在这里

void Run(void)
{
    // 1. 初始化串口,用于打印结果
    UART0_Init();
    // 2. 初始化BH1750传感器
    BH1750_Init();

    printf("欢迎使用地奇星RA6E2开发板\r\n");
    printf("BH1750光照传感器实验开始...\r\n\r\n");

    while(1)
    {
        // 3. 循环读取光照强度
        uint32_t light_value = BH1750_read();
        printf("当前光照强度 = %ld lux\r\n", light_value);

        // 4. 延时1秒再读
        R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
    }
}

4.4 修改主入口 (hal_entry.c)

最后,别忘了在FSP生成的 hal_entry.c 文件中调用我们的 Run() 函数。

#include "hal_data.h"
#include "Apply/app.h" // 包含我们的应用头文件

FSP_CPP_HEADER
void R_BSP_WarmStart(bsp_warm_start_event_t event);
FSP_CPP_FOOTER

void hal_entry(void)
{
    /* TODO: add your own code here */
    Run(); // 调用我们的主程序

#if BSP_TZ_SECURE_BUILD
    /* Enter non-secure code */
    R_BSP_NonSecureEnter();
#endif
}

5. 实验现象与调试心得

将代码编译下载到地奇星RA6E2开发板,连接好BH1750模块(VCC, GND, SCL, SDA)和串口模块(用于打印数据)。给开发板上电,打开串口助手(如Putty、SecureCRT等),设置好波特率(与你UART初始化的一致,比如115200)。

你应该能看到串口不断打印出光照强度值。用手遮住传感器,数值会变小;用手机手电筒照射传感器,数值会急剧变大。

几个常见的坑点和调试建议:

  1. I2C地址不对:这是最常见的问题。BH1750的地址引脚(ADDR)电平决定了地址是0x23还是0x5C。一定要确认你的模块状态。如果读不到数据,可以尝试用逻辑分析仪或示波器抓一下I2C波形,看看起始信号后发出的地址对不对。
  2. 没有上拉电阻:虽然开发板可能集成了,但如果你是自己搭的电路,务必在SDA和SCL线上接上4.7kΩ的上拉电阻到3.3V。
  3. 时序问题:BH1750发送模式命令后,需要等待足够的时间(>180ms)才能读取有效数据。延时不够会导致读到0或错误值。
  4. FSP配置错误:仔细检查引脚分配是否正确,I2C堆栈是否成功添加,速率和地址配置是否无误。生成代码后,可以检查一下 hal_data.c 文件,看看 g_i2c_master0_cfg 这个结构体里的参数是不是你设置的值。
  5. 接线错误:再三检查VCC、GND、SDA、SCL这四根线有没有接错或接触不良。

好了,到这里,你已经成功在地奇星RA6E2上通过I2C驱动了BH1750传感器。整个过程其实就分三步:理解I2C协议(知道怎么“聊天”)、用FSP配置硬件(准备好“聊天工具”)、编写驱动代码(发出“聊天”指令)。希望这个详细的实战教程能帮你打通I2C学习的任督二脉,以后遇到其他I2C设备,比如温湿度传感器、加速度计等,都可以举一反三,快速上手。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐