该套系统的工作原理为:首先,用户需要先把WIFI密码刷入STM32的ROM中,这样做而不是运行后修改是以便安全,然后,在宿舍服务器下,部署基于CQHTTP与NoneBot的docker-compose的QQ机器人(附带智能门锁通信Python插件),注:没有QQ机器人也可以开发APP进行局域网UDP通信,若需要WAN通信,则需要在路由器上进行端口映射或者使用FRP。上电QQ机器人与智能门锁系统,智能门锁系统LED红绿灯交替闪烁,代表正在连接WIFI,连接成功后绿灯亮起1s随后亮灯都熄灭以节约电量,此时系统上只有电源模块的贴片LED常亮,表征正常运行。
在智能门锁连接到WIFI后,需要配置路由器DHCP,给它分配静态IP,并修改机器人的门锁配置,配置IP地址,以便长时间进行通讯。注:若WIFI断开,则系统会重新进入连接状态,直到重连成功。
随后,机器人会对私聊进行监听,并在用户发出指令后,检查用户是否属于可通行QQ,若不属于,则机器人拒绝开门,若属于,则QQ机器人会向智能门锁IP地址发送UDP数据包以通知智能门锁进行开门,智能门锁收到后,随即将发出PWM方波控制舵机移动到指定位置以物理打开门锁并使蜂鸣器发出声音,提醒开门成功,等待5秒后,舵机释放,门锁在弹簧作用下复原,智能门锁重新等待UDP数据造成的中断以等待下一次开门,QQ机器人继续监听用户的私聊信息。
一、创建STM32工程
1. 安装STM32CubeIDE
- 从ST官网下载并安装IDE,选择对应操作系统的版本。
2. 新建工程
- 选择芯片型号STM32F103C8Tx
。
- 配置时钟树:启用HSE(外部高速晶振),设置主频为72MHz。
- 生成代码框架:勾选HAL库支持,自动生成初始化代码。
Pinout & Configuration
Clock Configuration

二、根据功能配置芯片引脚
具体配置在上面Pinout图中已有截图。
三、组件选择、组件连接、代码编写与功能测试
组件方面,我选择了:STM32F103最小开发板、有源蜂鸣器(低电平触发)、ESP8266模块、LED灯*2、开关*1、5V->3.3V电源模块、舵机、5cm*7cm焊接板、5V可充电电池。
组件连接的话,可以先使用杜邦线进行暂时连接,在测试好代码后就可以焊接了。
对于具体模块的功能与引脚连接,网上容易查到,故本文在此不再赘述。
我们编写固件代码,先配置一些全局常量,用于ESP8266模块通信。
/* USER CODE BEGIN PD */
char WIFI_client_mode[] ="AT+CWMODE=1\r\n";
char LAN_connect[] ="AT+CWJAP=\"wifi_ssid\",\"wifi_password\"\r\n";
char UDP_server_connect[] ="AT+CIPSTART=\"UDP\",\"192.168.1.214\",19730,8080,0\r\n";
char WIFI_force_mode[] ="AT+CIPMODE=1\r\n";
char datatransport_start[] ="AT+CIPSEND\r\n";
char test_at[] ="AT\r\n";
char reset[] ="AT+RST\r\n";
/* USER CODE END PD */
再定义一些全局变量,用来表示状态、充当缓冲区。
/* USER CODE BEGIN PV */
uint8_t buf=0;
char ch[200] ;
uint16_t UART1_RX_STA=0;
int LAN_mark=0;
int other_land_mark=0;
int suceed_mark=0;
int error=1;
uint32_t WIFIState = -1;
/* USER CODE END PV */
定义后,我们用 HAL_UART_RxCpltCallback 来接收 ESP8266 的数据。
/* USER CODE BEGIN 0 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance != USART2) return;
if((UART1_RX_STA & 0x8000)==0){
if(UART1_RX_STA & 0x4000){
if(buf=='\n'){
UART1_RX_STA |= 0x8000;
if(!strcmp(ch,"WIFI GOT IP")){
WIFIState = 1;
}
if(!strcmp(ch,"+IPD,6:OPEN")){
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
DoorLock(false);
//HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
//HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(VOICE_OUT_GPIO_Port, VOICE_OUT_Pin, GPIO_PIN_RESET);
HAL_Delay(50);
HAL_GPIO_WritePin(VOICE_OUT_GPIO_Port, VOICE_OUT_Pin, GPIO_PIN_SET);
HAL_Delay(30);
HAL_GPIO_WritePin(VOICE_OUT_GPIO_Port, VOICE_OUT_Pin, GPIO_PIN_RESET);
HAL_Delay(50);
HAL_GPIO_WritePin(VOICE_OUT_GPIO_Port, VOICE_OUT_Pin, GPIO_PIN_SET);
HAL_Delay(3000);
DoorLock(true);
HAL_GPIO_WritePin(VOICE_OUT_GPIO_Port, VOICE_OUT_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(VOICE_OUT_GPIO_Port, VOICE_OUT_Pin, GPIO_PIN_RESET);
HAL_Delay(50);
HAL_GPIO_WritePin(VOICE_OUT_GPIO_Port, VOICE_OUT_Pin, GPIO_PIN_SET);
//HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
//HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_SET);
HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_3);
}
memset(ch,'\0',200);
UART1_RX_STA=0;
}else{
UART1_RX_STA=0;
}
}else{
if(buf==0x0d){
UART1_RX_STA|=0x4000;
}else{
ch[UART1_RX_STA&0x3fff]=buf;
UART1_RX_STA++;
if(UART1_RX_STA>199)
UART1_RX_STA = 0;
}
}
}
// 等待下次接收
HAL_UART_Receive_IT(&huart2, &buf,1);
}
/* USER CODE END 0 */
当收到
+IPD,6:OPEN
这个的时候,我们就可以执行开门操作了。
我这里是采用了一个自己写的函数 DoorLock 来控制门的状态。
/* USER CODE BEGIN 0 */
void DoorLock(bool lock)
{
float temp;
uint8_t angle = 0;
if(lock)
{
angle = 40;
}
else
{
angle = 150;
}
temp =angle/9*10+50 ;
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, (uint16_t )temp);
}
/* USER CODE END 0 */
这个函数用于在锁门时和开门时将舵机转动到不同的角度上,而由于舵机拉着门锁,舵机固定在门板上,所以我们就可以达到拉动门闩的效果,以此来开门。
除此之外,在开门的时候会让蜂鸣器发出声响来进行提示。
最后我们来看一下 main 函数,初始化了所有要用的东西。
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM1_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);
WIFIState = 0;
HAL_NVIC_SetPriority(SysTick_IRQn,0,0);
HAL_UART_Receive_IT(&huart2, &buf, 1);
HAL_UART_Transmit_IT(&huart2, (const uint8_t*)reset, strlen(reset));
HAL_Delay(1000);
HAL_UART_Transmit_IT(&huart2, (const uint8_t*)WIFI_client_mode, strlen(WIFI_client_mode));
HAL_UART_Transmit_IT(&huart2, (const uint8_t*)LAN_connect, strlen(LAN_connect));
while(WIFIState != 1)
{
HAL_Delay(200);
HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin);
HAL_Delay(200);
HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin);
}
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
HAL_UART_Transmit_IT(&huart2, (const uint8_t*)UDP_server_connect, strlen(UDP_server_connect));
HAL_UART_Transmit_IT(&huart2, (const uint8_t*)WIFI_force_mode, strlen(WIFI_force_mode));
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_SET);
HAL_Delay(1000);
WIFIState = 0;
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
while(WIFIState != 1)
{
HAL_Delay(10000);
}
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
HAL_UART_Transmit_IT(&huart2, (const uint8_t*)UDP_server_connect, strlen(UDP_server_connect));
HAL_UART_Transmit_IT(&huart2, (const uint8_t*)WIFI_force_mode, strlen(WIFI_force_mode));
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_RESET);
HAL_Delay(1000);
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin, GPIO_PIN_SET);
HAL_Delay(1000);
WIFIState = 0;
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
里面有 WiFi 连接流程与重新连接的流程,防止系统掉线。
四、焊接并组装到门上
由于我是焊接新手,背后除了VIN和GND两根线外,其他均用铜线进行跳线连接,再使用松香进行保护。
注:板子我焊过两次,这是第一次焊的,非常丑陋,采用了带皮导线进行跳线,事实证明这非常不稳定,不要这样焊接。
有实力的可以自己打PCB。
在门板上我采用了钉子直接把舵机钉在门上,然后与门锁系统进行连接,板子使用双面胶与一个塑料袋,塑料袋装着板子托在门板上,旁边再用一个同样的方法兜住一个电池,可以说是非常的“简陋”了,升级版的在下面的3D打印外壳中。
五、设计外壳并进行3D打印
由于塑料袋兜住的不稳定性,我找到了一个会设计3D打印的同学,请他设计了一个盒子。
实物图(这样看起来确实好了一些)
六、与QQ机器人进行联动
流程图
NoneBot 插件
我们使用UDP来进行消息发送,因为UDP不需要进行连接建立,而如果用TCP进行连接,可能会因为网络差而断线,重连还是会比较麻烦。
最后,为了安全,必须在OpenWRT防火墙进行相应设置,比如我采用OpenVPN组网进行通信,只能允许OpenVPN接口与门锁系统通信。
七、结语
本项目的开发完整实现了基于STM32F103C8的智能宿舍门锁系统,从硬件选型到软件设计,从机械装配到物联网联动,逐步攻克了嵌入式开发与系统集成的核心挑战。通过STM32CubeIDE的高效开发、ESP8266的稳定通信以及舵机的精准控制,成功构建了一个支持远程指令触发、状态反馈和自动闭锁的智能化门禁解决方案。3D打印外壳的设计与QQ机器人的联动,进一步提升了系统的实用性和用户体验。
注:代码完全开源。如有意愿获得相关代码,请您联系我的邮箱。