一、RTC
1.1CobeMX
1. 进入 RTC 配置界面
在左侧菜单栏 Timers 分类下,点击 RTC 进入配置页面。
2. 模式与时钟源设置
- Mode :
Calendar(日历模式,用于计时) - Clock Source :
LSE(外部低速晶振,推荐 32.768kHz) - LSE 设置 :选择
Crystal/Ceramic Resonator(外接 32.768kHz 晶振)
3. 时间与日期基础配置
在 Configuration → Parameter Settings 中设置初始时间:
| 参数 | 配置值 | 说明 |
|---|---|---|
| Hour Format | 24-hour |
24 小时制 |
| Asynchronous Prediv | 127 |
异步预分频器 |
| Synchronous Prediv | 255 |
同步预分频器 |
| Hour/Minute/Second | 自定义初始值 | 上电后的默认时间 |
| Year/Month/Day | 自定义初始值 | 上电后的默认日期 |
分频计算:32.768kHz / (127+1) / (255+1) = 1Hz,保证 RTC 秒脉冲准确。
4. 后备域设置(可选)
Backup registers:保持默认即可,可用于存储断电后需保留的用户数据。- 开启
RTC alarm A(可选):用于闹钟定时唤醒,本项目暂不使用。

2.2程序
uint8_t BCD2DEC(uint8_t bcd);
void RTC_ReadData(void);
void RTC_OLED(void);
// RTC 时间日期变量
uint8_t hours, minutes, seconds;
uint8_t year, month, date, weekday;
int main(void)
{
OLED_Init();
OLED_Clear();
..........
while(1)
{
RTC_ReadData(void);
RTC_OLED(void);
..........
}
}
// BCD码转十进制
uint8_t BCD2DEC(uint8_t bcd)
{
return ((bcd >> 4) * 10) + (bcd & 0x0F);
}
/*** 读取RTC时间日期并转换***/
void RTC_ReadData(void)
{
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BCD);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BCD);
hours = BCD2DEC(sTime.Hours);
minutes = BCD2DEC(sTime.Minutes);
seconds = BCD2DEC(sTime.Seconds);
year = BCD2DEC(sDate.Year);
month = BCD2DEC(sDate.Month);
date = BCD2DEC(sDate.Date);
weekday = sDate.WeekDay;
}
//显示时间
void RTC_OLED(void)
{
// 2. OLED 显示时间、日期、星期
char buf[20];
// 显示时间 HH:MM:SS
sprintf(buf, "%02d:%02d:%02d", hours, minutes, seconds);
OLED_ShowString(0, 0, (uint8_t *)buf, 16);
// 显示日期 20XX/MM/DD
sprintf(buf, "20%02d/%02d/%02d", year, month, date);
OLED_ShowString(0, 2, (uint8_t *)buf, 16);
// 显示星期
const char *week_str[] = {"Mon","Tue","Wed","Thu","Fri","Sat","Sun"};
OLED_ShowString(0, 4, (uint8_t *)week_str[weekday - 1], 16);
}
二、TB6612电机驱动
2.1引脚接线口
| TB6612FNG 引脚 | STM32 / 系统连接 | 功能说明 |
|---|---|---|
| BIN1 | PA4 | 电机 B 方向控制信号 1,与 BIN2 配合决定转向 |
| BIN2 | PA5 | 电机 B 方向控制信号 2,与 BIN1 配合决定转向 |
| PWMB | PA10 (TIM2_CH3) | 电机 B PWM 调速信号,控制转速(0~ARR 占空比) |
| STBY | PB2 | 模块休眠 / 使能信号,高电平启用驱动,低电平进入休眠 |
| VM | 外接电源正极(如 5V/12V) | 电机功率电源输入,为电机提供动力 |
| GND | 系统地 | 公共地,必须与 STM32 共地 |
| VCC | 5V 或 3.3V | 驱动模块逻辑供电,推荐与 STM32 同电压(3.3V/5V 兼容) |
| AIN1/AIN2/PWMA | (未使用) | 电机 A 方向与 PWM 控制引脚,本项目暂未使用 |
| AO1/AO2/BO1/BO2 | 电机接线端 | AO1/AO2 对应电机 A,BO1/BO2 对应电机 B,接电机线 |
2.2Code MX配置
2.2.1、配置PWM
- 进入
Timers → TIM2 - Mode 设置 :
- 时钟源:
Internal Clock - Channel3/4:
PWM Generation CH3
- 时钟源:
- 计数器配置 :
- Prescaler:
72-1 - Counter Period:
1000-1 - 向上计数模式
- Prescaler:
- PWM 通道 3/4 配置 :
- 模式:
PWM mode 1 - 初始 Pulse:
0 - 极性:
High
- 模式:
通道3输出的PWM用来控制电机转速,通道4输出的PWM用来控制电灯的亮度。

2.2.2、配置GPIO
用PA4/5两个GPIO口来控制电机的转动方向。
| PA4/5 | 当前设置 | 对应英文选项 |
|---|---|---|
| GPIO 输出电平 | 初始低电平 | Low |
| GPIO 模式 | 推挽输出 | Output Push Pull |
| 上下拉配置 | 无上下拉 | No pull-up and no pull-down |
| 最大输出速度 | 低速 | Low |

2.3、程序
2.3.1、tb6612.文件
#ifndef __TB6612_H__
#define __TB6612_H__
#include"main.h"
//宏定义
//控制电机转速接口
#define BIN1_PIN GPIO_PIN_4
#define BIN1_PORT GPIOA
#define BIN2_PIN GPIO_PIN_5
#define BIN2_PORT GPIOA
//风扇挡位,CCR的值
#define Speed_Gear1 300
#define Speed_Gear2 500
#define Speed_Gear3 850
//温度阈值
#define Tpt_Gear0 28.0f
#define Tpt_Gear1 32.0f
#define Tpt_Gear2 36.0f
#define Tpt_Gear3 36.0f
//PWM输出通道
#define tb_channel TIM_CHANNEL_3
//函数声明
void MOTOR_B_Forwar(uint16_t speed);
void MOTOR_B_Backwar(uint16_t speed);
void MOTOR_B_Brake(void);
void MOTOR_B_Stop(void);
void MOTOR_B_Rev(float temp);
#endif
2.3.2、tb6612.c文件
1、控制逻辑
| 控制函数 | BIN1 电平 | BIN2 电平 | PWM 占空比 | 电机状态 | 说明 |
|---|---|---|---|---|---|
MOTOR_B_Forwar(speed) |
高 | 低 | speed(0~999) |
正转 | speed 越大,转速越快;speed=0 时停止输出 |
MOTOR_B_Backwar(speed) |
低 | 高 | speed(0~999) |
反转 | speed 越大,转速越快;speed=0 时停止输出 |
MOTOR_B_Brake() |
高 | 高 | 任意 | 刹车 / 急停 | 电机被强制短接,快速制动 |
MOTOR_B_Stop() |
低 | 低 | 任意 | 滑行 / 自由停机 | 电机断电,靠惯性缓慢停下 |
2、控制代码
//电机B正转speed:0~999,
void MOTOR_B_Forwar(uint16_t speed)
{
HAL_GPIO_WritePin(BIN1_PORT,BIN1_PIN,GPIO_PIN_SET);
HAL_GPIO_WritePin(BIN2_PORT,BIN2_PIN,GPIO_PIN_RESET);
__HAL_TIM_SET_COMPARE(&htim2,tb_channel,speed);
}
//电机B反转转speed:0~999
void MOTOR_B_Backwar(uint16_t speed)
{
HAL_GPIO_WritePin(BIN1_PORT,BIN1_PIN,GPIO_PIN_RESET);
HAL_GPIO_WritePin(BIN2_PORT,BIN2_PIN,GPIO_PIN_SET);
__HAL_TIM_SET_COMPARE(&htim2,tb_channel,speed);
}
//电机B刹车
void MOTOR_B_Brake(void)
{
HAL_GPIO_WritePin(BIN1_PORT,BIN1_PIN,GPIO_PIN_SET);
HAL_GPIO_WritePin(BIN2_PORT,BIN2_PIN,GPIO_PIN_SET);
}
//电机B滑行停机
void MOTOR_B_Stop(void)
{
HAL_GPIO_WritePin(BIN1_PORT,BIN1_PIN,GPIO_PIN_RESET);
HAL_GPIO_WritePin(BIN2_PORT,BIN2_PIN,GPIO_PIN_RESET);
}
//判断挡位
void MOTOR_B_Rev(float temp)
{
uint16_t speed = 0;
if(temp > Tpt_Gear0)
{
if(temp < Tpt_Gear1)
{
speed = Speed_Gear1;
}
else if(temp <= Tpt_Gear2)
{
speed = Speed_Gear2;
}
else if(temp > Tpt_Gear3)
{
speed = Speed_Gear3;
}
MOTOR_B_Forwar(speed);
}
else
{
MOTOR_B_Stop();
}
}
2.4、main
int main(void)
{
HAL_TIM_PWM_Start(&htim2,tb_channel);
..........
while(1)
{
DHT11_Task();
MOTOR_B_Rev(DHT11_data.temp);//温度控制电机
..........
}
}
三、电灯
3.1、引脚
LED的正极接PB11,负极接GND。
3.2、代码
#ifndef __LCD_H__
#define __LCD_H__
#include"main.h"
#define LED_channel TIM_CHANNEL_4 //PWM输出通道
void LED_Sta(uint16_t speed);
#endif
#include"LCD.h"
#include"tim.h"
void LED_Sta(uint16_t speed)
{
__HAL_TIM_SET_COMPARE(&htim2,LED_channel,speed);
}
四、蓝牙&光照强度控制电灯
4.1、代码
uint8_t LED_Flag = 0; //LED控制权标志位,默认上电控制权给光照强度
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
USART_Flag = 1;//数据接收标志位
LED_Flag = 1; //LED控制权标志位
}
//重启串口
HAL_UART_Receive_IT(&huart1, &Rx_date, 1);//必须重启,要不然只能接收到一次数据
}
int main(void)
{
HAL_TIM_PWM_Start(&htim2,LED_channel);
..........
while(1)
{
/*******蓝牙指令&光照强度控制LED*******/
if(LED_Flag)//LED_Flag为1时,LED的控制权在蓝牙
{
if(USART_Flag)//判断蓝牙是否发送了数据
{
if(Rx_date == 0xA1)//'0xA1'1档,将电灯亮度反馈给蓝牙
{
LED_Sta(300);
printf("speed: %d\r\n", 300);
}
else if(Rx_date == 0xA2)//'0xA2'2档,将电灯亮度反馈给蓝牙
{
LED_Sta(500);
printf("speed: %d\r\n", 500);
}
else if(Rx_date == 0xA3)//'0xA3'3档,将电灯亮度反馈给蓝牙
{
LED_Sta(850);
printf("speed: %d\r\n", 850);
}
else if(Rx_date == 0xA4)//'0xA4'关闭,将控制权还给光敏
{
LED_Sta(0);
LED_Flag = 0;
}
USART_Flag = 0;//清除接收标志位
}
}
else//LED_Flag为0时,LED的控制权在光敏,数值大于1500开启电灯
{
if(adc_val[0] > 1500)
{
if(adc_val[0] > 3000)
{
LED_Sta(850);
}
else if(adc_val[0] > 2300)
{
LED_Sta(500);
}
else if(adc_val[0] > 1500)
{
LED_Sta(300);
}
}
else
{
LED_Sta(0);
}
}
.........
}
}
4.2、蓝牙配对操作流程
- 打开手机 蓝牙助手(调试全能王),进入蓝牙搜索界面
- 找到对应的蓝牙名称
- 点击连接,默认配对密码:0000 或 1234
- 配对成功:模块指示灯变为慢闪,建立串口透传通道
接收编码格式:gb2312(中文显示编码),用来接收亮度反馈。
发送'A1''A2'等指令控制。

五、按键切换屏幕页面&调节风扇挡位
5.1、Cobe MX配置
将PB0/1,配置为外部中断下降沿触发模式,开启内部上拉。PB0用来切换屏幕,PB1用来调节风扇转速。


5.2、代码
5.2.1、key.h
#ifndef __KEY_H__
#define __KEY_H__
#include "main.h"
extern uint8_t key_int_flag;
extern uint8_t motor_key_flag;
extern uint8_t key_inoup_flag;
#endif
5.2.2、key.c
#include"key.h"
uint8_t key_int_flag = 0;
uint8_t key_inoup_flag = 0;
uint8_t motor_key_flag = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_PIN)
{
if(GPIO_PIN == GPIO_PIN_0)//判断是否为PB0产生的中断
{
if(key_int_flag)
{
key_int_flag = 0;//'0'屏幕显示时间
}
else
{
key_int_flag = 1;//'1'屏幕显示环境参数
}
key_inoup_flag = 1;//标志是否产生中断
}
if(GPIO_PIN == GPIO_PIN_1)//判断是否为PB1产生的中断
{
motor_key_flag = 1;//标志是否产生中断
}
}
5.2.3、屏幕切换
int main(void)
{
.........
while(1)
{
/*******页面切换*******/
if(key_int_flag)
{
HAL_Delay(10);
if(key_inoup_flag)//只有屏幕发生切换时才清一次屏幕
OLED_Clear();
key_inoup_flag = 0;//清除中断标志位
Env_OLED();//显示环境
}
else if(0 == key_int_flag)
{
HAL_Delay(20);
if(key_inoup_flag)
OLED_Clear();
key_inoup_flag = 0;//清除中断标志位
RTC_OLED();//显示时间
}
.......
}
}
5.2.4、按键调速
uint8_t tb_Flag = 1; //电机控制权标志位,默认上电控制权给温度
uint8_t Speed_Gear_Num = 0; //按键计数
int main(void)
{
............
while(1)
{
/*******电机按键消抖 + 档位控制(修复抖动核心)*******/
if(motor_key_flag == 1)
{
HAL_Delay(5); // 20ms 消抖延时
// 再次读取引脚电平,确认按键真的按下(双重防抖)
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
{
Speed_Gear_Num += 1;
tb_Flag = 0;
}
motor_key_flag = 0; // 清空按键标志
}
/*******电机驱动切换*******/
if(tb_Flag)//判断电机控制权
{
MOTOR_B_Rev(DHT11_data.temp);//温度控制电机
}
else
{
HAL_Delay(20);
/*根据产生的中断次数来判断电机的挡位,1~3三个档转速,其他计数值则将控制权交给温度
if(1 == Speed_Gear_Num)
{
MOTOR_B_Forwar(Speed_Gear1);
}
else if(2 == Speed_Gear_Num)
{
MOTOR_B_Forwar(Speed_Gear2);
}
else if(3 == Speed_Gear_Num)
{
MOTOR_B_Forwar(Speed_Gear3);
}
else
{
MOTOR_B_Stop();//关闭电机
Speed_Gear_Num = 0;//计数清零
tb_Flag = 1;//释放控制权
}
}
..........
}
}
六、main.c
下面的代码需要手写的。
#include "oled.h"
#include "dht11.h"
#include "stdio.h"
#include "tb6612.h"
#include "key.h"
#include "LCD.h"
uint8_t BCD2DEC(uint8_t bcd);
void RTC_ReadData(void);
void RTC_OLED(void);
void Env_OLED(void);
uint16_t adc_val[2] = {0};//存放MQ135和光敏的数据
int i = 0; //循环变量
// RTC 时间日期变量
uint8_t hours, minutes, seconds;
uint8_t year, month, date, weekday;
uint8_t tb_Flag = 1; //电机控制权标志位,默认上电控制权给温度
uint8_t Speed_Gear_Num = 0; //按键计数
uint8_t Rx_date = 0; //接收串口数据
uint8_t USART_Flag = 0; //接收标志位
uint8_t LED_Flag = 0; //LED控制权标志位,默认上电控制权给光照强度
int main(void)
{
OLED_Init();
OLED_Clear();
HAL_TIM_PWM_Start(&htim2,tb_channel);
HAL_TIM_PWM_Start(&htim2,LED_channel);
HAL_UART_Receive_IT(&huart1, &Rx_date, 1);
while (1)
{
for(i = 0; i < 2; i++)
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1,10);
adc_val[i] = HAL_ADC_GetValue(&hadc1);
}
//启动DHT11
DHT11_Task();
// 读取RTC时间日期
RTC_ReadData();
/*******页面切换*******/
if(key_int_flag)
{
HAL_Delay(10);
if(key_inoup_flag)
OLED_Clear();
key_inoup_flag = 0;
Env_OLED();
}
else if(0 == key_int_flag)
{
HAL_Delay(20);
if(key_inoup_flag)
OLED_Clear();
key_inoup_flag = 0;
RTC_OLED();
}
key_inoup_flag = 0;
/*******电机按键消抖 + 档位控制(修复抖动核心)*******/
if(motor_key_flag == 1)
{
HAL_Delay(5); // 20ms 消抖延时
// 再次读取引脚电平,确认按键真的按下(双重防抖)
if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1) == GPIO_PIN_RESET)
{
Speed_Gear_Num += 1;
tb_Flag = 0;
}
motor_key_flag = 0; // 清空按键标志
}
/*******电机驱动切换*******/
if(tb_Flag)
{
MOTOR_B_Rev(DHT11_data.temp);//温度控制电机
}
else
{
HAL_Delay(20);
if(1 == Speed_Gear_Num)
{
MOTOR_B_Forwar(Speed_Gear1);
}
else if(2 == Speed_Gear_Num)
{
MOTOR_B_Forwar(Speed_Gear2);
}
else if(3 == Speed_Gear_Num)
{
MOTOR_B_Forwar(Speed_Gear3);
}
else
{
MOTOR_B_Stop();
Speed_Gear_Num = 0;
tb_Flag = 1;//释放控制权
}
}
/*******蓝牙指令&光照强度控制LED*******/
if(LED_Flag)
{
if(USART_Flag)
{
if(Rx_date == 0xA1)
{
LED_Sta(300);
printf("speed: %d\r\n", 300);
}
else if(Rx_date == 0xA2)
{
LED_Sta(500);
printf("speed: %d\r\n", 500);
}
else if(Rx_date == 0xA3)
{
LED_Sta(850);
printf("speed: %d\r\n", 850);
}
else if(Rx_date == 0xA4)
{
LED_Sta(0);
LED_Flag = 0;
}
USART_Flag = 0;
}
}
else
{
if(adc_val[0] > 1500)
{
if(adc_val[0] > 3000)
{
LED_Sta(850);
}
else if(adc_val[0] > 2300)
{
LED_Sta(500);
}
else if(adc_val[0] > 1500)
{
LED_Sta(300);
}
}
else
{
LED_Sta(0);
}
}
HAL_Delay(1000); // 间隔 3 秒(≥DHT11 响应时间 2s)
}
}
//显示环境
void Env_OLED(void)
{
char buf[20];
// 页面1:环境参数
sprintf((char *)buf, "Light:%d", adc_val[0]);
OLED_ShowString(0, 0, (uint8_t *)buf, 16);
sprintf((char *)buf, "Air:%d", adc_val[1]);
OLED_ShowString(0, 2, (uint8_t *)buf, 16);
sprintf((char *)buf, "Temp:%d C", DHT11_data.temp);
OLED_ShowString(0, 4, (uint8_t *)buf, 16);
sprintf((char *)buf, "Hum:%d %%RH", DHT11_data.humidity);
OLED_ShowString(0, 6, (uint8_t *)buf, 16);
}
//显示时间
void RTC_OLED(void)
{
// 2. OLED 显示时间、日期、星期
char buf[20];
// 显示时间 HH:MM:SS
sprintf(buf, "%02d:%02d:%02d", hours, minutes, seconds);
OLED_ShowString(0, 0, (uint8_t *)buf, 16);
// 显示日期 20XX/MM/DD
sprintf(buf, "20%02d/%02d/%02d", year, month, date);
OLED_ShowString(0, 2, (uint8_t *)buf, 16);
// 显示星期
const char *week_str[] = {"Mon","Tue","Wed","Thu","Fri","Sat","Sun"};
OLED_ShowString(0, 4, (uint8_t *)week_str[weekday - 1], 16);
}
// BCD码转十进制
uint8_t BCD2DEC(uint8_t bcd)
{
return ((bcd >> 4) * 10) + (bcd & 0x0F);
}
/*** 读取RTC时间日期并转换***/
void RTC_ReadData(void)
{
RTC_TimeTypeDef sTime;
RTC_DateTypeDef sDate;
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BCD);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BCD);
hours = BCD2DEC(sTime.Hours);
minutes = BCD2DEC(sTime.Minutes);
seconds = BCD2DEC(sTime.Seconds);
year = BCD2DEC(sDate.Year);
month = BCD2DEC(sDate.Month);
date = BCD2DEC(sDate.Date);
weekday = sDate.WeekDay;
}
/***串口接收中断回调函数***/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
USART_Flag = 1;
LED_Flag = 1;
}
//重启串口
HAL_UART_Receive_IT(&huart1, &Rx_date, 1);
}