该套系统的工作原理为:首先,用户需要先把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

二、根据功能配置芯片引脚

外设模块

引脚

功能模式

ESP8266 (UART)

PA2(TX)

USART2_TX

PA3(RX)

USART2_RX

舵机 (PWM)

PA10

TIM1_CH3 (PWM输出)

蜂鸣器

PA4

VOICE_OUT GPIO输出

LED指示灯(红/绿)

PA5 / PA1

GPIO输出

具体配置在上面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机器人的联动,进一步提升了系统的实用性和用户体验。

注:代码完全开源。如有意愿获得相关代码,请您联系我的邮箱。