16、传感器与微控制器系统的应用与实现

传感器与微控制器系统实现

传感器与微控制器系统的应用与实现

1. 寄存器介绍

1.1 配置寄存器

地址为 01h 的是 16 位配置寄存器,我们将使用其第 14 和 13 位来定义传感器的转换分辨率。在固件中,我们会将配置寄存器的其他可写位设置为零,这是上电后的默认值。

1.2 指针寄存器

“加一寄存器”是 8 位指针寄存器。若将指针寄存器的值设为 00h,我们将操作温度寄存器;若设为 01h,则操作配置寄存器。

1.3 其他寄存器

地址为 02h 和 03h 的寄存器对于边界观察和生成报警信号很重要。寄存器 11h - 13h 可将 01h - 03h 寄存器的值复制到 EEPROM 区域,以便在掉电和上电后恢复这些值。

2. I2C 通信

2.1 配置寄存器通信

2.1.1 读写操作步骤

读写操作通常包含两个步骤:
1. 将操作的寄存器地址设置到指针寄存器。
2. 执行所需操作。

配置寄存器是 16 位寄存器,读写时会传输 2 个字节,但允许在传输第一个字节后结束操作。由于第一个传输的字节总是 MSB(高 8 位),且我们只关注配置寄存器的 MSB,所以可在传输第一个字节后中断通信。

2.1.2 读操作步骤
  1. 主机发送起始条件。
  2. 主机发送温度寄存器的 I2C 地址并指示写操作;若 A2 = A1 = A0 = GND,主机将发送 1001 0000b。
  3. 温度传感器用 ACK 确认地址。
  4. 主机发送要写入指针寄存器的地址,这里是 01h。
  5. 温度传感器用 ACK 确认数据。
  6. 主机发送停止条件。
  7. 主机发送起始条件。
  8. 主机发送温度寄存器的 I2C 地址并指示读操作;若 A2 = A1 = A0 = GND,主机将发送 1001 0001b。
  9. 主机从传感器读取配置寄存器的 MSB(高 8 位)。
  10. 主机发送 NOT ACK,因为我们不关心 LSB。
  11. 主机通过发送停止条件结束通信。
2.1.3 写操作步骤
  1. 主机发送起始条件。
  2. 主机发送温度寄存器的 I2C 地址并指示写操作。若 A2 = A1 = A0 = GND,主机将发送 1001 0000b。
  3. 温度传感器用 ACK 确认地址。
  4. 主机发送要写入指针寄存器的地址,这里是 01h。
  5. 温度传感器用 ACK 确认数据。
  6. 主机发送配置寄存器的 MSB 值。
  7. 温度传感器用 ACK 确认数据。
  8. 主机通过发送停止条件结束通信。

2.2 温度寄存器通信

读取温度寄存器内容的过程与读取配置寄存器类似,只是操作的是另一个寄存器,且要读取 16 位寄存器的 MSB 和 LSB 值。

2.2.1 读操作步骤
  1. 主机发送起始条件。
  2. 主机发送温度寄存器的 I2C 地址并指示写操作;若 A2 = A1 = A0 = GND,主机将发送 1001 0000b。
  3. 温度传感器用 ACK 确认地址。
  4. 主机发送要写入指针寄存器的地址,这里是 00h。
  5. 温度传感器用 ACK 确认数据。
  6. 主机发送停止条件。
  7. 主机发送起始条件。
  8. 主机发送温度寄存器的 I2C 地址并指示读操作;若 A2 = A1 = A0 = GND,主机将发送 1001 0001b。
  9. 主机从传感器读取温度寄存器的 MSB(高 8 位)。
  10. 主机用 ACK 确认,因为要继续读取。
  11. 主机从传感器读取温度寄存器的 LSB(低 8 位)。
  12. 主机发送 NOT ACK,因为已获取完整 16 位数据,可结束通信。
  13. 主机通过发送停止条件结束通信。

2.3 湿度传感器 HYT939

2.3.1 传感器特性

该传感器可测量 0% - 100% 的湿度,精度为 1.8%,还能测量 -40°C 到 125°C 的温度,精度为 0.2°C。它工作在 2.7V - 5.5V 的电源下,是通过 I2C 总线与主机通信的数字传感器,采用 TO - 39 封装,有四个引脚,提供电源(Vcc、GND)和 I2C 总线的两条必要线路(SDA 和 SCL)。

传感器的 I2C 地址需谨慎处理,数据手册中标准 I2C 地址为 28h(0010 1000b),但测试发现该地址的传感器不可用,实际使用地址 50h(0101 0000b)。湿度测量分辨率为 14 位,0000h 表示 0% 湿度,3FFFh 表示 100% 湿度。

2.3.2 I2C 通信

传感器使用两个“命令”:
- MR = 测量请求
- DF = 数据获取

测量请求步骤
  1. 主机发送起始条件。
  2. 主机发送传感器的 I2C 地址并指示写操作,主机发送 0101 0000b,LSb 为写指示。
  3. 传感器用 ACK 响应,表示识别为 MR 命令并开始测量。
  4. 主机通过发送停止条件结束通信。
数据获取步骤
  1. 主机发送起始条件。
  2. 主机发送传感器的 I2C 地址并指示读操作,主机发送 0101 0001b,LSb 为读指示。
  3. 传感器用 ACK 响应,表示识别为 DF 命令。
  4. 主机读取湿度寄存器的 MSB(主机将 MSB 的第 7 和第 6 位设为零)。
  5. 主机发送 ACK。
  6. 主机接收湿度寄存器的 LSB。
  7. 主机发送 ACK。
  8. 主机接收温度寄存器的 MSB。
  9. 主机发送 ACK。
  10. 主机接收温度寄存器的 LSB(主机将 LSB 的第 1 和第 0 位设为零)。
  11. 主机发送 NOT ACK。
  12. 主机通过发送停止条件结束通信。
2.3.3 湿度和温度计算

根据数据手册,温度(°C)计算公式为:
[T [°C] = \frac{165}{2^{14}} * T_{RAW} - 40]
相对湿度计算公式为:
[rF [\%] = \frac{100}{2^{14}} * rF_{RAW}]

3. 硬件模块

3.1 整体架构

整个系统由 4 个模块组成:
- 主模块(微控制器、LCD、电源和键盘)
- 带 DS1307 的 RTC 模块
- 带 AT30TSE754 的温度测量模块
- 带 HYT939(或类似)的湿度测量模块

假设输入电压为 5.0V,可使用普通智能手机充电器或移动电源为整个系统供电。

3.2 各模块详情

3.2.1 主模块

主模块包含微控制器、电源管理、4x20LCD 和键盘。可通过连接器 J2、J3 和 J4 连接 I2C 设备。I2C 总线使用 P2.1 和 P2.2 实现,使用 4k7 上拉电阻连接 SCL 和 SDA 线。

J2 的电源为 5.0V,连接 RTC 模块的另外两个连接器电源为 3.3V,可使用 ADP3339 芯片或任何输出 3.3V、电流能力至少 250mA 的 LDO 从 5.0V 输入电压获得 3.3V。微控制器、内部 PCF8574A 芯片和两个 I2C 连接器由 3.3V 供电,LCD 模块由 5.0V 供电。

键盘通过 PCF8574A GPIO 芯片以 4x4 矩阵连接,P4 - P7 作为输出一次激活一列,P0 - P3 作为输入读取按下的按钮。通过向相应端口发送逻辑 0 激活一列,然后读取行(P0 - P3)来识别按下的按钮,假设一次只按一个按钮,若按多个按钮,固件可能忽略或选择其中一个,不识别组合按键。

3.2.2 RTC I2C 模块

该模块简单,可在通用板上轻松构建。除 RTC 芯片外,还需一个 32,768 kHz、负载电容为 12.5pF 的时钟晶体。需注意,RTC 模块需 5.0V 供电,使用 3.3V 无法与芯片通信。

3.2.3 温度 I2C 模块

该模块可选,若固件未找到该温度传感器,将使用湿度模块的内部温度传感器进行温度测量。选择 I2C 地址 1001 010x(A0 = A2 = GND,A1 = Vcc),固件会扫描 I2C 总线以找到可用地址的温度传感器。使用 AT30TSE754、AT30TSE752、AT30TSE758 或 AT30TSE750 均可,这些传感器的区别仅在于 EEPROM 容量,此处并不使用。

3.2.4 湿度 I2C 模块

该模块简单,除传感器外,仅需一个 100nF 电容使其稳定。图中的 LED 仅用于电源指示,可连接到适当电阻和 Vcc、GND,但并非必需。

4. 测试固件

4.1 测试功能

测试固件可与 RTC 和温度传感器配合工作,这里先跳过这些功能,重点关注键盘驱动。通过测试固件可检查键盘是否工作以及按键连接是否正确。

4.2 测试过程

启动后,可看到测试固件版本。3 秒后直接进入键盘测试功能,退出后进入选择屏幕,可选择“键盘测试”再次进行测试。

选择“键盘测试”后,可观察键盘界面情况。若未按任何按钮,“键码”显示为 FFh。不同列的按键按下后会有不同显示结果:
- 第一列(左起):按钮 1、4、7 和星号,星号用于退出功能,按下其他按钮会在 LCD 上显示相应结果。
- 第二列:按键 2、5、8 和 0,按下后显示相应结果。
- 第三列:按键 3、6、9 和 #,按下后显示相应结果。
- 第四列:按键 A、B、C 和 D,按下后显示相应结果。

4.3 键盘测试固件代码

// --------------------------------------------------------------------
// --------------------------------------------------------------------
// ---- Keyboard Smoke Test
// --------------------------------------------------------------------
// --------------------------------------------------------------------
void kbd_smoke_test()
{
  active_key = 0xFF;             //(ST-1) per default – no button is pressed
  lcd.clear();                   //(ST-2) clear LCD
  lcd.setCursor(0,3);            //(ST-3a) write the hint how to leave this functionality
  lcd.print("(*) Exit");         //(ST-3b) write the hint how to leave this functionality
  while(active_key != 0x0E)      //(ST-4) main test loop; leave the loop, if star has been pressed
  {
    read_kbd();                  //(ST-5) read active key (pressed key)
    test_show_key_code();        //(ST-6) display active key (pressed key)
    delay(100);                  //(ST-7) wait 100ms
  }
  gv_refresh = 1;                //(ST-8) request LCD refresh after comming back to main firmware loop
}
// --------------------------------------------------------------------

void test_show_key_code()
{
  byte lv_in;
  byte lv_out;

  lcd.setCursor(0,0);
  lcd.print("Key Code: ");
  w_hex(active_key);
  lcd.print(" (");
  if (active_key == 0xFF) lcd.print("-");
  else lcd.print(char(act_key_ascii));
  lcd.print(")");

  lcd.setCursor(0,1);
  lcd.print("Out: P7-P4: ");
  if (active_key == 0xFF) lcd.print("****    ");
  else
  {
    if ((active_key == 0x01) || (active_key == 0x04) || 
        (active_key == 0x07) || (active_key == 0x0E)) lcd.print("0111  C1");
    if ((active_key == 0x02) || (active_key == 0x05) ||
        (active_key == 0x08) || (active_key == 0x00)) lcd.print("1011  C2");
    if ((active_key == 0x03) || (active_key == 0x06) ||
        (active_key == 0x09) || (active_key == 0x0F)) lcd.print("1101  C3");
    if ((active_key == 0x0A) || (active_key == 0x0B) ||
        (active_key == 0x0C) || (active_key == 0x0D)) lcd.print("1110  C4");
  }

  lv_in = in_kbd_7 & in_kbd_6 & in_kbd_5 & in_kbd_4;

  lcd.setCursor(0,2);
  lcd.print("In : P3-P0: ");
  w_in(lv_in);
}
// --------------------------------------------------------------------

void w_out(byte iv_byte)
{
  lcd.print(bitRead(iv_byte,7));
  lcd.print(bitRead(iv_byte,6));
  lcd.print(bitRead(iv_byte,5));
  lcd.print(bitRead(iv_byte,4));
}
// --------------------------------------------------------------------

void w_in(byte iv_byte)
{
  lcd.print(bitRead(iv_byte,3));
  lcd.print(bitRead(iv_byte,2));
  lcd.print(bitRead(iv_byte,1));
  lcd.print(bitRead(iv_byte,0));
}
// --------------------------------------------------------------------

5. 正式固件

5.1 库的使用

固件中使用两个库来驱动 LCD 和 I2C 总线:
- LiquidCrystal.h :用于 LCD 连接。
- Wire.h :用于 I2C 总线连接。

// --------------------------------------------------------------------
/*
1. LCD connection - library: LiquidCrystal.h
 Connection:
 =================================
 LCD pin              Connect to
 ---------------------------------
 01 - GND             GND, pot
 02 - VCC             +5V, pot
 03 - Contrast        Pot wiper
 04 - RS              P1.0
 05 - R/W             GND
 06 - EN              P1.1
 07 - DB0             GND
 08 - DB1             GND
 09 - DB2             GND
 10 - DB3             GND
 11 - DB4             P1.2
 12 - DB5             P1.3
 13 - DB6             P1.4
 14 - DB7             P1.5
 15 - BL+             +5V
 16 - BL-             GND
 =================================
// --------------------------------------------------------------------
2. I2C Bus - Library: Wire.h
   Connection:
      SCL -> P2.1
      SDA -> P2.2
 */
// --------------------------------------------------------------------
#include <LiquidCrystal.h>
#include <Wire.h>
// --------------------------------------------------------------------
// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(P1_0, P1_1, P1_2, P1_3, P1_4, P1_5);
// --------------------------------------------------------------------

以上代码展示了 LCD 和 I2C 的连接方式,最后一行是 LCD 的初始化命令,将连接信息作为输入传递给 LiquidCrystal 定义的 lcd 。后续将继续探讨固件的设置功能等内容。

5.2 设置功能

设置功能是固件初始化的重要部分,它为后续的操作奠定基础。以下是设置功能的主要步骤:

5.2.1 初始化 LCD

在设置函数中,首先要对 LCD 进行初始化操作,确保其能够正常显示信息。代码如下:

void setup() {
  // 初始化 LCD
  lcd.begin(20, 4); // 假设是 20x4 的 LCD
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("System Initializing...");
  // 其他初始化操作...
}

上述代码中, lcd.begin(20, 4) 用于设置 LCD 的列数和行数, lcd.clear() 清空屏幕, lcd.setCursor(0, 0) 将光标移动到屏幕左上角, lcd.print("System Initializing...") 显示初始化提示信息。

5.2.2 初始化 I2C 总线

I2C 总线的初始化是为了确保与各个 I2C 设备(如温度传感器、湿度传感器、RTC 模块等)能够正常通信。代码如下:

void setup() {
  // 初始化 I2C 总线
  Wire.begin();
  // 其他初始化操作...
}

Wire.begin() 函数用于启动 I2C 总线,使微控制器能够与总线上的设备进行通信。

5.2.3 初始化其他设备

除了 LCD 和 I2C 总线,还可能需要对其他设备进行初始化,如键盘、RTC 模块等。以下是一个简单的示例:

void setup() {
  // 初始化键盘
  initKeyboard();
  // 初始化 RTC 模块
  initRTC();
  // 其他初始化操作...
}

initKeyboard() initRTC() 是自定义的初始化函数,用于对键盘和 RTC 模块进行初始化设置。

5.3 主循环功能

主循环是固件的核心部分,它不断地执行各种任务,如读取传感器数据、处理键盘输入、更新显示信息等。以下是主循环的主要功能:

5.3.1 读取传感器数据

在主循环中,需要定期读取温度传感器和湿度传感器的数据,并进行相应的处理。代码如下:

void loop() {
  // 读取温度数据
  int temperature = readTemperature();
  // 读取湿度数据
  int humidity = readHumidity();
  // 处理数据...
}

readTemperature() readHumidity() 是自定义的函数,用于读取温度和湿度传感器的数据。

5.3.2 处理键盘输入

键盘输入是用户与系统交互的重要方式,主循环需要不断地检测键盘输入,并根据输入执行相应的操作。代码如下:

void loop() {
  // 读取键盘输入
  byte key = readKeyboard();
  if (key != 0xFF) {
    // 处理按键事件
    handleKeyEvent(key);
  }
  // 其他操作...
}

readKeyboard() 函数用于读取键盘输入, handleKeyEvent(key) 函数用于处理按键事件。

5.3.3 更新显示信息

根据读取的传感器数据和键盘输入,需要及时更新 LCD 上的显示信息,让用户能够直观地了解系统状态。代码如下:

void loop() {
  // 读取传感器数据
  int temperature = readTemperature();
  int humidity = readHumidity();
  // 更新显示信息
  updateDisplay(temperature, humidity);
  // 其他操作...
}

updateDisplay(temperature, humidity) 函数用于更新 LCD 上的温度和湿度显示信息。

5.4 功能函数实现

为了实现上述的设置功能和主循环功能,需要编写一些具体的功能函数。以下是部分功能函数的实现:

5.4.1 读取温度传感器数据
int readTemperature() {
  // 设置指针寄存器地址为 00h,选择温度寄存器
  Wire.beginTransmission(TEMPERATURE_SENSOR_ADDRESS);
  Wire.write(0x00);
  Wire.endTransmission();
  // 读取温度数据
  Wire.requestFrom(TEMPERATURE_SENSOR_ADDRESS, 2);
  if (Wire.available() >= 2) {
    int msb = Wire.read();
    int lsb = Wire.read();
    int temperatureData = (msb << 8) | lsb;
    // 根据公式计算温度
    float temperature = ((float)temperatureData / 256.0) - 40.0;
    return (int)temperature;
  }
  return 0;
}

上述代码中, TEMPERATURE_SENSOR_ADDRESS 是温度传感器的 I2C 地址,通过 I2C 通信读取温度寄存器的数据,并根据公式计算出实际温度。

5.4.2 读取湿度传感器数据
int readHumidity() {
  // 发送测量请求
  Wire.beginTransmission(HUMIDITY_SENSOR_ADDRESS);
  Wire.write(0x00);
  Wire.endTransmission();
  delay(100); // 等待测量完成
  // 读取湿度数据
  Wire.requestFrom(HUMIDITY_SENSOR_ADDRESS, 4);
  if (Wire.available() >= 4) {
    int humidityMSB = Wire.read();
    int humidityLSB = Wire.read();
    int humidityData = ((humidityMSB & 0x3F) << 8) | humidityLSB;
    // 根据公式计算湿度
    float humidity = ((float)humidityData / 16383.0) * 100.0;
    return (int)humidity;
  }
  return 0;
}

上述代码中, HUMIDITY_SENSOR_ADDRESS 是湿度传感器的 I2C 地址,通过发送测量请求和读取数据,根据公式计算出实际湿度。

5.4.3 处理按键事件
void handleKeyEvent(byte key) {
  switch (key) {
    case 0x01: // 按键 1
      // 执行相应操作
      break;
    case 0x02: // 按键 2
      // 执行相应操作
      break;
    // 其他按键处理...
    default:
      break;
  }
}

handleKeyEvent(byte key) 函数根据不同的按键值执行相应的操作,可根据实际需求进行扩展。

5.5 流程图总结

为了更清晰地展示整个系统的工作流程,以下是一个 mermaid 格式的流程图:

graph TD;
    A[系统启动] --> B[设置功能];
    B --> B1[初始化 LCD];
    B --> B2[初始化 I2C 总线];
    B --> B3[初始化其他设备];
    B --> C[主循环功能];
    C --> C1[读取传感器数据];
    C1 --> C11[读取温度数据];
    C1 --> C12[读取湿度数据];
    C --> C2[处理键盘输入];
    C --> C3[更新显示信息];
    C --> C;

该流程图展示了系统从启动到设置功能,再到主循环功能的整个工作流程,主循环不断地读取传感器数据、处理键盘输入和更新显示信息。

5.6 总结

通过上述的介绍,我们详细了解了传感器与微控制器系统的应用与实现,包括寄存器的使用、I2C 通信协议、硬件模块的搭建、测试固件和正式固件的编写等内容。以下是整个系统的关键信息总结:

项目 详情
寄存器 配置寄存器用于设置传感器分辨率,指针寄存器用于选择操作的寄存器
I2C 通信 包括温度寄存器、配置寄存器、湿度传感器的读写操作,遵循特定的通信步骤
硬件模块 主模块、RTC 模块、温度测量模块、湿度测量模块,各模块有不同的电源要求和连接方式
测试固件 用于测试键盘功能,通过特定的代码实现按键检测和显示
正式固件 使用 LiquidCrystal.h Wire.h 库,包括设置功能和主循环功能,实现传感器数据读取、键盘输入处理和显示更新

通过合理的设计和编程,我们可以构建一个功能完善、稳定可靠的传感器与微控制器系统,实现对温度和湿度的精确测量和监控。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值