STM32 项目实战:温湿度 / 光敏 / 蓝牙 + 风扇 / LED 双闭环控制(二)

一、RTC

1.1CobeMX

1. 进入 RTC 配置界面

在左侧菜单栏 Timers 分类下,点击 RTC 进入配置页面。

2. 模式与时钟源设置
  • ModeCalendar(日历模式,用于计时)
  • Clock SourceLSE(外部低速晶振,推荐 32.768kHz)
  • LSE 设置 :选择 Crystal/Ceramic Resonator(外接 32.768kHz 晶振)
3. 时间与日期基础配置

ConfigurationParameter 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
    • 向上计数模式
  • 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);
}
相关推荐
省四收割者1 小时前
从硬件中断到分布式协程:全景解构高并发机制与 C / Golang 的巅峰对决
c++·分布式·嵌入式硬件·golang
崇山峻岭之间1 小时前
单片机BLDC PID控制实验
单片机·嵌入式硬件
DLGXY1 小时前
STM32 项目实战:温湿度 / 光敏 / 蓝牙 + 风扇 / LED 双闭环控制(一)
stm32·单片机·嵌入式硬件
贤哥哥yyds13 小时前
【无标题】
stm32
崇山峻岭之间15 小时前
单片机步进电机实验
单片机·嵌入式硬件
xiangw@GZ17 小时前
802.11全系列标准调制编码与速率档对应关系
网络·单片机·嵌入式硬件·架构
希希之光17 小时前
Aurix Tc3xx Port&Dio模块总结
单片机·嵌入式硬件
三品吉他手会点灯17 小时前
STM32F103 学习笔记-24-I2C-读写EEPROM(第1节)-I2C物理层介绍
笔记·stm32·学习
日拱一卒的小田17 小时前
ZYNQ学习笔记2-ZYNQ的UART控制器1
单片机·嵌入式硬件