STM32+ESP8266+DHT11+OLED WiFi温湿度监控系统实现
用STM32ESP8266DHT11OLED做一套WiFi温湿度监控手把手教你从零搭完这个项目说白了一句话把房间里的温度和湿度通过DHT11传感器采回来交给STM32处理一边在OLED小屏幕上本地显示一边通过ESP8266模块联网发出去让你用手机或电脑随时能看到当前环境数据。整套做下来成本不到五十块钱却能顺带把单片机开发、串口通信、传感器时序协议、WiFi透传这几块嵌入式基本功全练一遍特别适合正在学STM32、准备搞毕设或者单纯想给家里添个环境监测小工具的读者。我做这项目的初衷很简单书房里夏天闷热冬天干冷体感全靠猜就想搞个能远程看温湿度的东西。网上现成方案不少但要么是用Arduino玩起来差点意思要么直接买成品传感器网关贵还改不了逻辑。索性自己从最小系统开始搭边做边踩坑最后整套东西稳定跑了几个月数据也准功耗也低今天把整个流程完整拆出来分享。1. 项目设计与整体思路拆解1.1 这套系统到底在做什么先说清楚系统架构整个项目的数据流是一条非常清晰的链路采集层DHT11温湿度传感器负责采集空气温度和相对湿度以数字信号输出。处理层STM32F103C8T6作为主控负责读取DHT11数据、解析校验、控制OLED显示、通过串口给ESP8266下发指令并转发数据。输出层0.96寸OLED屏本地显示ESP8266模块通过WiFi将数据上报到远端。很多新手一上来就想把采集显示联网云端一次搞定结果一个环节出错就全盘懵。我的建议是分模块调通先让STM32能稳定读取DHT11并在串口打印数值再接OLED显示最后才接ESP8266做联网上报。每加一块问题范围就缩小一分调试难度也直线下降。1.2 核心部件选型为什么是这三件套方案选型我横向对比过好几轮这里把关键判断逻辑讲清楚方便你以后自己选型也有章可循。主控STM32F103C8T6而不是Arduino Uno做这个项目市面上最常见的路线是Arduino加DHT11库几十行代码就完事。但既然标题写的是STM32自然要用寄存器级和HAL库的方式吃透时序协议的底层逻辑。STM32F103C8T6价格十几块钱72MHz主频20KB RAM64KB Flash驱动DHT11和OLED绰绰有余。更重要的是STM32的定时器资源丰富后面做精确微秒延时、做低功耗唤醒、做PID温控都有扩展空间不会被单片机性能卡脖子。传感器DHT11而不是DHT22或SHT30DHT11精度确实一般——温度±2℃湿度±5%RH采样频率还得大于1秒一次。但它的优势在于便宜、模块化程度高、通信协议简单只需要一根数据线就能读出20字节数据实际有效40位。对监控室温这种场景DHT11的精度完全够用。如果你需要更高的测量精度代码框架不用大改把DHT11换成DHT22AM2302即可接口和时序几乎一样。通信模块ESP8266-01S而不是ESP32ESP32功能当然更强自带WiFi蓝牙双模但做这种简单上报任务属于杀鸡用牛刀而且开发环境配置对新手不友好。ESP8266-01S市场价大概七八块钱出厂自带AT固件STM32只需要通过串口发AT指令就能让它联网完全不需要在ESP8266上单独写程序。这种主控透传模块的分工方式也符合模块化设计的思路坏了直接换一个模块就行不用重刷固件。显示0.96寸I2C接口OLEDOLED对比度高、可视角度大关键是从I2C接口接出来只需要两根线SCL和SDA不占用太多GPIO。SSD1306驱动方案非常成熟网上现成代码一大把几行初始化就能点亮。部件型号成本约核心参数选型理由主控STM32F103C8T615元72MHz / 64KB Flash性价比高资料丰富可扩展性强传感器DHT115元温度±2℃ / 湿度±5%RH便宜可靠单总线协议适合学习WiFi模块ESP8266-01S8元802.11 b/g/nAT指令透传模块化无需写固件显示屏0.96寸OLED12元128x64I2C显示清晰接线简单功耗低1.3 两个容易忽视的设计决策做完整个项目回头复盘有这两个设计决策是让系统稳定运行的关键一个是传感器与主控的电源隔离DHT11模块和OLED模块上的逻辑芯片对电源纹波比较敏感而ESP8266在WiFi发射瞬间电流会拉到300mA左右造成3.3V轨电压跌落。如果全都挤在一个LDO后面传感器读数偶尔会出现诡异的跳变。所以我后面专门用了一路独立的AMS1117-3.3给ESP8266供电实测数据稳定很多。另一个是数据上报和本地显示分离。ESP8266联网是阻塞型操作AT指令的响应时间在几十到几百毫秒不等WiFi断开重连甚至会卡几秒。如果主程序在等待AT响应时卡住OLED刷新和DHT11采集都会停摆。正确的做法是上报数据放在主循环的空闲时间片里执行而不是让整机等着网络回复后面讲代码架构的时候我会详细说。2. 硬件电路设计与接线细节2.1 完整接线表STM32F103C8T6最小系统板网上能买到的STM32F103C8T6最小系统板引出的排针引脚都是一致的接线如下模块引脚连接STM32引脚说明DHT11VCC3.3V模块供电DHT11GNDGND共地DHT11DATAPB0数据线模块自带上拉电阻OLEDVCC3.3V模块供电OLEDGNDGND共地OLEDSCLPB6I2C1时钟线OLEDSDAPB7I2C1数据线ESP8266-01SVCC3.3V独立LDO注意电流需求ESP8266-01SGNDGND共地ESP8266-01STXPA10STM32的USART1 RXESP8266-01SRXPA9经分压STM32的USART1 TXESP8266-01SCH_PD(EN)3.3V使能引脚必须拉高ESP8266-01SGPIO0悬空运行模式需悬空或拉高ESP8266-01SGPIO2悬空运行模式需悬空或拉高ESP8266-01SRST悬空或接复位按键可不接2.2 关于ESP8266的供电和电平匹配踩过的大坑要提前说这里必须花大篇幅强调电源问题。ESP8266-01S正常工作电流平均70mA左右但WiFi发射瞬间电流峰值能到300mA如果直接用STM32板载的3.3V LDO给它供电大概率会出现两种情况一是ESP8266反复重启AT指令经常不回二是STM32跟着死机或复位——因为板载LDO被拉垮了。我实测下来最稳的方案是外部5V适配器供电板上AMS1117-3.3给STM32和DHT11、OLED供电同时用第二颗AMS1117-3.3的独立输出给ESP8266供电两个LDO的输出电容都加上100uF电解和0.1uF陶瓷电容。如果手头没有第二颗LDO至少要在ESP8266的VCC和GND之间并一个470uF的电解电容来扛瞬态电流。注意ESP8266-01S的VCC只能接3.3V绝对不能接5V接上去大概率直接烧毁模块内部稳压电路。电平匹配这块STM32F103的IO是3.3V逻辑ESP8266也是3.3V逻辑理论上是兼容的。但ESP8266-01S的RX对高电平的容限比较敏感STM32的TX输出3.3V虽然在规格书范围内实际使用中我建议加一个1kΩ2kΩ的分压电阻把STM32 TX的电平降到2.2V左右再进ESP8266的RX能明显降低通信误码率。ESP8266 TX到STM32 RX可以直接连不需要分压。2.3 DHT11模块与裸传感器选模块省心很多DHT11有两种形态一种是四脚直插裸传感器另一种是带PCB板的三针模块。裸传感器需要自己在数据线上外接一个4.7kΩ到10kΩ的上拉电阻到VCC模块则已经把上拉电阻和滤波电容做在板子上了。为了减少不确定性新手我强烈建议直接买模块版三根线VCC、GND、DATA接完就完事。还有一个很多人忽略的点DHT11的DATA引脚是开漏输出结构所以外部必须有上拉电阻。如果买的模块不带你得自己外接否则读回来的数据全是0xFF或者乱码。我遇到过好几个读者私信问DHT11读出来全是1排查到最后都是漏了上拉电阻。OLED模块的I2C地址默认是0x3C但也有部分模块是0x3D。代码里如果显示不出来先用I2C扫描程序打印一下实际地址不用死磕0x3C。另外OLED的VCC和GND一定别接反这模块烧了几乎没有维修价值。3. 软件环境搭建与核心代码实现3.1 开发环境与CubeMX工程配置工具链我用的是STM32CubeMX生成初始化代码加Keil MDK编译下载这套流程对新手最友好也方便以后换芯片型号重新生成工程。CubeMX里关键配置项如下SYS - Debug选Serial Wire否则烧录一次后第二次SWD就连接不上了。RCC - HSE选Crystal/Ceramic Resonator使用板载8MHz晶振。时钟树HSE经过PLL倍频到72MHz主频APB1总线时钟36MHzAPB2总线时钟72MHz。USART1异步模式波特率1152008位数据1位停止位无校验。用于与ESP8266通信。USART2异步模式波特率115200用作调试串口接USB转TTL小板打印日志。I2C1标准模式100kHzPB6和PB7。GPIOPB0配置为开漏输出、无上拉下拉用于DHT11数据线。CubeMX生成工程后需要自己额外添加的代码包括微秒级延时函数、DHT11驱动、OLED驱动或移植U8g2库、ESP8266 AT指令封装、主逻辑状态机。3.2 微秒延时DHT11时序的命脉DHT11是单总线协议时序精度要求微秒级。STM32的HAL库自带的HAL_Delay()只能毫秒级不够用需要实现一个微秒延时。最推荐的方式是用**DWTData Watchpoint and Trace**模块的CYCCNT寄存器精度高且不占用定时器资源// 启用DWT计数 void DWT_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; } // 微秒延时系统时钟72MHz void delay_us(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t ticks us * 72; while ((DWT-CYCCNT - start) ticks); }注意delay_us函数里不要加任何中断关闭操作否则DWT计数会受影响。DHT11的整个时序读取过程建议在临界区保护下进行但要先关闭中断、完成一次40位读取、再恢复中断整个读取过程大约5ms不会影响系统其他任务。3.3 DHT11通信时序把协议拆开揉碎DHT11单总线协议的核心是主机发起起始信号传感器应答然后连续输出40位数据。每一位的0和1是靠高电平持续时间区分的。我用文字一步步拆解第一步主机发送起始信号主机把数据线拉低至少18ms然后释放并拉高20-40us。这个低电平时间必须足够长DHT11才能识别到起始信号。18ms在代码里就是HAL_GPIO_WritePin(DHT11_GPIO_Port, DHT11_Pin, GPIO_PIN_RESET); delay_ms(20);实际延时20ms比较保险。第二步传感器响应DHT11收到起始信号后会把数据线拉低80us再拉高80us表示我准备好了开始传数据。第三步逐位读取40位数据每位数据都是先拉低50us然后拉高高电平持续26-28us表示0持续70us表示1。读数据位的代码如下uint8_t DHT11_ReadBit(void) { // 等待数据线从低变高 while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_RESET); delay_us(40); // 在高电平中间采样 if (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET) { while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET); // 等待高电平结束 return 1; } else { return 0; } }这里的关键技巧是拉高后延时40us再采样如果还是高电平说明是170us高电平如果已经变低说明是026-28us高电平。实测用40us作为分界点非常稳定。第四步校验数据40位数据分成5个字节湿度整数、湿度小数、温度整数、温度小数、校验和。校验和等于前四个字节之和的低8位。如果校验失败这次读取直接丢弃等下一次采样周期再读——千万不要用错误数据去刷新OLED或上报云端。关于GPIO模式的切换因为DHT11数据线需要双向通信读取时要能把引脚从输出模式切到输入模式。CubeMX里我把PB0配成开漏输出这样释放总线时引脚自动变为高阻态靠上拉电阻拉高读输入的时候直接用HAL_GPIO_ReadPin读引脚电平不用来回切换模式代码简洁很多。3.4 驱动0.96寸OLEDSSD1306OLED驱动我直接移植了U8g2库的单色版本支持中英文混显屏幕布局灵活底层只用适配两个I2C读写函数uint8_t u8x8_hal_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch (msg) { case U8X8_MSG_BYTE_SEND: HAL_I2C_Master_Transmit(hi2c1, OLED_I2C_ADDR, (uint8_t *)arg_ptr, arg_int, 100); break; case U8X8_MSG_BYTE_INIT: break; default: return 0; } return 1; }不追求花哨界面的话第一版可以让OLED固定显示三行第一行是湿度百分比第二行是温度摄氏度第三行是ESP8266连接状态或IP地址。等基础功能稳定了再研究翻页显示历史最大最小值或者画个简单曲线。OLED的I2C地址宏定义注意别写错#define OLED_I2C_ADDR 0x3C 1因为HAL库的地址参数需要左移一位这是新手最容易卡住的地方。3.5 ESP8266联网与数据上报ESP8266-01S出厂默认是AT固件通过串口发AT指令即可控制。我们需要实现的指令序列如下AT // 测试模块是否响应 ATCWMODE1 // 设置为Station模式 ATCWJAP你的WiFi名,你的WiFi密码 // 连接WiFi ATCIFSR // 查看模块IP地址 ATCIPSTARTTCP,192.168.1.100,8080 // 建立TCP连接 ATCIPSEND37 // 发送37字节数据 GET /update?temp26.5hum45.2 HTTP/1.1\r\nHost: 192.168.1.100\r\n\r\n在STM32端每个AT指令都要等待模块返回OK或ERROR才能继续下一步。这里我写了一个带超时机制的字符串发送和接收匹配函数uint8_t ESP8266_SendCommand(char *cmd, char *expect, uint16_t timeout_ms) { USART1_SendString(cmd); memset(esp_rx_buf, 0, ESP_RX_BUF_SIZE); esp_rx_len 0; uint16_t elapsed 0; while (elapsed timeout_ms) { if (strstr(esp_rx_buf, expect) ! NULL) { return ESP8266_OK; } HAL_Delay(10); elapsed 10; } return ESP8266_TIMEOUT; }这个函数的逻辑很直接发完指令后不断检查串口接收缓冲区里是否出现了期望的关键字比如OK、ERROR、CONNECT等。实际调试过程中我发现ESP8266返回字符串里经常混有换行和回车符所以匹配关键字时用strstr做子串匹配不要用strcmp做全串比较。数据上报的payload我这里用最简单的HTTP GET请求往局域网内一台电脑或树莓派上跑的TCP服务发数据接收端记录到日志文件里就能形成温湿度变化曲线。如果你想上云平台思路一样只是把TCP服务器的IP换成平台接入地址payload格式换成平台要求的JSON格式。3.6 主循环架构怎么让三件事互不干扰主循环的逻辑设计是整个软件架构的核心核心原则是不让任一模块阻塞其他模块uint32_t last_dht_read 0; uint32_t last_upload 0; uint8_t wifi_connected 0; while (1) { // 每2秒读一次DHT11DHT11采样周期不能短于1秒 if ((HAL_GetTick() - last_dht_read) 2000) { if (DHT11_Read(humidity, temperature) DHT11_OK) { OLED_DisplayEnvironment(temperature, humidity); } last_dht_read HAL_GetTick(); } // 每10秒尝试上报一次如果WiFi断开则只重连不上报 if ((HAL_GetTick() - last_upload) 10000) { if (wifi_connected) { char buffer[128]; snprintf(buffer, sizeof(buffer), GET /update?temp%.1fhum%.1f HTTP/1.1\r\nHost: 192.168.1.100\r\n\r\n, temperature, humidity); if (ESP8266_SendData(buffer) ESP8266_OK) { // 上报成功 } else { wifi_connected 0; } } last_upload HAL_GetTick(); } // 每30秒检查一次WiFi连接状态 if ((HAL_GetTick() - last_wifi_check) 30000) { if (ESP8266_SendCommand(ATCIPSTATUS\r\n, CIPSTATUS:0, 500) ESP8266_TIMEOUT) { // 未连接重新执行连接流程 wifi_connected ESP8266_Reconnect(); } else { wifi_connected 1; } last_wifi_check HAL_GetTick(); } }HAL_GetTick()是SysTick中断里累加的毫秒计数器用它可以实现简单的非阻塞定时常驻任务。如果后续要加功能比如温度超过阈值时蜂鸣器报警或者风扇启动只需要在这个主循环里再加一个时间片分支不会干扰已有逻辑。4. 联调测试与常见问题排查实录4.1 ESP8266 AT指令不回或返回ERROR这是联调阶段最常遇到的问题基本集中在三个方面供电不足、串口参数不匹配、固件异常。供电不足的场景表现在模块偶尔能回AT一连接WiFi就死掉或重启。用万用表量ESP8266 VCC引脚在发送数据瞬间如果电压掉到3.0V以下基本可以断定供电问题。解决办法要么加独立LDO要么在模块附近加大电容。串口参数不匹配ESP8266出厂波特率常见的是115200但也有模块被卖家改成9600或74880。首次上电用串口助手分别试115200和9600和74880这三个波特率总有一个能通。另外注意STM32的USART1要和ESP8266的TX/RX交叉连接否则收不到任何数据。固件异常有些二手模块或者拆机模块的AT固件损坏表现是上电后串口只输出乱码。重新用ESP8266 FLASH工具烧录AT固件即可网上搜ESP8266 AT固件 bin文件就能找到官方固件包。4.2 DHT11一直读失败或数据跳变这个问题的排查顺序我建议是这样的检查接线和上拉DATA线是接在PB0上吗模块板上有没有上拉电阻模块的电源和GND接对了吗先万用表量模块VCC和GND之间有没有3.3V。检查时序用示波器或者逻辑分析仪抓DATA线上的波形看起始信号后有没有80us的低电平响应。没有示波器的可以在代码里把读到的高电平脉冲时间打印到串口看看是不是26-28us和70us这两个区间。检查采样频率DHT11数据手册明确写采集周期必须大于1秒代码里我设置的是2秒读一次。如果你在主循环里不加节流疯狂读取传感器容易不响应返回全是1。温度和湿度数值交替跳变多半是电源纹波太大。把这路传感器供电从ESP8266那路挪开故障通常就消失了。还有一个很隐蔽的问题DHT11对线材长度敏感超过20cm的杜邦线容易出通信错误。如果必须远距离布置传感器数据线用双绞线并在传感器端加一个0.1uF退耦电容。4.3 OLED不亮、花屏和显示乱码OLED不亮优先排查三件事供电有没有、I2C地址对不对、SDA/SCL有没有接反。用I2C扫描程序打印识别的设备地址十秒钟就能确认地址问题。OLED花屏的原因多是电源干扰或者I2C总线上有其他设备地址冲突。花屏复现时摸一下ESP8266模块温度如果是模块发射时花屏在OLED的VCC和GND之间加一个10uF钽电容就能解决。显示乱码的常见原因是U8g2库的字体没初始化对或者代码里用了中文但字库没有对应的中文字符。第一版先只显示英文和数字跑通了再加中文字库。4.4 上电后STM32无法烧录程序这个问题的典型症状是Keil烧录时提示No STM32 Target Found。原因通常是CubeMX里SYS Debug没有选Serial Wire导致程序跑起来后把SWD引脚给重新配置成了普通GPIO。解决办法有两个如果程序还能跑按住复位键的同时点烧录在复位释放瞬间完成连接如果程序已经跑飞了把BOOT0引脚拉高上电进入ISP模式这时用串口ISP工具擦除Flash再把BOOT0拉低恢复正常启动。还有一种情况是ST-Link接线太长杜邦线超过10厘米时SWD信号完整性下降降低SWD时钟频率到1MHz或4MHz就能解决。4.5 实测数据放了三天记录稳定性怎么样项目调试完成后我在书房实际跑了三天每10秒上报一次数据连续72小时。整理下来几个有参考价值的结论数据稳定性温度读数一直稳定在25.1℃到26.8℃之间变化和书房的体感完全吻合。湿度在48%到61%区间波动下午开窗通风时会有一个明显的下降拐点。WiFi长期连接稳定性三天内出现过两次掉线都在凌晨时段可能是家用路由器定时清理客户端导致的。我的重连机制每次都能自动恢复最长的断线时间是47秒。DHT11与空调温度计对比距离空调温度计约1米实测温差在1.3℃以内湿度差在5%以内对于日常监控完全够用。功耗情况USB电流表实测整机平均电流约120mA其中ESP8266占了大头。如果以后要做电池供电让ESP8266在不上报时进入Modem Sleep平均电流能降到20mA左右这个话题以后可以单独开一篇写。5. 进阶扩展方向让这套系统更实用5.1 数据可视化云端平台接入如果觉得局域网TCP上报不够直观可以考虑接入物联网云平台。现在阿里云物联网、腾讯云IoT、OneNET都有免费版设备接入额度ESP8266通过MQTT协议把数据推上去然后用平台的仪表盘组件画温度曲线手机上就能实时查看历史数据。MQTT协议的原理比TCP稍复杂一些需要在ESP8266端做协议拆包好在云平台都提供设备端SDK和AT固件。对STM32来说只需要把上报的payload从HTTP格式改造成MQTT的PUBLISH报文然后发给平台的接入地址和端口即可。5.2 数据存储与报警联动一个很实用的扩展是增加历史数据存储功能就是在接收端电脑或者树莓派把每次上报的数据追加写入CSV文件。积累一个月后用Excel打开就能做数据分析看看家里湿度低于40%的时段是不是和感冒频率相关。报警逻辑我也加过一版温度高于30℃时STM32驱动一个蜂鸣器发声湿度低于30%时通过ESP8266向手机推送一条通知用Server酱或者PushPlus这类微信推送服务。实现起来就是在主循环的DHT11读取分支里加一个比较判断触发时置位一个标志位在上报分支里执行推送。5.3 扩展更多传感器这套架构扩展其他传感器非常方便因为STM32的GPIO和I2C资源还有大量富余。我后来在同一块板上加了BH1750光照传感器和土壤湿度传感器用ADC读取做成了一个室内植物养护监控终端OLED屏翻页显示温湿度、光照、土壤湿度四组数据。每个传感器都是独立模块、独立驱动文件主循环里统一读一遍刷新显示无脑复用。提示加传感器之前先统计一下当前STM32F103C8T6的Flash和RAM占用。我加了BH1750和ADC读取后Flash用了不到30KBRAM用了不到3KB还剩余很多空间所以几乎不用担心资源不够的问题。5.4 低功耗改造思路如果你的应用场景是野外或者不方便接电源的地方低功耗设计就提上日程了。目前这版系统因为ESP8266全程在线平均电流120mA用18650锂电池供电也就撑一天多。改造方向有三个STM32进入STOP模式DHT11只在唤醒后读取。ESP8266平时处于掉电状态用STM32的GPIO控制一个MOS管给模块断电只在需要上报时上电。上报频率从10秒一次降到5分钟一次。这样改完后整机平均电流可以做到5mA以下同样一颗18650能撑一周多。不过低功耗的坑比普通模式多得多比如DHT11冷启动后第一秒读取的时序不稳定需要做重试机制ESP8266冷启动连WiFi要花好几秒这期间不能被中断打断。这些都是要实战中一点点踩出来的。最后分享几个我从这个项目里悟出来的实操细节整个项目做下来最深的体会是嵌入式项目的坑七成在硬件两成在时序一成才在逻辑。很多新手写代码能一次通过但一到实物联调就卡壳根源就是没把电源完整性和电平匹配当回事。如果你现在准备做这个项目我的建议是先别急着写代码按照第二部分的接线表把线插好用万用表把每个模块的供电电压都量一遍确认没有5V串进3.3V的器件再开始写固件。另一个很实用的小技巧是在调试阶段一定要保留一个调试串口专用通道。我使用的是USART2作为日志输出口通过USB转TTL小板连到电脑串口助手。DHT11每次读取成功或失败ESP8266每次连接状态变化都打印一行带时间戳的日志。这样出问题时不用猜直接看日志就知道是哪一环断了。这比用OLED屏幕看状态高效得多OLED只有看最终结果时才方便。最后多说一句这套系统不需要一次全部做完。第一步先打通DHT11和串口打印第二步点OLED屏第三步联WiFi每一步都是独立的完成状态走完一步就有一分的成就感。等你四步全部走完你会在某天半夜醒来时突然想起这个系统还在一丝不苟地记录着房间的温湿度——那种我做的东西在默默工作的感觉就是折腾硬件最大的乐趣所在。
