本次通过上位机蓝牙控制LED灯的亮度,并通过OLED显示。下位机收集传感器采集到的温度、湿度数据,然后通过蓝牙发送到上位机显示。(感谢视频中老师的细致讲解)
一、硬件介绍。
这里使用SHT30作为温湿度传感器,使用I2C与NUCEO-WBA55CG相连接;使用了一块微雪的1.51寸透明OLED屏幕,用来展示PWM的信息。这块OLED屏幕也是使用I2C驱动,透明的OLED看上去科技感很足。
二、硬件连接。
CN4 | sht30 | OLED | ||||||||||||
2 | GPIO_2 | 1 | GPIO_26 | 2 | GPIO_25 | |||||||||
4 | GPIO_4 | 3 | GPIO_28 | PB2 | D15 | SCL | 黄 | 黄 | 4 | GPIO_27 | ||||
6 | 5V | 5 | GPIO_29 | PB1 | D14 | SDA | 绿 | 绿 | 6 | GPIO_30 | ||||
8 | GND | 7 | AVDD | AVDD | 8 | 5V | ||||||||
10 | 9 | GND | 10 | GPIO_32 | ||||||||||
12 | IOREF | 11 | GPIO_31 | PB4(LD1) | D13 | SCK | 12 | GPIO_35 | ||||||
14 | NRST | 13 | GPIO_33 | PB3 | D12 | MISO | 14 | GPIO_36 | ||||||
16 | 3V3 | 15 | GPIO_34 | PA15 | D11 | MOSI | 16 | GPIO_38 | ||||||
18 | 5V | 17 | GPIO_37 | PA12 | D10 | SS | 橙 CS | 18 | GPIO_40 | |||||
20 | GND | 19 | GPIO_39 | PA9 | D9 | 20 | GND | |||||||
22 | GND | 21 | GPIO_41 | PA15/PA11 | D8 | 22 | GPIO_43 | |||||||
24 | VIN | 23 | GPIO_42 | PB9/PC13 | D7 | 24 | GPIO_45 | |||||||
26 | GPIO_7 | 25 | GPIO_44 | PB0 | D6 | 白 RST | 26 | GPIO_46 | ||||||
28 | GPIO_11 | PA7 | A0 | 27 | GPIO_47 | PB14 | D5 | 绿 DC | 28 | GPIO_48 | ||||
30 | GPIO_12 | PA6 | A1 | TIM2_CH4 | PWM输出 | 29 | GPIO_49 | PB13 | D4 | 30 | GPIO_51 | |||
32 | GPIO_17 | PA2 | A2 | 31 | GPIO_50 | PB6(B2) | D3 | 32 | GND | |||||
34 | GPIO_18 | PA1 | A3 | 33 | GPIO_52 | PB7(B3) | D2 | 34 | GPIO_53 | |||||
36 | GPIO_21 | PA5/PA8 | A4 | SDA | 35 | GPIO_54 | PB5 | D1 | 36 | GPIO_56 | ||||
38 | GPIO_22 | PA0/PA5 | A5 | SCL | 37 | GPIO_55 | PA10 | D0 | 38 | GPIO_57 |
线接的比较乱,OLED和SHT30都是走I2C接口,其中OLED还有rst、dc、cs三条线,测试下来dc,cs两条线感觉是没有用到,可以悬空的。OLED也有SPI的接线方式,但是尝试过使用SPI去驱动OLED屏幕,在蓝牙的例程中,系统会出现卡死,暂时没能解决,所以最终选择了使用I2C方式驱动。
三、下位机编程。
参考着老师的视频,使用例程来作为基础工程,这里我使用了WBA1.5.0的例程。例程初次编译,会有一个报错,调整一下对应文件路径就可以解决了。
使用STM32CUBEMX,来调整硬件基础设置。这里需要使用PWM来控制LED灯的亮度,开启TIM2的CH4的PWM功能。对应的管脚PA6。调整PWM输出评论为1KHz。
配置I2C1,用来驱动SHT30和OLED。SHT30上送内容有温度和湿度两个数据,这里我使用float类型,每个数据长度就是4个字节,一共为8个字节。所以这里需要调整下位机蓝牙上送字节长度。如图所示:
接下来编程部分,添加sht3x驱动文件,添加oled驱动文件,也有尝试了使用U8g2,来驱动OLED,单独驱动没问题,放到蓝牙工程中就不能正常运行,不明白为啥。
#include "sht3x.h" #include <assert.h> /** * Registers addresses. */ typedef enum { SHT3X_COMMAND_MEASURE_HIGHREP_STRETCH = 0x2c06, SHT3X_COMMAND_CLEAR_STATUS = 0x3041, SHT3X_COMMAND_SOFT_RESET = 0x30A2, SHT3X_COMMAND_HEATER_ENABLE = 0x306d, SHT3X_COMMAND_HEATER_DISABLE = 0x3066, SHT3X_COMMAND_READ_STATUS = 0xf32d, SHT3X_COMMAND_FETCH_DATA = 0xe000, SHT3X_COMMAND_MEASURE_HIGHREP_10HZ = 0x2737, SHT3X_COMMAND_MEASURE_LOWREP_10HZ = 0x272a } sht3x_command_t; static uint8_t calculate_crc(const uint8_t *data, size_t length) { uint8_t crc = 0xff; for (size_t i = 0; i < length; i++) { crc ^= data[i]; for (size_t j = 0; j < 8; j++) { if ((crc & 0x80u) != 0) { crc = (uint8_t)((uint8_t)(crc << 1u) ^ 0x31u); } else { crc <<= 1u; } } } return crc; } static bool sht3x_send_command(sht3x_handle_t *handle, sht3x_command_t command) { uint8_t command_buffer[2] = {(command & 0xff00u) >> 8u, command & 0xffu}; if (HAL_I2C_Master_Transmit(handle->i2c_handle, handle->device_address << 1u, command_buffer, sizeof(command_buffer), SHT3X_I2C_TIMEOUT) != HAL_OK) { return false; } return true; } static uint16_t uint8_to_uint16(uint8_t msb, uint8_t lsb) { return (uint16_t)((uint16_t)msb << 8u) | lsb; } bool sht3x_init(sht3x_handle_t *handle) { assert(handle->i2c_handle->Init.NoStretchMode == I2C_NOSTRETCH_DISABLE); // TODO: Assert i2c frequency is not too high uint8_t status_reg_and_checksum[3]; if (HAL_I2C_Mem_Read(handle->i2c_handle, handle->device_address << 1u, SHT3X_COMMAND_READ_STATUS, 2, (uint8_t*)&status_reg_and_checksum, sizeof(status_reg_and_checksum), SHT3X_I2C_TIMEOUT) != HAL_OK) { return false; } uint8_t calculated_crc = calculate_crc(status_reg_and_checksum, 2); if (calculated_crc != status_reg_and_checksum[2]) { return false; } return true; } bool sht3x_read_temperature_and_humidity(sht3x_handle_t *handle, float *temperature, float *humidity) { sht3x_send_command(handle, SHT3X_COMMAND_MEASURE_HIGHREP_STRETCH); HAL_Delay(1); uint8_t buffer[6]; if (HAL_I2C_Master_Receive(handle->i2c_handle, handle->device_address << 1u, buffer, sizeof(buffer), SHT3X_I2C_TIMEOUT) != HAL_OK) { return false; } uint8_t temperature_crc = calculate_crc(buffer, 2); uint8_t humidity_crc = calculate_crc(buffer + 3, 2); if (temperature_crc != buffer[2] || humidity_crc != buffer[5]) { return false; } uint16_t temperature_raw = uint8_to_uint16(buffer[0], buffer[1]); uint16_t humidity_raw = uint8_to_uint16(buffer[3], buffer[4]); *temperature = -45.0f + 175.0f * (float)temperature_raw / 65535.0f; *humidity = 100.0f * (float)humidity_raw / 65535.0f; return true; } bool sht3x_set_header_enable(sht3x_handle_t *handle, bool enable) { if (enable) { return sht3x_send_command(handle, SHT3X_COMMAND_HEATER_ENABLE); } else { return sht3x_send_command(handle, SHT3X_COMMAND_HEATER_DISABLE); } }
/** * 基于硬件IIC通信的OLED驱动程序 * API完全打包好的直接选择你的硬件IIC口即可 * */ #include "main.h" #include "stdio.h" #include "oled_font.h" #include "oled.h" uint8_t OLED_Buffer[8][128]={0}; extern I2C_HandleTypeDef hi2c1; /** * @brief OLED写命令 * @param Command 要写入的命令 * @retval 无 */ void OLED_WriteCommand(uint8_t Command) { uint8_t *pData; pData = &Command; HAL_I2C_Mem_Write(&Hardware_IIC_No,0x78,0x00,I2C_MEMADD_SIZE_8BIT,pData,1,100); } /** * @brief OLED写数据 * @param Data 要写入的数据 * @retval 无 */ void OLED_WriteData(uint8_t Data) { uint8_t *pData; pData = &Data; HAL_I2C_Mem_Write(&Hardware_IIC_No,0x78,0x40,I2C_MEMADD_SIZE_8BIT,pData,1,100); } /** * @brief OLED设置光标位置 * @param Y 以左上角为原点,向下方向的坐标,范围:0~7 * @param X 以左上角为原点,向右方向的坐标,范围:0~127 * @retval 无 */ void OLED_SetCursor(uint8_t Y, uint8_t X) { OLED_WriteCommand(0xB0 | Y); //设置Y位置 OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置低4位 OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置高4位 } /** * @brief OLED清屏 * @param 无 * @retval 无 */ void OLED_Clear(void) { uint8_t i, j; for (j = 0; j < 8; j++) { OLED_SetCursor(j, 0); for(i = 0; i < 128; i++) { OLED_WriteData(0x00); } } } /** * @brief OLED显示一个字符 * @param Line 行位置,范围:1~4 * @param Column 列位置,范围:1~16 * @param Char 要显示的一个字符,范围:ASCII可见字符 * @retval 无 */ void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char) { uint8_t i; OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分 for (i = 0; i < 8; i++) { OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容 } OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分 for (i = 0; i < 8; i++) { OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容 } } /** * @brief OLED显示字符串 * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串,范围:ASCII可见字符 * @retval 无 */ void OLED_ShowString(uint8_t Line, uint8_t Column, char *String) { uint8_t i; for (i = 0; String[i] != '\0'; i++) { OLED_ShowChar(Line, Column + i, String[i]); } } /** * @brief OLED次方函数 * @retval 返回值等于X的Y次方 */ uint32_t OLED_Pow(uint32_t X, uint32_t Y) { uint32_t Result = 1; while (Y--) { Result *= X; } return Result; } /** * @brief OLED显示数字(十进制,正数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~4294967295 * @param Length 要显示数字的长度,范围:1~10 * @retval 无 */ void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length) { uint8_t i; for (i = 0; i < Length; i++) { OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0'); } } /** * @brief OLED显示数字(十进制,带符号数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:-2147483648~2147483647 * @param Length 要显示数字的长度,范围:1~10 * @retval 无 */ void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length) { uint8_t i; uint32_t Number1; if (Number >= 0) { OLED_ShowChar(Line, Column, '+'); Number1 = Number; } else { OLED_ShowChar(Line, Column, '-'); Number1 = -Number; } for (i = 0; i < Length; i++) { OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0'); } } /** * @brief OLED显示数字(十六进制,正数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~0xFFFFFFFF * @param Length 要显示数字的长度,范围:1~8 * @retval 无 */ void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length) { uint8_t i, SingleNumber; for (i = 0; i < Length; i++) { SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16; if (SingleNumber < 10) { OLED_ShowChar(Line, Column + i, SingleNumber + '0'); } else { OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A'); } } } /** * @brief OLED显示数字(二进制,正数) * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~1111 1111 1111 1111 * @param Length 要显示数字的长度,范围:1~16 * @retval 无 */ void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length) { uint8_t i; for (i = 0; i < Length; i++) { OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0'); } } /** * @brief OLED显示模拟量 * @param Line起始位置,范围0~3 * @param Colum 起始列位置,0~7 * @param CHN 汉字序列 */ void OLED_ShowChinese(uint8_t Line, uint8_t Column, uint8_t CHN) { uint8_t i; OLED_SetCursor(Line*2, Column*16); //设置光标位置在上半部分 for (i = 0; i < 16; i++) { OLED_WriteData(GBK[CHN][i]); //显示上半部分内容 } OLED_SetCursor((Line*2)+1,Column*16); //设置光标位置在下半部分 for (i = 0; i < 16; i++) { OLED_WriteData(GBK[CHN][i + 16]); //显示下半部分内容 } } /** * @brief OLED显示模拟量 * @param Pic第几张图片,范围0~3 * @param Colum 起始列位置,0~7 * */ void OLED_ShowPicture(uint8_t Pic) { uint8_t i,j; Pic=Pic*8; for(j=0;j<8;j++) { OLED_SetCursor(j,0);//开始显示 for (i = 0; i < 128; i++) { OLED_WriteData(Picture[j+Pic][i]); //发送数据 } } } /* * 快速显示照片 */ void OLED_FastShowPicture(uint8_t Pic) { uint8_t i,j; Pic=Pic*8; for(j=0;j<8;j++) { for (i = 0; i < 128; i++) { OLED_Buffer[j][i]=(Picture[j+Pic][i]); //发送数据 } } } /* * @brief 向缓冲区写入字符数据 * Line 1~4; * Column 1~16; */ void OLED_FastShowChar(uint8_t Line, uint8_t Column, char Char) { uint8_t i; for (i = 0; i < 8; i++) { OLED_Buffer[(Line- 1) * 2][(Column-1)*8+i] = OLED_F8x16[Char - ' '][i];//显示上半部分内容 } for (i = 0; i < 8; i++) { OLED_Buffer[((Line ) * 2) - 1][(Column-1)*8+i]= OLED_F8x16[Char - ' '][i+8]; //显示下半部分内容 } } /** * @brief 快速的OLED显示字符串 * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串,范围:ASCII可见字符 * @retval 无 */ void OLED_FastShowString(uint8_t Line, uint8_t Column, char *String) { uint8_t i; for (i = 0; String[i] != '\0'; i++) { OLED_FastShowChar(Line, Column + i, String[i]); } } /** * @brief 快速的OLED显示数字 * @param Line 起始行位置,范围:1~4 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的字数字,范围:数字,变量 * @param Length 数字长度 * @retval 无 */ void OLED_FastShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length) { uint8_t i; for (i = 0; i < Length; i++) { OLED_FastShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0'); } } /* * @brief 向OLED屏幕发送缓存区数据到OLED的0x40寄存器位置用以显示; */ void Refresh() { uint8_t j; uint8_t *pData; for(j=0;j<8;j++) { pData = &OLED_Buffer[j][0]; OLED_SetCursor(j,0);//开始显示 HAL_I2C_Mem_Write(&Hardware_IIC_No,0x78,0x40,I2C_MEMADD_SIZE_8BIT,pData,128,0xff); } } /** * @brief OLED初始化 * @param 无 * @retval 无 */ void OLED_Init(void) { OLED_CS_1; OLED_DC_1; OLED_RST_0; HAL_Delay(400); OLED_CS_0; OLED_DC_0; OLED_RST_1; HAL_Delay(400); OLED_WriteCommand(0xAE); //关闭显示 OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率 OLED_WriteCommand(0x80); OLED_WriteCommand(0xA8); //设置多路复用率 OLED_WriteCommand(0x3F); OLED_WriteCommand(0xD3); //设置显示偏移 OLED_WriteCommand(0x00); OLED_WriteCommand(0x40); //设置显示开始行 OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置 OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置 OLED_WriteCommand(0xDA); //设置COM引脚硬件配置 OLED_WriteCommand(0x12); OLED_WriteCommand(0x81); //设置对比度控制 OLED_WriteCommand(0xCF); OLED_WriteCommand(0xD9); //设置预充电周期 OLED_WriteCommand(0xF1); OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别 OLED_WriteCommand(0x30); OLED_WriteCommand(0xA4); //设置整个显示打开/关闭 OLED_WriteCommand(0xA6); //设置正常/倒转显示 OLED_WriteCommand(0x8D); //设置充电泵 OLED_WriteCommand(0x14); OLED_WriteCommand(0xAF); //开启显示 OLED_Clear(); //OLED清屏 }
在文件p2p_server_app.c中,找到初始化蓝牙的部分,在初始化蓝牙的同时,初始化自己的温湿度传感器和OLED。
void P2P_SERVER_APP_Init(void) { UNUSED(P2P_SERVER_APP_Context); P2P_SERVER_Init(); /* USER CODE BEGIN Service1_APP_Init */ UTIL_SEQ_RegTask(1U << CFG_TASK_SEND_NOTIF_ID, UTIL_SEQ_RFU, P2P_SERVER_Switch_c_SendNotification); if (!sht3x_init(&handle)) { LOG_INFO_APP("SHT3x access failed.\n\r"); } OLED_Init(); //OLED_ShowString(1, 1, "Hello World!!!");//显示字符串 /** * Initialize LedButton Service */ P2P_SERVER_APP_Context.Switch_c_Notification_Status = Switch_c_NOTIFICATION_OFF; P2P_SERVER_APP_LED_BUTTON_context_Init(); /* USER CODE END Service1_APP_Init */ return; }
上位机在启动后,连接上蓝牙,就会将PWM的值通过蓝牙传输给下位机,下位机在接收到上位机的请求后,设置PWM,并读取SHT30,并将温湿度信息,返还给上位机。
/* Functions Definition ------------------------------------------------------*/ void P2P_SERVER_Notification(P2P_SERVER_NotificationEvt_t *p_Notification) { /* USER CODE BEGIN Service1_Notification_1 */ char buf[6]; /* USER CODE END Service1_Notification_1 */ switch(p_Notification->EvtOpcode) { /* USER CODE BEGIN Service1_Notification_Service1_EvtOpcode */ /* USER CODE END Service1_Notification_Service1_EvtOpcode */ case P2P_SERVER_LED_C_READ_EVT: /* USER CODE BEGIN Service1Char1_READ_EVT */ /* USER CODE END Service1Char1_READ_EVT */ break; case P2P_SERVER_LED_C_WRITE_NO_RESP_EVT: /* USER CODE BEGIN Service1Char1_WRITE_NO_RESP_EVT */ LOG_INFO_APP("Receive Message =%d %d\n",p_Notification->DataTransfered.p_Payload[0],p_Notification->DataTransfered.p_Payload[1]); //从蓝牙接收到消息,pwm输出 __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, p_Notification->DataTransfered.p_Payload[0]*10); // sprintf(buf,"%3d%%",p_Notification->DataTransfered.p_Payload[0]+1); OLED_ShowString( 2, 6,buf); P2P_SERVER_Switch_c_SendNotification(); /* USER CODE END Service1Char1_WRITE_NO_RESP_EVT */ break; case P2P_SERVER_SWITCH_C_NOTIFY_ENABLED_EVT: /* USER CODE BEGIN Service1Char2_NOTIFY_ENABLED_EVT */ P2P_SERVER_APP_Context.Switch_c_Notification_Status = Switch_c_NOTIFICATION_ON; LOG_INFO_APP("-- P2P APPLICATION SERVER : NOTIFICATION ENABLED\n"); LOG_INFO_APP(" \n\r"); /* USER CODE END Service1Char2_NOTIFY_ENABLED_EVT */ break; case P2P_SERVER_SWITCH_C_NOTIFY_DISABLED_EVT: /* USER CODE BEGIN Service1Char2_NOTIFY_DISABLED_EVT */ P2P_SERVER_APP_Context.Switch_c_Notification_Status = Switch_c_NOTIFICATION_OFF; LOG_INFO_APP("-- P2P APPLICATION SERVER : NOTIFICATION DISABLED\n"); LOG_INFO_APP(" \n\r"); /* USER CODE END Service1Char2_NOTIFY_DISABLED_EVT */ break; default: /* USER CODE BEGIN Service1_Notification_default */ /* USER CODE END Service1_Notification_default */ break; } /* USER CODE BEGIN Service1_Notification_2 */ /* USER CODE END Service1_Notification_2 */ return; }
四、上位机编程。
上位机,这里使用的是python+pyqt。参考着老师的程序,写个个简单的界面。代码没有彻底完成,断开蓝牙的功能还没有实现。
#!/usr/bin/env python # -*- coding:utf-8 -*- # @FileName :bt_pwm_light_mainwin.py # @Time :2024/12/24 15:25 # @Author :aramy import sys from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import QMainWindow, QApplication from UI.btappwin import Ui_MainWindow from bt_tool.bluetoothreceicer import BluetoothReceiver from bt_tool.bluetoothscanner import BluetoothScanner class MyMainForm(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.dialPWM.setEnabled(False) self.ui.comboBoxBluetooth.setEnabled(False) @pyqtSlot(int) def on_dialPWM_valueChanged(self, value): self.ui.lineEditPWM.setText(str(self.ui.dialPWM.value()+1)) self.thread1.pwmval = self.ui.dialPWM.value() @pyqtSlot() def on_pushButtonAction_clicked(self): if self.ui.pushButtonAction.text().find("扫描蓝牙")>=0: # 等待扫描蓝牙 self.ui.comboBoxBluetooth.clear() self.ui.labeMsg.setText("正在扫描蓝牙设备...") async_thread = BluetoothScanner(self) async_thread.finished.connect(self.async_task_finished) async_thread.device_found_signal.connect(self.add_device_to_list) async_thread.start() self.ui.pushButtonAction.setEnabled(False) elif self.ui.pushButtonAction.text().find("连接蓝牙")>=0: print("连接蓝牙") selected_item = self.ui.comboBoxBluetooth.currentText() self.ui.comboBoxBluetooth.setEnabled(False) if selected_item: device_address = selected_item.split('||')[1] self.ui.labeMsg.setText("正在连接蓝牙"+ selected_item) print(device_address) self.thread1 = BluetoothReceiver(device_address) self.thread1.finished.connect(self.async_task_finished) self.thread1.connection_status_signal.connect(self.update_status_label) self.thread1.data_received_signal.connect(self.update_data_label) self.thread1.start() elif self.ui.pushButtonAction.text().find("断开蓝牙")>=0: print("断开蓝牙") def update_data_label(self, data): try: # 接收到蓝牙上传的 温湿度信息 self.ui.labeMsg.setText(f"接收数据: {data}") self.ui.lcdNumberTempture.display(data['Temp']) self.ui.lcdNumberHumid.display(data['Hum']) except Exception as e: print("接收数据解释错误:", str(e)) def update_status_label(self, status): self.ui.labeMsg.setText(f"状态: {status}") self.ui.dialPWM.setEnabled(True) self.thread1.pwmval = self.ui.dialPWM.value() self.ui.comboBoxBluetooth.setEnabled(False) self.ui.pushButtonAction.setText("断开蓝牙") def async_task_finished(self): self.ui.labeMsg.setText("扫描完成") def add_device_to_list(self, device_info): # 查询到蓝牙列表,添加到combobox列表里 if device_info['name'] is not None: print(device_info['name'],device_info['address']) self.ui.comboBoxBluetooth.addItem(device_info['name']+"||"+device_info['address']) self.ui.comboBoxBluetooth.setEnabled(True) self.ui.pushButtonAction.setText("连接蓝牙") self.ui.pushButtonAction.setEnabled(True) if __name__ == "__main__": app = QApplication(sys.argv) # 初始化 myWin = MyMainForm() # 将窗口控件显示在屏幕上 myWin.show() # 程序运行,sys.exit方法确保程序完整退出。 sys.exit(app.exec_())
五、效果演示。
上位机启动,扫描蓝牙设备。
搜索到蓝牙设备后,连接蓝牙设备。
连接上后,就可以读取温湿度信息,并且设置PWM的值了。
调试过程中,没有接LED灯管,使用示波器查看PWM波形。
六、源码
https://passport.eepw.com.cn/deal/down/id/394517
https://share.eepw.com.cn/share/download/id/394518
七、视频
https://www.bilibili.com/video/BV19drmYNEwX/