在创客的世界里,最令人兴奋的莫过于用最少的成本,创造出既有技术含量又能解决实际问题的产品。今天,我们就来挑战一个项目:制作一个功能完备、外观精致的温湿度闹钟,总成本控制在50元以内。它不仅能显示时间、温度和湿度,还能设定闹钟,绝对是提升桌面幸福感的神器!
一、 核心思路:低成本与高溢值的平衡
"低成本"不等于"低品质",而"高溢价"则源于设计感和实用性。我们的策略是:
- 核心廉价:选用成熟、廉价且资料丰富的单片机和传感器。
- 显示升级:不用廉价的数码管,而选用带背光的液晶屏,瞬间提升产品档次。
- 功能集成:将时钟、温湿度、闹钟等常用功能合而为一,提高实用性。
- 外观用心:通过简单的3D打印外壳或亚克力板,让产品拥有完整的"产品形态"。
二、 BOM清单(物料清单)
| 元件名称 | 型号/规格 | 参考价格(元) | 备注 |
|---|---|---|---|
| 主控 | STM32F103C8T6 最小系统板 | ~15 | 性能强大,性价比之王 |
| 显示 | 0.96寸 OLED屏 (I2C接口) | ~10 | 自发光,对比度高,省电 |
| 传感器 | DHT11 温湿度传感器模块 | ~5 | 经典、稳定、易用 |
| 时钟 | DS3231 高精度时钟模块 | ~8 | 带备用电池,断电时间不丢失 |
| 输入 | 轻触按键 x 3 | ~1 | 用于设置时间和闹钟 |
| 其他 | 杜邦线、面包板、蜂鸣器 | ~5 | 用于连接和报警 |
| 外壳 | 3D打印或纸盒 | 0-5 | 可选,但强烈推荐 |
| 总计 | ~49 | 完美控制在预算内! |
三、 硬件连接
接线非常简单,我们主要使用I2C和单总线通信。
-
OLED (I2C) :
VCC->3.3VGND->GNDSCL->PB6(STM32的I2C1_SCL)SDA->PB7(STM32的I2C1_SDA)
-
DS3231 (I2C) : 与OLED并联在同一组I2C总线上。
VCC->3.3VGND->GNDSCL->PB6SDA->PB7
-
DHT11:
VCC->3.3VGND->GNDDATA->PA0(任意GPIO即可)
-
按键 (设置/加/减) :
- 一端分别接
PA1,PA2,PA3 - 另一端接
GND(启用内部上拉电阻)
- 一端分别接
-
蜂鸣器:
- 正极 ->
PA4 - 负极 ->
GND
- 正极 ->
四、 软件与代码
我们将使用STM32CubeMX生成基础工程,然后编写应用逻辑。这里为了简化,我将核心逻辑整合到一个代码文件中,并假设你已经配置好了I2C、GPIO和时钟。
所需库:
HAL库 (STM32CubeMX生成)DHT11驱动库DS3231驱动库SSD1306 OLED驱动库
这些库在GitHub上都非常容易找到。
核心代码 (main.c)
scss
/* USER CODE BEGIN Includes */
#include "dht11.h"
#include "ds3231.h"
#include "ssd1306.h"
#include <stdio.h>
/* USER CODE END Includes */
/* ... CubeMX生成的代码 ... */
/* USER CODE BEGIN PV */
// 定义按键GPIO
#define SET_BUTTON_PIN GPIO_PIN_1
#define SET_BUTTON_PORT GPIOA
#define UP_BUTTON_PIN GPIO_PIN_2
#define UP_BUTTON_PORT GPIOA
#define DOWN_BUTTON_PIN GPIO_PIN_3
#define DOWN_BUTTON_PORT GPIOA
#define BUZZER_PIN GPIO_PIN_4
#define BUZZER_PORT GPIOA
// 全局变量
DHT11_Data_t dht11_data;
DS3231_Time_t ds3231_time;
DS3231_Alarm1_t alarm1;
uint8_t set_mode = 0; // 0:正常显示, 1:设置时间, 2:设置闹钟
uint8_t cursor_pos = 0; // 设置时的光标位置
uint32_t last_tick = 0;
const uint16_t update_interval = 2000; // 2秒更新一次温湿度
/* USER CODE END PV */
// ... 其他函数 ...
int main(void)
{
/* ... CubeMX初始化 ... */
/* USER CODE BEGIN 2 */
// 初始化外设
SSD1306_Init();
DS3231_Init();
HAL_Delay(100); // 等待外设稳定
// 设置一个默认闹钟 (例如 07:30:00)
alarm1.seconds = 0;
alarm1.minutes = 30;
alarm1.hours = 7;
alarm1.day_date = 1; // 不关心具体日期
alarm1.mode = DS3231_ALARM1_EVERY_SECOND; // 先设为每秒触发,用于测试
DS3231_SetAlarm1(&alarm1);
DS3231_EnableAlarm1(1); // 开启闹钟1
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
uint32_t current_tick = HAL_GetTick();
// 按键扫描
handle_buttons();
// 闹钟检测
if (DS3231_CheckAlarm1Flag()) {
DS3231_ClearAlarm1Flag();
trigger_alarm();
}
// 定时更新温湿度
if (current_tick - last_tick > update_interval) {
last_tick = current_tick;
DHT11_Read(&dht11_data);
}
// 显示逻辑
update_display();
HAL_Delay(100); // 主循环延时
/* USER CODE END WHILE */
}
}
void handle_buttons(void) {
if (HAL_GPIO_ReadPin(SET_BUTTON_PORT, SET_BUTTON_PIN) == GPIO_PIN_RESET) {
HAL_Delay(50); // 消抖
if (HAL_GPIO_ReadPin(SET_BUTTON_PORT, SET_BUTTON_PIN) == GPIO_PIN_RESET) {
set_mode = (set_mode + 1) % 3; // 循环切换模式
cursor_pos = 0;
while(HAL_GPIO_ReadPin(SET_BUTTON_PORT, SET_BUTTON_PIN) == GPIO_PIN_RESET); // 等待释放
}
}
if (set_mode != 0) { // 只有在设置模式下才响应加减键
if (HAL_GPIO_ReadPin(UP_BUTTON_PORT, UP_BUTTON_PIN) == GPIO_PIN_RESET) {
HAL_Delay(50);
if (HAL_GPIO_ReadPin(UP_BUTTON_PORT, UP_BUTTON_PIN) == GPIO_PIN_RESET) {
adjust_value(1);
while(HAL_GPIO_ReadPin(UP_BUTTON_PORT, UP_BUTTON_PIN) == GPIO_PIN_RESET);
}
}
if (HAL_GPIO_ReadPin(DOWN_BUTTON_PORT, DOWN_BUTTON_PIN) == GPIO_PIN_RESET) {
HAL_Delay(50);
if (HAL_GPIO_ReadPin(DOWN_BUTTON_PORT, DOWN_BUTTON_PIN) == GPIO_PIN_RESET) {
adjust_value(-1);
while(HAL_GPIO_ReadPin(DOWN_BUTTON_PORT, DOWN_BUTTON_PIN) == GPIO_PIN_RESET);
}
}
}
}
void adjust_value(int direction) {
// 此处简化处理,实际应根据set_mode和cursor_pos调整time或alarm结构体
if (set_mode == 1) { // 设置时间
if (cursor_pos == 0) ds3231_time.hours = (ds3231_time.hours + direction + 24) % 24;
if (cursor_pos == 1) ds3231_time.minutes = (ds3231_time.minutes + direction + 60) % 60;
DS3231_SetTime(&ds3231_time);
} else if (set_mode == 2) { // 设置闹钟
if (cursor_pos == 0) alarm1.hours = (alarm1.hours + direction + 24) % 24;
if (cursor_pos == 1) alarm1.minutes = (alarm1.minutes + direction + 60) % 60;
DS3231_SetAlarm1(&alarm1);
}
cursor_pos = (cursor_pos + 1) % 2; // 自动切换到下一个设置项
}
void update_display(void) {
char buffer[32];
DS3231_GetTime(&ds3231_time);
SSD1306_Clear();
// 第一行:时间
snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d", ds3231_time.hours, ds3231_time.minutes, ds3231_time.seconds);
SSD1306_GotoXY(0, 0);
SSD1306_Puts(buffer, &Font_11x18, SSD1306_COLOR_WHITE);
// 第二行:温湿度
snprintf(buffer, sizeof(buffer), "T:%2dC H:%2d%%", (int)dht11_data.temperature, (int)dht11_data.humidity);
SSD1306_GotoXY(0, 24);
SSD1306_Puts(buffer, &Font_7x10, SSD1306_COLOR_WHITE);
// 第三行:闹钟状态
snprintf(buffer, sizeof(buffer), "Alarm: %02d:%02d", alarm1.hours, alarm1.minutes);
SSD1306_GotoXY(0, 40);
SSD1306_Puts(buffer, &Font_7x10, SSD1306_COLOR_WHITE);
SSD1306_UpdateScreen();
}
void trigger_alarm(void) {
// 简单的蜂鸣报警
for(int i = 0; i < 10; i++) {
HAL_GPIO_WritePin(BUZZER_PORT, BUZZER_PIN, GPIO_PIN_SET);
HAL_Delay(200);
HAL_GPIO_WritePin(BUZZER_PORT, BUZZER_PIN, GPIO_PIN_RESET);
HAL_Delay(200);
}
}
/* ... 其他用户代码 ... */
五、 打造"高溢价"的关键一步:外壳
一个裸露的电路板只是半成品,一个精致的外壳才能让它成为"产品"。
- 3D打印:如果你有3D打印机,可以设计一个专属外壳,考虑按键的开孔、屏幕的视窗以及散热。
- 亚克力激光切割:设计简洁的几何形状,用几层不同颜色的亚克力板堆叠起来,效果非常出众。
- 废物利用:找一个漂亮的旧饼干盒或者小木盒,精心规划内部空间,也能达到意想不到的效果。
六、 总结与拓展
至此,一个成本不到50元,功能强大且外观精美的温湿度闹钟就完成了。它集成了时间显示、环境监测和闹钟功能,无论是自用还是作为礼物都非常有意义。
可以进一步拓展的功能:
- 光敏传感器:根据环境光线自动调节OLED屏幕亮度。
- 蓝牙模块:通过手机APP设置时间和闹钟。
- 更多闹钟:支持设置多个闹钟。
- 温度单位切换:支持摄氏度和华氏度切换。
这个项目完美诠释了创客精神:用技术赋能创意,用低成本实现高价值。现在,轮到你来动手,打造属于你自己的桌面小神器了!