传感器与微控制器系统的应用与实现
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 读操作步骤
- 主机发送起始条件。
- 主机发送温度寄存器的 I2C 地址并指示写操作;若 A2 = A1 = A0 = GND,主机将发送 1001 0000b。
- 温度传感器用 ACK 确认地址。
- 主机发送要写入指针寄存器的地址,这里是 01h。
- 温度传感器用 ACK 确认数据。
- 主机发送停止条件。
- 主机发送起始条件。
- 主机发送温度寄存器的 I2C 地址并指示读操作;若 A2 = A1 = A0 = GND,主机将发送 1001 0001b。
- 主机从传感器读取配置寄存器的 MSB(高 8 位)。
- 主机发送 NOT ACK,因为我们不关心 LSB。
- 主机通过发送停止条件结束通信。
2.1.3 写操作步骤
- 主机发送起始条件。
- 主机发送温度寄存器的 I2C 地址并指示写操作。若 A2 = A1 = A0 = GND,主机将发送 1001 0000b。
- 温度传感器用 ACK 确认地址。
- 主机发送要写入指针寄存器的地址,这里是 01h。
- 温度传感器用 ACK 确认数据。
- 主机发送配置寄存器的 MSB 值。
- 温度传感器用 ACK 确认数据。
- 主机通过发送停止条件结束通信。
2.2 温度寄存器通信
读取温度寄存器内容的过程与读取配置寄存器类似,只是操作的是另一个寄存器,且要读取 16 位寄存器的 MSB 和 LSB 值。
2.2.1 读操作步骤
- 主机发送起始条件。
- 主机发送温度寄存器的 I2C 地址并指示写操作;若 A2 = A1 = A0 = GND,主机将发送 1001 0000b。
- 温度传感器用 ACK 确认地址。
- 主机发送要写入指针寄存器的地址,这里是 00h。
- 温度传感器用 ACK 确认数据。
- 主机发送停止条件。
- 主机发送起始条件。
- 主机发送温度寄存器的 I2C 地址并指示读操作;若 A2 = A1 = A0 = GND,主机将发送 1001 0001b。
- 主机从传感器读取温度寄存器的 MSB(高 8 位)。
- 主机用 ACK 确认,因为要继续读取。
- 主机从传感器读取温度寄存器的 LSB(低 8 位)。
- 主机发送 NOT ACK,因为已获取完整 16 位数据,可结束通信。
- 主机通过发送停止条件结束通信。
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 = 数据获取
测量请求步骤
- 主机发送起始条件。
- 主机发送传感器的 I2C 地址并指示写操作,主机发送 0101 0000b,LSb 为写指示。
- 传感器用 ACK 响应,表示识别为 MR 命令并开始测量。
- 主机通过发送停止条件结束通信。
数据获取步骤
- 主机发送起始条件。
- 主机发送传感器的 I2C 地址并指示读操作,主机发送 0101 0001b,LSb 为读指示。
- 传感器用 ACK 响应,表示识别为 DF 命令。
- 主机读取湿度寄存器的 MSB(主机将 MSB 的第 7 和第 6 位设为零)。
- 主机发送 ACK。
- 主机接收湿度寄存器的 LSB。
- 主机发送 ACK。
- 主机接收温度寄存器的 MSB。
- 主机发送 ACK。
- 主机接收温度寄存器的 LSB(主机将 LSB 的第 1 和第 0 位设为零)。
- 主机发送 NOT ACK。
- 主机通过发送停止条件结束通信。
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
库,包括设置功能和主循环功能,实现传感器数据读取、键盘输入处理和显示更新
|
通过合理的设计和编程,我们可以构建一个功能完善、稳定可靠的传感器与微控制器系统,实现对温度和湿度的精确测量和监控。
传感器与微控制器系统实现
超级会员免费看

被折叠的 条评论
为什么被折叠?



