相关连接
【STM32】【HAL库】遥控关灯0 概述
【STM32】【HAL库】遥控关灯1主机
【STM32】【HAL库】遥控关灯2 分机
【STM32】【HAL库】遥控关灯3 遥控器
需求
主机需要以下功能:
- 接收来自物联网平台的命令
- 发送RF433信号给从机
- 接收RF433信号和红外信号
- 驱动舵机动作
方案设计
使用双MCU方案,ESP32C3负责物联网相关通信,STM32负责发送信号给从机和接收RF433的信号,还有舵机控制
本单使用ESP32即可,但手头的RF433的遥控器的协议不是常见的,没找到相关的解码库
而ESP32本人不算熟悉,经过测试没法成功解码,因此使用双MCU方案,后续可能会改进
ESP32与STM32直接使用串口通信
使用巴法云平台作为物联网平台,使用MQTT协议连接
硬件设计
433接收
使用XL700芯片(淘宝)(单价0.52)
电路是数据手册的参考电路,天线使用弹簧天线
433发射
使用XL4456(淘宝)(单价0.47)
电路是数据手册的参考电路,天线使用弹簧天线
esp32
使用ESP32C3(单价10)(也可以使用esp8266模块,但手头无货,故使用这个芯片)
烧录时同时按下两个按键,先松开EN按键 2s以上后在松开Io9按键
stm32
最小系统设计
电源
使用5v电源适配器
只需要在这里转3,3v即可
其余接口
包括控制舵机的接口和红外接口
软件设计
协议
功能 | esp32串口输出代码 | 物联网平台代码 | stm32动作 |
---|---|---|---|
主屋开 | 0xac0000 | H_ON | 操作舵机开灯 |
主屋关 | 0xac00ff | H_OFF | 操作舵机关灯 |
北屋开 | 0xac1100 | N_ON | 将串口代码通过RF433发射 |
北屋关 | 0xac11ff | N_OFF | 将串口代码通过RF433发射 |
南屋开 | 0xac2200 | S_ON | 将串口代码通过RF433发射 |
南屋关 | 0xac22ff | S_OFF | 将串口代码通过RF433发射 |
西屋开 | 0xac3300 | W_ON | 将串口代码通过RF433发射 |
西屋关 | 0xac33ff | W_OFF | 将串口代码通过RF433发射 |
全开 | 0xacff00 | ALL_ON | 分别发送各屋开灯代码 |
全关 | 0xacffff | ALL_OFF | 分别发送各屋关灯代码 |
esp32
环境
这里使用Arduino框架
请自行查询arduino的环境搭建
这里使用了一个第三方库(PubSubClient)来建立MQTT连接
这里提供zip文件(成品的github连接中),自行导入即可
如下图选择添加zip库,添加即可
wifi连接
将esp32设置为sta模式,接入其他wifi
根据wifi的ssid和password接入
如下所示
const char *ssid = "";
const char *password = "";
void setupWifi() {
WiFi.mode(WIFI_STA);
esp_wifi_set_mac(WIFI_IF_STA, newMACAddress);
Serial.println(WiFi.macAddress());
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
MQTT
云平台设置
使用的是巴法云的mqtt服务
巴法云设置,传送门
一般的MQTT有这么几个要素
设备id(mqtt_devid),产品id(mqtt_pubid),密钥信息(mqtt_password),主题名(mqtt_topic)
在巴法云中只用了设备ID和主题名(产品ID和密钥为空即可)
#define mqtt_devid "********" //设备ID
#define mqtt_pubid " " //产品ID
#define mqtt_password " " //鉴权信息
const char *mqtt_topic = "ESP32HomeRFLight2";
设备ID是巴法云控制台中的这个红圈里的私钥
主题则是自己建立的主题名
程序
连接函数
巴法云的MQTT连接地址是(bemfa.com),端口号是 9501
MQTT连接,传入链接地址端口,在传入设备信息,主题信息即可
注意设置回调函数(接收到信息时触发)(client.setCallback(callback)😉
void clientReconnect() {
while (!client.connected()) //再重连客户端
{
delay(3000);
client.setServer(mqtt_server, 9501); //设置客户端连接的服务器
client.connect(mqtt_devid, mqtt_pubid, mqtt_password); //客户端连接到指定的产品的指定设备.同时输入鉴权信息
client.subscribe(mqtt_topic);
client.setCallback(callback); //设置好客户端收到信息是的回调
Serial.println("reconnect MQTT...");
if (client.connect(mqtt_devid, mqtt_pubid, mqtt_password)) {
Serial.println("connected");
} else {
Serial.println("failed");
Serial.println(client.state());
}
}
}
//收到主题下发的回调, 注意这个回调要实现三个形参 1:topic 主题, 2: payload: 传递过来的信息 3: length: 长度
void callback(char *topic, byte *payload, unsigned int length)
{
}
在回调函数中需要对信息处理
首先把主题信息和数据信息提取出来,转化成string格式(可以用内置函数比较)
String topic_zj = "";
String data_zj = "";
for (size_t i = 0; i < strlen(topic); i++) {
topic_zj += (char)topic[i];
}
for (size_t i = 0; i < length; i++) {
data_zj += (char)payload[i];
}
之后根据协议做比较即可
if (!topic_zj.compareTo(mqtt_topic)) {
Serial.write(0xac);
if (!data_zj.compareTo("H_ON")) {
Serial.write(0x00);
Serial.write(0x00);
} else if (!data_zj.compareTo("H_OFF")) {
Serial.write(0x00);
Serial.write(0xFF);
} else if (!data_zj.compareTo("N_ON")) {
Serial.write(0x11);
Serial.write(0x00);
} else if (!data_zj.compareTo("N_OFF")) {
Serial.write(0x11);
Serial.write(0xFF);
} else if (!data_zj.compareTo("S_ON")) {
Serial.write(0x22);
Serial.write(0x00);
} else if (!data_zj.compareTo("S_OFF")) {
Serial.write(0x22);
Serial.write(0xFF);
} else if (!data_zj.compareTo("W_ON")) {
Serial.write(0x33);
Serial.write(0x00);
} else if (!data_zj.compareTo("W_OFF")) {
Serial.write(0x33);
Serial.write(0xFF);
} else if (!data_zj.compareTo("ALL_ON")) {
Serial.write(0xFF);
Serial.write(0x00);
} else if (!data_zj.compareTo("ALL_OFF")) {
Serial.write(0xFF);
Serial.write(0xFF);
}
}
esp32总程序
#include <PubSubClient.h>
#include <WiFi.h>
#include <esp_wifi.h>
uint8_t newMACAddress[] = { 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf };
const char *ssid = "K2FeO4";
const char *password = "lxz123ac";
const char *mqtt_server = "bemfa.com"; //onenet 的 IP地址
#define mqtt_devid "858e79b6f49d47fb90f2bd9f9ca2d331" //设备ID
#define mqtt_pubid " " //产品ID
#define mqtt_password " " //鉴权信息
const char *mqtt_topic = "ESP32HomeRFLight2";
WiFiClient espClient; //创建一个WIFI连接客户端
PubSubClient client(espClient); // 创建一个PubSub客户端, 传入创建的WIFI客户端
char msg_buf[200]; //发送信息缓冲区
void setupWifi() {
WiFi.mode(WIFI_STA);
esp_wifi_set_mac(WIFI_IF_STA, newMACAddress);
Serial.println(WiFi.macAddress());
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
//收到主题下发的回调, 注意这个回调要实现三个形参 1:topic 主题, 2: payload: 传递过来的信息 3: length: 长度
void callback(char *topic, byte *payload, unsigned int length) {
String topic_zj = "";
String data_zj = "";
for (size_t i = 0; i < strlen(topic); i++) {
topic_zj += (char)topic[i];
}
for (size_t i = 0; i < length; i++) {
data_zj += (char)payload[i];
}
if (!topic_zj.compareTo(mqtt_topic)) {
Serial.write(0xac);
if (!data_zj.compareTo("H_ON")) {
Serial.write(0x00);
Serial.write(0x00);
} else if (!data_zj.compareTo("H_OFF")) {
Serial.write(0x00);
Serial.write(0xFF);
} else if (!data_zj.compareTo("N_ON")) {
Serial.write(0x11);
Serial.write(0x00);
} else if (!data_zj.compareTo("N_OFF")) {
Serial.write(0x11);
Serial.write(0xFF);
} else if (!data_zj.compareTo("S_ON")) {
Serial.write(0x22);
Serial.write(0x00);
} else if (!data_zj.compareTo("S_OFF")) {
Serial.write(0x22);
Serial.write(0xFF);
} else if (!data_zj.compareTo("W_ON")) {
Serial.write(0x33);
Serial.write(0x00);
} else if (!data_zj.compareTo("W_OFF")) {
Serial.write(0x33);
Serial.write(0xFF);
} else if (!data_zj.compareTo("ALL_ON")) {
Serial.write(0xFF);
Serial.write(0x00);
} else if (!data_zj.compareTo("ALL_OFF")) {
Serial.write(0xFF);
Serial.write(0xFF);
}
}
}
void sendTempAndHumi() {
if (client.connected()) {
Serial.print("public message:");
client.publish("$dp", (uint8_t *)msg_buf, 3); //发送数据到主题$dp
}
}
//重连函数, 如果客户端断线,可以通过此函数重连
void clientReconnect() {
while (!client.connected()) //再重连客户端
{
delay(3000);
client.setServer(mqtt_server, 9501); //设置客户端连接的服务器
client.connect(mqtt_devid, mqtt_pubid, mqtt_password); //客户端连接到指定的产品的指定设备.同时输入鉴权信息
client.subscribe(mqtt_topic);
client.setCallback(callback); //设置好客户端收到信息是的回调
Serial.println("reconnect MQTT...");
if (client.connect(mqtt_devid, mqtt_pubid, mqtt_password)) {
Serial.println("connected");
} else {
Serial.println("failed");
Serial.println(client.state());
}
}
}
void setup() {
// put your setup code here, to run once:
// rtc_wdt_protect_off();
// rtc_wdt_enable();
// rtc_wdt_feed();
// rtc_wdt_set_time(RTC_WDT_STAGE0, 8000);
Serial.begin(115200);
setupWifi(); //调用函数连接WIFI
delay(2000);
clientReconnect();
}
void loop() {
// put your main code here, to run repeatedly:
if (!WiFi.isConnected()) //先看WIFI是否还在连接
{
setupWifi();
}
if (!client.connected()) //如果客户端没连接ONENET, 重新连接
{
clientReconnect();
}
client.loop(); //客户端循环检测
}
stm32
相关链接
用到了之前写的几个库
舵机驱动
NEC
RF433
舵机关灯思路
设计思路
需要:
接收RF433信号/红外,根据解码的信号控制舵机
接收来自串口信号,根据信号发送RF433或控制舵机
需要用到的外设及功能
定时器(3个)(红外433解码,合用一个,舵机控制一个,RF433发送的时序控制一个)
串口1个(与esp32通信)
GPIO(5个,后续详细说)
硬件看门狗
HAL初始化
定时器1
用作 红外和RF433解码的计时
需要分频后1us为周期,最大计数无需改变,开启溢出中断
定时器2
用作舵机控制的PWM生成
每隔20us触发一次中断
定时器3
用作RF433信号发射时的计时
分频1us,计数值默认最大即可,开中断
GPIO
LED:用作指示灯,推挽输出即可
GPIO
RF433输出
需要配置为推挽输出(开漏不行)
GPIO
舵机控制信号
配置为开漏浮空(外部接上拉电阻到5V),配置为最高等级(避免复位时让电机出现误动作)
GPIO
RF433输入
配置为边沿中断模式
GPIO
红外输入
配置为下降沿中断模式
注意开两个外部中断的中断设置
串口
开启中断,后面使用空闲中断来接收数据
硬件看门狗
32分频,溢出值4000
每(32/40k*4000=3.2s)触发一次
本程序目的是让程序每3.2s重启一次,因此只在需要操作舵机时喂狗,主循环无喂狗
程序
分为
- 红外和RF433的解码共用了一个定时器,需要做时序控制,让红外有输入时屏蔽RF433,避免出现问题(RF会有幻听,会阶段性输入高低电平)
- 接收来自RF433,串口,红外的数据,在主循环里根据不同的指令做发射信号/控制舵机的动作
中断回调函数(舵机/红外/RF433的驱动)
保存串口数据
调用之前的库文件
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim1)
{
}
else if (htim == &htim2)
{
if (M_EN == 1)
Steering_Engine_Action();
else
HAL_GPIO_WritePin(Steering_Engine_GPIOx, Steering_Engine_GPIO_Pin, GPIO_PIN_SET);
}
else if (htim == &htim3)
{
}
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_2) // 433
{
if (Input_EN == 1)
if (IR_NEC_Read_ins == 0)
if (RF_READ_OK == 0)
RF_Read_Decode();
}
else if (GPIO_Pin == GPIO_PIN_3) // IR
{
if (Input_EN == 1)
if (IR_NEC_Read_OK == 0)
IR_NEC_Read_Decode(air);
}
}
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart == &huart1)
{
Uart1_OK = 1;
HAL_UART_Transmit(&huart2, Uart1_Buf, Size, 0xfff);
}
}
开关灯控制
详情原理见,传送门
void OPEN()
{
M_EN = 1;
HAL_IWDG_Refresh(&hiwdg);
Steering_Engine_360(0, 30);
HAL_Delay(500);
HAL_IWDG_Refresh(&hiwdg);
Steering_Engine_360(1, 40);
HAL_Delay(80);
HAL_IWDG_Refresh(&hiwdg);
Steering_Engine_Stop();
M_EN = 0;
}
void CLOSE()
{
M_EN = 1;
HAL_IWDG_Refresh(&hiwdg);
Steering_Engine_360(1, 30);
HAL_Delay(500);
HAL_IWDG_Refresh(&hiwdg);
Steering_Engine_360(0, 30);
HAL_Delay(80);
HAL_IWDG_Refresh(&hiwdg);
Steering_Engine_Stop();
M_EN = 0;
}
主循环内容,根据传入的信息判断
if (RF_READ_OK == 1)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
Input_EN = 0;
if (RF_READ_data[0] == 0xac && RF_READ_data[1] == 0x01 && RF_READ_data[2] == 0x00)
OPEN();
else if (RF_READ_data[0] == 0xac && RF_READ_data[1] == 0x01 && RF_READ_data[2] == 0xff)
CLOSE();
HAL_IWDG_Refresh(&hiwdg);
RF_READ_data[0] = 0;
RF_READ_data[1] = 0;
RF_READ_data[2] = 0;
RF_READ_OK = 0;
Input_EN = 1;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
}
if (IR_NEC_Read_OK == 1)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
Input_EN = 0;
// printf("%02X%02X%02X\r\n", RF_READ_data[0], RF_READ_data[1], RF_READ_data[2]);
if (IR_NEC_Read_Dat[0] == 0x4D && IR_NEC_Read_Dat[1] == 0xb2 && IR_NEC_Read_Dat[2] == 0xa3 && IR_NEC_Read_Dat[3] == 0x5C)
OPEN();
else if (IR_NEC_Read_Dat[0] == 0x4D && IR_NEC_Read_Dat[1] == 0xb2 && IR_NEC_Read_Dat[2] == 0x59 && IR_NEC_Read_Dat[3] == 0xa6)
CLOSE();
if (IR_NEC_Read_Dat[0] == 0x84 && IR_NEC_Read_Dat[1] == 0xff && IR_NEC_Read_Dat[2] == 0x81 && IR_NEC_Read_Dat[3] == 0x7e)
OPEN();
else if (IR_NEC_Read_Dat[0] == 0x84 && IR_NEC_Read_Dat[1] == 0xff && IR_NEC_Read_Dat[2] == 0x01 && IR_NEC_Read_Dat[3] == 0xfe)
CLOSE();
HAL_IWDG_Refresh(&hiwdg);
IR_NEC_Read_Dat[0] = 0;
IR_NEC_Read_Dat[1] = 0;
IR_NEC_Read_Dat[2] = 0;
IR_NEC_Read_Dat[3] = 0;
IR_NEC_Read_OK = 0;
Input_EN = 1;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
}
if (Uart1_OK == 1)
{
Input_EN = 0;
HAL_IWDG_Refresh(&hiwdg);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
if (Uart1_Buf[0] == 0xac && Uart1_Buf[1] != 0x00 && Uart1_Buf[1] != 0xFF)
{
for (int i = 0; i < 3; i++)
RF433_Buf[i] = Uart1_Buf[i];
RF_Write_Send(RF433_Buf);
}
else if (Uart1_Buf[0] == 0xac && Uart1_Buf[1] == 0x00)
{
if (Uart1_Buf[2] == 0x00)
OPEN();
else if (Uart1_Buf[2] == 0xff)
CLOSE();
}
if (Uart1_Buf[0] == 0xac && Uart1_Buf[1] == 0xff)
{
// HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
if (Uart1_Buf[2] == 0x00)
{
RF_Write_Send(Data_N_Open);
HAL_IWDG_Refresh(&hiwdg);
HAL_Delay(300);
HAL_IWDG_Refresh(&hiwdg);
RF_Write_Send(Data_S_Open);
OPEN();
}
else if (Uart1_Buf[2] == 0xff)
{
RF_Write_Send(Data_N_Close);
HAL_IWDG_Refresh(&hiwdg);
HAL_Delay(300);
HAL_IWDG_Refresh(&hiwdg);
RF_Write_Send(Data_S_Close);
HAL_IWDG_Refresh(&hiwdg);
CLOSE();
}
}
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
Input_EN = 1;
Uart1_OK = 0;
HAL_UARTEx_ReceiveToIdle_IT(&huart1, Uart1_Buf, 20);
}
注意,串口使用了空闲中断模式,鉴别不同数据帧
成品
另外app开发很简单,百度凑凑就行了,源码同样在GitHub上,请自行查看即可
GitHub