简介本资源是面向嵌入式开发初学者与进阶工程师的STM32温室控制系统完整开发包聚焦农业物联网场景下的环境参数智能调控实践解决温湿度、光照、土壤湿度等多传感器数据采集、逻辑判断与执行器联动控制等核心问题。压缩包共1087个文件含570个C源码文件实现ADC采样、PID调节、串口通信等核心功能、256个头文件定义外设驱动与系统接口、51个汇编文件底层启动与中断处理以及调试配置icf/lnp、编译输出axf/hex/map和说明文档pdf/pptx/txt整体容量33.46MB结构完整覆盖从硬件连接到RTOS集成的全开发链路。已有64人学习下载资源包含经实测可运行的工程代码、详尽的系统架构说明、硬件接线图解及高级特性应用示例如ARM CMSIS-DSP库调用、线性插值算法实现特别适合通过真实项目掌握STM32外设驱动、多任务调度与农业场景落地方法。1. 温室不是靠“感觉”控温的一个STM32项目如何把温湿度、光照、通风全拧成闭环你见过凌晨三点还在手动开窗、关风机、调遮阳帘的温室管理员吗我见过——那是在我接手某校农科实训基地旧系统前。原系统用51单片机继电器硬控温湿度超限报警靠蜂鸣器“滴滴滴”数据全靠手抄记录本。直到某次连续阴雨天棚内湿度飙到98%霉斑一夜爬满番茄叶而控制器还在按固定时间表启停加湿器。这不是设备问题是控制逻辑没进阶它缺感知、缺决策、缺执行反馈闭环。这个“基于STM32的温室控制系统.zip”就是为解决这类问题而生的典型工程包它不是玩具Demo而是以STM32F103C8T6或类似主控为核心集成DHT22温湿度、BH1750光照、HC-SR04超声波测水位/卷帘高度、步进电机驱动五线四相和继电器阵列通过PID调节加热/降温/补光/通风并支持串口调试与基础人机交互的完整嵌入式方案。它适合农业物联网初学者练手、高职院校毕业设计落地、小型智能大棚快速部署——关键在于所有模块选型、驱动适配、时序协调都已验证过不是理论拼凑。你拿到zip解压后能直接烧录、接线、看到LCD上跳动的实时数据而不是先花三天查HAL库ADC切换通道的坑。下面我们就从芯片选型开始一层层拆解这个系统怎么跑起来、怎么不翻车。2. 从芯片到外设为什么选STM32F103C8T6以及它怎么带得动一整套温室传感器2.1 主控选型不是越贵越好而是“够用可扩展资料多”STM32F103C8T6被称作“STM32界的Arduino Uno”不是因为它性能强而是因为它的生态成熟度、外设资源与成本比达到了极佳平衡点72MHz主频 64KB Flash 20KB RAM足够跑FreeRTOS轻量级任务温控、光照采集、电机控制分时调度也够塞下PID参数、历史数据缓存、简单菜单逻辑丰富定时器资源TIM2/TIM3/TIM4可分别用于TIM2生成PWM驱动步进电机五线四相需细分驱动需双路互补PWMTIM3输入捕获测HC-SR04回波时间超声波测距核心TIM4SysTick替代延时避免delay()卡死见避坑章多路ADC DMADHT22虽是单总线但BH1750用I²C、土壤湿度模拟量用ADC——F103有3个ADC支持16通道扫描配合DMA可无CPU干预采样避免ADC切换通道时序错乱这是热词里高频踩坑点USART1/2/3全可用USART1接CH340转USB调试printf to usart stm32USART2接ESP8266或巴法云模块stm32 巴法云USART3留作未来485通信stm32控制伺服电机485关键一点LD文件兼容性——很多新手在Keil或VSCode配置stm32开发环境时因ld文件链接脚本未适配C8T6的Flash/RAM布局导致烧录后程序不运行。本项目使用标准STM32F103C8Tx_FLASH.ld已修正.data段加载地址与.bss段清零范围避免全局变量初始化失败。提示不要盲目升级到F4/F7系列。F103的HAL库对DHT22单总线、ILI9341屏幕stm32使用ili9341读id是a1a1、步进电机驱动等外设支持更稳定社区例程多出问题能快速搜到解决方案。F4虽然性能强但HAL库对某些老旧SPI屏的DCX引脚时序控制反而更玄学。2.2 传感器与执行器选型为什么是这些组合而不是其他模块型号/类型接口方式关键理由热词关联点温湿度DHT22单总线成本低、精度够±0.5℃/±2%RH、无需外部校准STM32 GPIO模拟时序成熟stm32 adc切换通道不适用但常被误用光照强度BH1750I²C数字输出、分辨率高1lx、抗干扰强比光敏电阻ADC更稳定stm32 i2c clock stretch问题少超声波测距HC-SR04GPIO触发测水箱液位/卷帘开合度非测空气距离需TIM3输入捕获精确计时避开软件延时误差stm32超声波测距显示屏ILI93412.4寸SPI支持16位色、分辨率240×320驱动库成熟读ID为0xA1A1是验证SPI通信是否正常的黄金标准stm32使用ili9341读id是a1a1执行机构5V步进电机五线四相 ULN2003GPIO/PWM成本低、扭矩足、正反转精准五线四相需双路PWM控制两相绕组TIM2双通道输出五线四相步进电机stm32通风/补光5V继电器模块4路GPIO隔离驱动风机、补光灯、加热片注意继电器线圈驱动电流需ULN2003缓冲不可STM32直接拉stm32按键模块电路设计同理为什么不用DS18B20—— 它虽精度高但单总线挂载多个时需ROM搜索DHT22在单点监测场景下更省GPIO、代码更简。为什么不用SHT30—— 成本翻倍且F103的I²C在长导线环境下易受干扰DHT22的单总线反而更鲁棒。为什么ILI9341读ID必须是0xA1A1—— 这是芯片出厂ID若读出0x0000或0xFFFF说明SPI时钟极性/相位CPOL/CPHA配置错误或CS/RS引脚接反——这是调试屏幕最快速的“心跳检测”。2.3 开发环境VSCode PlatformIO 是比Keil更透明的起点虽然Keil MDK仍是工业主流但对学习者VSCode PlatformIO 的优势在于依赖自动管理platformio.ini中声明platform ststm32、board bluepill_f103c8自动下载对应芯片包stm32芯片包安装无需手动找HAL库版本编译日志可见报错时直接定位到main.c:127行而非Keil里一堆汇编中间文件J-Link下载零配置插上J-LinkPlatformIO自动识别pio run -t upload一键烧录比Keil里设Flash算法更省心vscode配置stm32开发环境关键配置项platformio.ini片段[env:bluepill_f103c8] platform ststm32 board bluepill_f103c8 framework stm32cube monitor_speed 115200 upload_protocol jlink ; 必须指定启动文件否则__libc_init_array未调用全局构造函数不执行 build_flags -D STM32F103xB -I Inc -I Drivers/STM32F1xx_HAL_Driver/Inc -I Drivers/CMSIS/Device/ST/STM32F1xx/Include注意若用Keil务必确认Use MicroLIB未勾选否则printf重定向会异常且Target页中Crystal Oscillator填8000000外部晶振频率Debug页中SW Device选SWD——这是stm32禁用jtag后最常见的调试失败原因。3. 核心驱动落地DHT22单总线、ILI9341 SPI、HC-SR04定时器捕获三连调3.1 DHT22用GPIO模拟时序拒绝HAL_Delay()的玄学等待DHT22通信协议要求严格时序主机拉低至少800μs启动信号 → 拉高80μs → 等待传感器响应80μs低80μs高→ 逐位读取40bit数据50μs低27μs高为050μs低70μs高为1。任何一处超时整个帧就废。HAL库的HAL_Delay()最小精度是1ms完全不能用。必须用__NOP()或定时器微秒级延时。// dht22.c 关键片段精准微秒延时基于SysTick已配置为1us tick static void DHT22_DelayUs(uint32_t us) { uint32_t start SysTick-VAL; uint32_t delay us * (SystemCoreClock / 1000000); while ((start - SysTick-VAL) delay) { if (start SysTick-VAL) start 0xFFFFFF; // 溢出处理 } } // 启动信号拉低800us → 拉高80us HAL_GPIO_WritePin(DHT22_PORT, DHT22_PIN, GPIO_PIN_RESET); DHT22_DelayUs(800); HAL_GPIO_WritePin(DHT22_PORT, DHT22_PIN, GPIO_PIN_SET); DHT22_DelayUs(80); // 切换为输入模式等待响应 HAL_GPIO_Mode_t mode GPIO_MODE_INPUT; HAL_GPIO_Init(DHT22_PORT, GPIO_InitStruct); // 此处需重配置GPIO为浮空输入 // 读取40bit每bit先拉低50us再读电平持续时间 for (int i 0; i 40; i) { // 等待下降沿传感器拉低 while (HAL_GPIO_ReadPin(DHT22_PORT, DHT22_PIN) GPIO_PIN_SET); // 测量高电平宽度0为27us1为70us uint32_t t_high 0; while (HAL_GPIO_ReadPin(DHT22_PORT, DHT22_PIN) GPIO_PIN_RESET) { t_high; if (t_high 100) break; // 超时退出 } data[i/8] 1; if (t_high 50) data[i/8] | 1; // 高电平50us判为1 }参数说明DHT22_DelayUs()用SysTick计数而非HAL_Delay()确保μs级精度t_high阈值设为50us是经验值实测DHT22在25℃下0码高电平约25~30us1码约65~75us必须重配置GPIO为输入否则输出模式下读取始终为高这是新手最常翻车点。3.2 ILI9341SPI初始化读ID验证0xA1A1是唯一真理ILI9341的SPI通信需严格遵循其时序CS拉低 → 发送指令/数据 → CS拉高。读ID指令0x00后需读2字节正确值为0xA1A1。// ili9341.c 初始化SPI使用HAL_SPI_TransmitReceive void ILI9341_Init(void) { // SPI1初始化CPOL0, CPHA0, BaudRate18MHzF103最高支持 hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; // CPOL0 hspi1.Init.CLKPhase SPI_PHASE_1EDGE; // CPHA0 hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_2; // 72MHz/236MHz → 实际SPI时钟18MHz HAL_SPI_Init(hspi1); // 硬件复位 HAL_GPIO_WritePin(ILI9341_RST_PORT, ILI9341_RST_PIN, GPIO_PIN_RESET); HAL_Delay(100); HAL_GPIO_WritePin(ILI9341_RST_PORT, ILI9341_RST_PIN, GPIO_PIN_SET); HAL_Delay(100); // 读ID发送0x00指令读2字节 uint8_t cmd 0x00; uint8_t rx_data[2]; HAL_GPIO_WritePin(ILI9341_CS_PORT, ILI9341_CS_PIN, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); HAL_SPI_Receive(hspi1, rx_data, 2, HAL_MAX_DELAY); HAL_GPIO_WritePin(ILI9341_CS_PORT, ILI9341_CS_PIN, GPIO_PIN_SET); if (rx_data[0] 0xA1 rx_data[1] 0xA1) { // ID正确继续初始化序列... ILI9341_WriteCommand(0x11); // Sleep Out HAL_Delay(120); ILI9341_WriteCommand(0x29); // Display On } else { // ID错误检查SPI引脚、CPOL/CPHA、CS电平 Error_Handler(); } }参数说明SPI_BAUDRATEPRESCALER_2F103 APB2时钟72MHzSPI1挂载APB2分频后18MHz满足ILI9341最大20MHz要求CPOL0, CPHA0这是ILI9341默认模式若读ID失败优先检查此两项CS必须软件控制HAL_SPI不自动管理CS必须手动拉低/拉高否则多设备SPI总线冲突。3.3 HC-SR04TIM3输入捕获测回波告别毫秒级误差HC-SR04触发后返回高电平脉宽即为声波往返时间单位μs距离 脉宽 × 340m/s ÷ 2 ÷ 1000000。软件延时测不准必须用定时器输入捕获。// hc_sr04.cTIM3配置为输入捕获CH2PA7 void HC_SR04_Init(void) { // PA7配置为复用推挽TIM3_CH2 GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // TIM3初始化1MHz计数频率72MHz/721MHz即1us/计数 htim3.Instance TIM3; htim3.Init.Prescaler 71; // 72MHz/(711)1MHz htim3.Init.CounterMode TIM_COUNTERMODE_UP; htim3.Init.Period 0xFFFF; HAL_TIM_IC_Init(htim3); // CH2配置为上升沿捕获开始计时 TIM_IC_InitTypeDef sConfigIC {0}; sConfigIC.ICPolarity TIM_INPUTCHANNELPOLARITY_RISING; sConfigIC.ICSelection TIM_ICSELECTION_DIRECTTI; sConfigIC.ICPrescaler TIM_ICPSC_DIV1; sConfigIC.ICFilter 0; HAL_TIM_IC_ConfigChannel(htim3, sConfigIC, TIM_CHANNEL_2); // 开启中断 HAL_TIM_IC_Start_IT(htim3, TIM_CHANNEL_2); } // 中断服务函数捕获上升沿→下降沿→计算差值 void HAL_TIM_IC_MspCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM3 htim-Channel HAL_TIM_ACTIVE_CHANNEL_2) { static uint32_t rising_time 0; static uint32_t falling_time 0; static uint8_t state 0; if (state 0) { // 上升沿记录时间 rising_time HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); state 1; // 切换为下降沿捕获 __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_FALLING); } else if (state 1) { // 下降沿计算脉宽 falling_time HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2); uint32_t pulse_width (falling_time rising_time) ? (falling_time - rising_time) : (0x10000 falling_time - rising_time); distance_cm (pulse_width * 0.017) 0.5; // 0.017cm/us0.5四舍五入 state 0; // 恢复上升沿捕获 __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_2, TIM_INPUTCHANNELPOLARITY_RISING); } } }参数说明Prescaler71使TIM3计数频率为1MHz即每个计数代表1μs脉宽值直接等于μs数pulse_width计算需考虑计数器溢出若falling_time rising_time说明计数器已溢出一次需加0x100000.017是声速340m/s换算340m/s 0.034cm/μs往返需÷2 → 0.017cm/μs。4. 控制逻辑闭环PID温控、光照阈值联动、步进电机精准定位4.1 温度PID控制器不是调参而是让积分不饱和温室温度控制不是开关控制ON/OFF否则风机/加热片频繁启停寿命骤减。本项目采用位置式PIDoutput Kp * error Ki * ∫error dt Kd * (error - error_prev)/dt但直接套公式会翻车积分项累积导致“超调严重恢复慢”。必须加积分分离输出限幅。// pid.c带防积分饱和的位置式PID typedef struct { float Kp, Ki, Kd; float setpoint; // 目标温度℃ float input; // 当前温度℃ float output; // 输出占空比0~100 float max_output; // 输出上限如风机最大占空比80% float min_output; // 输出下限如0% float integral; // 积分项 float last_error; // 上次误差 uint32_t last_time; // 上次计算时间ms } PID_TypeDef; float PID_Calculate(PID_TypeDef *pid) { float error pid-setpoint - pid-input; uint32_t now HAL_GetTick(); float dt (now - pid-last_time) / 1000.0f; // 秒 pid-last_time now; // 积分分离误差大时禁用积分防止超调 if (fabsf(error) 2.0f) { // 误差2℃才积分 pid-integral error * dt; } // 输出限幅 float output pid-Kp * error pid-Ki * pid-integral pid-Kd * (error - pid-last_error) / dt; pid-last_error error; // 防饱和积分项反向修正 if (output pid-max_output) { output pid-max_output; pid-integral - (output - pid-max_output) / (pid-Ki * dt); // 反向减积分 } else if (output pid-min_output) { output pid-min_output; pid-integral - (output - pid-min_output) / (pid-Ki * dt); } return output; } // 主循环调用 temp_pid.input dht22_data.temperature; temp_pid.output PID_Calculate(temp_pid); // 将output映射为TIM2 PWM占空比0~100 → 0~1000 __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, (uint32_t)(temp_pid.output * 10));参数说明Kp2.0, Ki0.5, Kd0.1适用于温室空气热惯性大的场景实测响应平稳积分分离阈值2℃避免低温启动时积分疯狂累积反向积分修正当输出达限幅时主动削减积分项防止脱离限幅后“飞车”。4.2 光照联动策略不是简单阈值开关而是渐变补偿补光灯不应在光照100lx时突然全亮否则植物应激。本项目采用分段线性补偿光照 ≥ 500lx补光灯关闭200lx ≤ 光照 500lx补光亮度 (500 - 光照) / 300 * 100%光照 200lx补光亮度 100%// light_control.c uint8_t Light_ComputeBrightness(uint16_t lux) { if (lux 500) return 0; else if (lux 200) { return (uint8_t)((500 - lux) * 100 / 300); // 200→100%, 500→0% } else { return 100; } } // 主循环 uint8_t brightness Light_ComputeBrightness(bh1750_data.lux); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_2, (uint32_t)(brightness * 10)); // 映射到0~1000为什么不用PWM直接调光—— LED补光灯需恒流驱动本项目用MOSFETPWM调光但占空比需线性映射亮度避免人眼感知闪烁。4.3 五线四相步进电机双路PWM实现半步细分定位精度±0.9°五线四相步进电机如28BYJ-48需5根线公共端4相A/B/C/D。ULN2003驱动时按A→AB→B→BC→C→CD→D→DA顺序通电实现半步0.9°/步。// stepper.cTIM2双通道输出互补PWMCH1/CH2控制A/B相CH3/CH4控制C/D相 void Stepper_SetStep(uint8_t step) { static const uint16_t pwm_duty[8] {1000, 707, 0, 707, 1000, 707, 0, 707}; // 半步序列占空比 static const uint8_t phase_map[8][4] { {1,0,0,0}, // A {1,1,0,0}, // AB {0,1,0,0}, // B {0,1,1,0}, // BC {0,0,1,0}, // C {0,0,1,1}, // CD {0,0,0,1}, // D {1,0,0,1}, // DA }; // 设置TIM2各通道占空比CH1A, CH2B, CH3C, CH4D __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, phase_map[step][0] ? pwm_duty[step] : 0); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_2, phase_map[step][1] ? pwm_duty[step] : 0); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_3, phase_map[step][2] ? pwm_duty[step] : 0); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_4, phase_map[step][3] ? pwm_duty[step] : 0); } // 主循环根据超声波测得的卷帘高度计算目标步数 uint16_t target_steps (target_height_cm * 100) / 0.9; // 0.9°/步一圈400步360° for (uint16_t i 0; i target_steps; i) { Stepper_SetStep(i % 8); HAL_Delay(2); // 步进间隔2ms对应125rpm }参数说明pwm_duty数组中707是√2/2×1000实现AB/BC/CD/DA相的电流合成获得平滑转矩HAL_Delay(2)是经验参数太快易失步太慢效率低实测2ms对应125rpm兼顾速度与力矩必须用硬件PWM软件GPIO翻转无法保证四相时序精度会导致抖动或堵转。5. 避坑指南那些让项目卡在“能编译但不工作”的血泪现场5.1 现象烧录后LED不闪、串口无输出但Keil显示“Download successful”原因system_stm32f1xx.c中HSE_VALUE未修改为实际晶振值如8MHz导致系统时钟配置错误所有外设失能startup_stm32f103xb.s中Stack_Size设得太小默认0x200全局变量或局部数组溢出破坏栈J-Link固件过旧不支持C8T6的Flash解锁。解决检查stm32f1xx_hal_conf.h中#define HSE_VALUE ((uint32_t)8000000)将Stack_Size改为0x400Heap_Size改为0x200J-Link Commander执行exec flasher STM32F103C8 unlock。5.2 现象DHT22读数始终为0或NaN原因GPIO未重配置为输入模式仍处于推挽输出读取值恒为高DHT22_DelayUs()中SysTick未启用或SystemCoreClock未正确初始化电源不稳DHT22供电电压低于3.3V尤其用USB供电时。解决在DHT22_ReadData()开头强制执行HAL_GPIO_DeInit()再HAL_GPIO_Init()在main()开头添加HAL_Init(); SystemClock_Config();给DHT22单独接3.3V LDO电源勿与STM32共用USB 5V经AMS1117-3.3降压。5.3 现象ILI9341屏幕全白或花屏但读ID成功0xA1A1原因ILI9341_WriteCommand(0x36)内存访问控制未设置导致GRAM地址方向错误ILI9341_WriteCommand(0x3A)接口像素格式设为0x0516位但实际写入24位RGBSPI时钟速率过高20MHz信号边沿畸变。解决初始化序列中加入ILI9341_WriteCommand(0x36); ILI9341_WriteData(0x48);BGR垂直翻转ILI9341_WriteCommand(0x3A); ILI9341_WriteData(0x05);严格16位将SPI_BAUDRATEPRESCALER改为_49MHz稳定性优先于速度。5.4 现象HC-SR04测距值跳变极大10cm→200cm随机原因超声波探头未固定振动导致回波散射TIM3输入捕获未开启滤波ICFilter0xF可滤除高频噪声电源纹波大影响HC-SR04内部振荡器。解决用橡胶垫固定探头远离风机出风口sConfigIC.ICFilter 0xF;采样4次取中值HC-SR04 VCC并联100μF电解电容0.1μF陶瓷电容。5.5 现象步进电机只抖动不转或转几圈后堵转原因ULN2003输入端未接上拉电阻5VSTM32 3.3V输出驱动不足PWM频率过低1kHz电机产生明显嗡鸣并发热未实现半步序列仅用全步A→B→C→D导致力矩不足。解决ULN2003输入端加4.7kΩ上拉至5VTIM2时钟设为72MHz预分频71计数周期1000 → PWM频率72kHz严格按A→AB→B→BC→C→CD→D→DA8步序列驱动不可省略AB/BC等中间态。6. 让系统真正“活”起来串口调试协议、数据持久化、低功耗唤醒技巧6.1 定制串口指令集用AT指令思维做本地调试与其在代码里改阈值再烧录不如设计一套轻量AT指令通过串口实时调控。本项目定义ATTEMP25.5设置目标温度为25.5℃ATLIGHT300设置光照补偿阈值为300lxATREAD返回当前温湿度、光照、距离、电机状态ATSAVE将当前PID参数、阈值存入Flash第64页地址0x0801FC00。// uart_debug.c简易AT解析器非完整状态机但够用 #define AT_CMD_MAX_LEN 32 char at_buffer[AT_CMD_MAX_LEN]; uint8_t at_index 0; void USART1_IRQHandler(void) { HAL_UART_IRQHandler(huart1); uint8_t ch; if (HAL_UART_Receive(huart1, ch, 1, HAL_MAX_DELAY) HAL_OK) { if (ch \r || ch \n) { at_buffer[at_index] \0; if (strncmp(at_buffer, ATTEMP, 8) 0) { temp_pid.setpoint atof(at_buffer 8); printf(Temp set to %.1f\r\n, temp_pid.setpoint); } else if (strncmp(at_buffer, ATREAD, 7) 0) { printf(T:%.1f H:%.0f L:%d D:%d\r\n, dht22_data.temperature, dht22_data.humidity, bh1750_data.lux, distance_cm); } at_index 0; } else if (at_index AT_CMD_MAX_LEN-1) { at_buffer[at_index] ch; } } }为什么不用现成AT库—— 太重。温室场景只需5条指令手写解析10行代码搞定RAM占用100字节比引入at_parser库更可靠。6.2 Flash数据持久化本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?