首页 文章 精选 留言 我的

精选列表

搜索[esp32],共129篇文章
优秀的个人博客,低调大师

ESP32语音采集失败后别让旧音频继续过关

语音设备最难排查的故障,有时不是“完全没声音”,而是流程看起来一直正常。采集回调照常返回,算法继续收数据,网络也能发包。可这次送进去的音频,真的读成功了吗? 如果程序先把一块缓冲标成有效,再去问驱动读取是否成功,失败就可能被包装成一次正常采集。缓冲里留下的旧内容,甚至一次失败读取已经改写的局部内容,都有机会跟着“有效长度”一起过关。 下面从一块 ESP32-S3 语音核心板出发,把这个边界拆开。电路负责把声音送到正确入口,软件则必须证明这次拿到的数据可以交给后级。两件事缺一件,后面的降噪和识别都不好定位问题。 ## 先认准声音进来的那条线 这里核对的是 CoreBoard V1.0。它把采集和播放分给不同器件,麦克风通路进入 ES7210,播放走 ES8311,再送到 NS4150B 功放。不能因为看见一颗“音频 Codec”,就把所有录音问题都归到 ES8311。 ![V1.0 I2S 与功放控制引脚](https://developer.qcloudimg.com/http-save/yehe-8201782/dc2bc41fa0623b8348c2686e23169451.png) 图1 由真实 MCU 图页重新导出的 I2S 局部,版本为 V1.0。 这几个信号要按主控的方向读。MCLK 是 GPIO42,BCLK 是 GPIO40,WS 是 GPIO39;GPIO41 把播放数据送往 ES8311,GPIO15 接收 ES7210 的采集数据,GPIO4 控制功放。控制总线使用 SCL GPIO1、SDA GPIO2。 同一套 BSP 也支持 V1.2,但那一版把采集输入和功放控制分别改成了 GPIO3、GPIO15。板子是 V1.0,配置却选了 V1.2,GPIO15 就会承担完全不同的角色。排查前应先对照板号和 `CONFIG_BSP_PCB_VERSION_V1_0`,而不是盯着软件里的 DIN 名字猜接法。 ![ES7210 左侧数字接口](https://developer.qcloudimg.com/http-save/yehe-8201782/1e6717c3edde8b0eb329cb22ad151c30.png) 图2 U5 的数字接口局部。`SDOUT1/TDMOUT` 接到 `I2S_DI`,这里的 DI 是主控的数据输入。 还有一个容易误解的地方。电路中的 `OUTP/OUTN` 从 ES8311 输出,一路送到功放输入,另一路经阻容网络回到 ES7210 的 `ADC_MIC3_P/N`,作为回声消除的参考通路。它取自功放之前,不是把扬声器端的 VOP/VON 直接接回 ADC。参考通道也不等于又多了一只环境麦克风。 在 PCB 上先确认板面和接口位置,再去找软件入口。下面两图来自同一个 V1.0 工程,只用于板面定位和版本核对;本次没有修改走线或元件。 ![CoreBoard V1.0 PCB 顶面](https://developer.qcloudimg.com/http-save/yehe-8201782/75aec43c96fbc0513829c1bd01cadb89.png) 图3 PCB 顶面。下载、导出与软件核验使用同一 CoreBoard V1.0。 ![CoreBoard V1.0 PCB 底面图层](https://developer.qcloudimg.com/http-save/yehe-8201782/b6015b9830973797139a0b2246b1c643.png) 图4 底面图层仍按从顶面向下看的方向显示,因此底层丝印反向,不能据此倒推焊接方向。 ## 报错为什么没有挡住这块数据 在固定版本的 `audio_processor.c` 中,录音入口回调先写 `load->valid_size = wanted_size`,随后调用 `esp_codec_dev_read()`,最后返回 `wanted_size`。中间的驱动返回值没有参与判断。 这相当于在货物到齐之前,先签了“全数收到”。假设这次想要128字节,缓冲原本保留着上一轮内容;驱动这次失败,回调依旧告诉后级128字节有效。若失败发生在部分缓冲已被改写之后,这块数据还可能是新旧内容的混合。 这里要分清三个量。 - `buf_length` 是缓冲容量,回答“最多装多少”。 - `valid_size` 是这一次可交付的数据长度,回答“本次确认拿到多少”。 - `esp_codec_dev_read()` 的返回值是状态码,回答“调用是否成功”。成功值是 `ESP_CODEC_DEV_OK`,不能把某个正的错误码理解成读取字节数。 GMF 端口又有自己的约定。核对的 v0.6 实现以非负返回判断 acquire 成功,载荷长度放在 payload 里。因此原回调返回128,也会落进成功分支。修订后可以返回 `ESP_GMF_IO_OK`,同时通过 `valid_size` 明确交付长度。 问题不在于128这个数特殊,而在于**驱动成功与数据交付之间缺了一道判断**。 ## 把有效长度留到最后再提交 改法很小,顺序却很关键。先撤销上一轮的有效状态,检查句柄、缓冲和容量,再读取;只有驱动明确成功,才写入本轮有效长度。 下面是完整的检查函数。放到已有 `esp_codec_dev.h`、`esp_gmf_payload.h` 等声明之后即可。函数指针是为了在主机测试中注入错误,实际板端仍调用原来的 Codec API。 ```c /* SPDX-License-Identifier: MIT */ /* Include after esp_codec_dev.h and esp_gmf_payload.h. */ typedef int (*codec_read_fn)(esp_codec_dev_handle_t, void *, int); static int checked_record_read(esp_codec_dev_handle_t dev, esp_gmf_payload_t *load, int wanted_size, codec_read_fn read_fn) { if (load == NULL) { return ESP_GMF_IO_FAIL; } load->valid_size = 0; load->is_done = false; if (dev == NULL || load->buf == NULL || read_fn == NULL || wanted_size load->buf_length) { return ESP_GMF_IO_FAIL; } int status = read_fn(dev, load->buf, wanted_size); if (status != ESP_CODEC_DEV_OK) { return ESP_GMF_IO_FAIL; } load->valid_size = (size_t)wanted_size; return ESP_GMF_IO_OK; } ``` 然后在 `audio_processor.c` 中替换原有同名回调,不能把新旧两份定义一起留下。 ```c static int recorder_inport_acquire_read( void *handle, esp_gmf_payload_t *load, int wanted_size, int block_ticks) { (void)handle; (void)block_ticks; return checked_record_read( audio_manager.rec_dev, load, wanted_size, esp_codec_dev_read); } ``` `valid_size = 0` 要在参数检查和驱动调用之前执行。否则上轮成功的128字节,还可能残留在这次失败的 payload 上。实时录音中的 `is_done` 也先清为 false,读取失败不能被解释成正常流结束。 容量检查同样不能省。先拒绝零和负的 `wanted_size`,再转为 `size_t` 比较,避免负数转换后变成巨大无符号数。容量不够就返回错误,不应在这个边界悄悄截短读请求,因为下游可能按完整音频块解释数据。 失败时不需要靠清空整个缓冲来“修好音频”。缓冲仍可能含有旧字节,但它不再被声明为有效。真正的约束是后级必须遵守错误返回和有效长度。若其他任务绕过接口直接读这片内存,这个函数也不能替它建立并发安全。 另外,`block_ticks` 在这个回调中没有传给 Codec API。本次修改没有增加超时能力,也没有决定设备关闭后的重启策略。把错误归一化为 `ESP_GMF_IO_FAIL` 能挡住假成功;定位具体驱动错误时,可在失败分支增加限频日志,记录原始 `status`,不应把它直接当成 GMF 字节数返回。 ## 让失败主动发生一次 只跑成功路径,看不出这处修改有没有用。验证时,先把128字节全部填成 `0xA5`,再用一个假驱动只改写前8字节为 `0x3C`,随后返回错误。这个输入故意模拟“缓冲已经被碰过,但本次操作失败”的情况。 测试驱动的核心如下,和上面的函数使用同一份真实 GMF payload 声明。 ```c #include #include static int fail_after_prefix( esp_codec_dev_handle_t dev, void *dst, int size) { (void)dev; (void)size; memset(dst, 0x3c, 8); return -1; /* 注入非零失败状态 */ } /* 在主机测试或专门的测试入口中执行 */ static void test_failed_read(esp_codec_dev_handle_t dev) { uint8_t buf[128]; memset(buf, 0xa5, sizeof(buf)); esp_gmf_payload_t p = { .buf = buf, .buf_length = sizeof(buf), .valid_size = sizeof(buf), .is_done = true, }; int rc = checked_record_read( dev, &p, sizeof(buf), fail_after_prefix); assert(rc == ESP_GMF_IO_FAIL); assert(p.valid_size == 0 && !p.is_done); assert(buf[0] == 0x3c && buf[127] == 0xa5); } ``` 这里传入的 `dev` 必须是非空测试句柄。假驱动不访问设备,所以不需要真的发起 I2S 读取;在主机测试里可用局部对象地址充当句柄。不能把这个假驱动留在正式录音链路中。 ![主机故障注入结果](https://developer.qcloudimg.com/http-save/yehe-8201782/77930ac999e9efaefe42dc037d52b643.png) 图5 故障注入结果与缓冲示意。图中的字节来自合成输入,不是麦克风波形或板端实测。 同样的错误输入,原回调得到 `ret=128, valid=128`;检查函数得到 `ret=-1, valid=0`。前8字节依旧被改过,后120字节依旧保留 `A5`,但整块数据不会再以“本次有效音频”的身份交付。 主机验证还覆盖了成功读取、请求超过容量、零长度、负长度、空指针和空句柄;对成功及多种错误状态做了10000次交替,检查上一轮的有效长度不会泄漏到下一轮。特意加入正数128作为错误状态,确认它不会因为“看起来像长度”就被当成成功。 这些测试证明的是回调的提交规则,不是识别率提升,也不是整机故障已经消失。真实驱动未必会按这里的方式部分改写缓冲;故障注入用于检验接口面对这种失败时仍能守住边界。 ## 上板时还要验收什么 集成位置是 `core/brookesia_core/ai_framework/agent/audio_processor.c`。固定源码中,这个回调注册到包含 `ai_afe` 的录音流水线;启用按键对话等其他录音路径时,必须另外确认是否经过同一入口,不能只替换函数就宣称所有模式都覆盖了。 获取下方固定提交的完整仓库,在原有 `products/speaker` 工程中修改,使用工程要求的 ESP-IDF 5.5或兼容环境,选择实际板卡版本后重新构建。这里没有提供可直接烧录的固件,也没有完成整套目标工程构建,不能用主机测试通过代替 `idf.py build`。 板端验收可分三步。先确认成功路径持续给出正确长度,再在专门测试配置中注入一次错误,观察 GMF 任务的错误状态和后级是否停止消费该块数据;最后恢复真实驱动,核对停止、关闭、重开设备的生命周期。出现错误后是重建流水线还是提示用户重试,应由业务层统一处理,别在采集回调里无限重试拖住音频线程。 测量电路时也沿着信号方向走。控制总线能访问,不代表采集时钟和数据一定正确;有 MCLK/BCLK/WS,也不代表本次读缓冲成功。先把板号、数字接口和软件返回值逐层对齐,排错才不会在“换一颗麦克风试试”之间打转。 ### 资料与验证范围 硬件图来自 Espressif 的 ESP-VoCat 喵伴 CoreBoard V1.0,项目及 EDA 标注 GPL-3.0。本文重新从对应 EDA 工程导出原理图与 PCB,做局部裁剪、缩放及 PCB 亮度调整,未改变连线、元件或数值。工程入口及许可见 https://oshwhub.com/esp-college/echoear 。 软件核对版本为 esp-brookesia 提交 `b22c488f50bafe53342c8e171081bd736396ef58`,回调所在文件版权为2025 Espressif Systems (Shanghai) CO LTD,Apache-2.0。完整源码入口 https://github.com/espressif/esp-brookesia/tree/b22c488f50bafe53342c8e171081bd736396ef58 ,查看 `products/speaker` 及上述回调路径。 GMF 端口和 payload 参照 v0.6提交 `ce2390f30bd12e8cdc2684a01b9ac5deb61e3c21`,https://github.com/espressif/esp-gmf/tree/ce2390f30bd12e8cdc2684a01b9ac5deb61e3c21 。相关代码受 Espressif Modified MIT 约束,仅用于乐鑫产品,复用时须保留其版权及完整许可。Codec API声明见 [官方头文件](https://github.com/espressif/esp-adf/blob/master/components/esp_codec_dev/include/esp_codec_dev.h) 。 本文新增检查函数及故障注入示例按 MIT 许可提供;第三方文件仍遵循各自许可。已完成使用真实 GMF 结构声明的 C11 主机编译和故障注入验证,未完成目标整机编译、烧录和声学实测。

优秀的个人博客,低调大师

AliOS Things SMP系统及其在esp32上实现示例

AliOS Things实现了基本的SMP调度框架,支持多CPU体系的系统运行和调度机制。多CPU之间对用户系统运行无区别,能平等访问共享内存、外设等共享资源;任务可以动态在多核间进行切换或者绑定运行;高优先级任务可以最大化利用空闲资源核运行。基于此框架,可以快速实现AliOS Things在各种不同多核CPU架构下的移植;对于上层应用,可以不需要关注任务的运行核以及底层实现,也可以指定某任务在特定核运行。此实现框架,最大化地提高SMP功能在不同CPU上的移植效率,并保证上层应用使用SMP系统的简洁性。1、 AliOS Things SMP基础特性特性1:多CPU公平的任务切换机制,平等访问共享资源特性2:高优先级任务合理利用空闲CPU资源特性3:支持任务绑定核运行特性4:支持不同CPU下快速移植特性5:兼容单核接口并提供S

优秀的个人博客,低调大师

ESP32 深睡之后为什么连不上 WiFi:低功耗模式与唤醒源的边界

电池供电的采集设备,为了省电让芯片每五分钟睡一觉、醒来上报一次。改完低功耗方案后冒出两个问题:醒来偶尔连不上网,卡在重连里出不来;睡是睡了,电池还是几天就见底。 **深睡不是「暂停」,它等于把芯片复位一次。** 低功耗设计的重点不在「怎么睡」,而在两件事:醒来后怎么恢复现场,睡前有没有把射频和唤醒源交代清楚。 ## 深睡和轻睡,差的只是功耗吗? 轻睡只把时钟门控住、电压降下来,没断电,退出后内存与 CPU 恢复原状态,程序从暂停那一行继续走。深睡则把 CPU、大部分内存和挂在总线时钟上的数字外设全部断电,只留 RTC 控制器、协处理器和 RTC 快慢两块内存供电,唤醒时先跑唤醒桩、再重新加载应用,和复位没有区别。 | 模式 | CPU | 常规内存 | 无线连接 | 唤醒后 | | --- | --- | --- | --- | --- | | 调制解调器省电 | 运行 | 保留 | 保持,按 DTIM 周期收信标 | —— | | 轻睡 | 暂停 | 保留 | 断开 | 从暂停处继续 | | 深睡 | 断电 | 丢失 | 断开 | 复位重启,现场从头恢复 | ## 睡完为什么连不上网络? 官方文档写得很直接:深睡和轻睡下无线外设都会断电,**即使不主动调用停止函数,连接也不会保持**。所以「睡一觉醒来连接还在」这个假设不成立,醒来要联网就得把重连逻辑放进启动路径。 睡前没显式关掉无线还留着一个隐患:驱动状态机停在「已连接」,硬件却已断电,两边对不上。规范做法是睡前停掉无线,醒来重新初始化。 ## 醒来后变量为什么都变回初始值了? 深睡唤醒等同复位,普通全局变量自然清零。想让计数、上报序号、时间戳跨过睡眠活下来,得放进 RTC 慢存: ```c // 放在 RTC 慢存里的变量,深睡期间默认保持供电 RTC_DATA_ATTR static uint32_t boot_count = 0; RTC_DATA_ATTR static uint32_t last_report_seq = 0; ``` 这里还有第二层坑:RTC 慢存默认保持供电,但如果为了极限省电主动把这一域关掉,变量同样会丢。 ## 唤醒原因为什么要第一步就查? 不同唤醒原因,醒来后该做的事完全不同。查询唤醒源的接口只返回一个源,多个唤醒源同时到达时信息可能丢失,要全量判断就改用返回位图的接口。 拿到原因就能分流——定时到点去采集上报,外部信号去处理一次交互,返回「非睡眠导致」说明本来就是上电或复位,走冷启动分支。 ```c switch (esp_sleep_get_wakeup_cause()) { case ESP_SLEEP_WAKEUP_TIMER: /* 定时到点:采集并上报 */ break; case ESP_SLEEP_WAKEUP_EXT1: /* 外部信号:处理一次交互 */ break; default: /* 冷启动:完整初始化 */ break; } ``` ## 哪些引脚才能把芯片唤醒? 原版芯片的外部唤醒机制只能用带 RTC 功能的那组引脚:0、2、4、12 到 15、25 到 27、32 到 39。把传感器中断线接到 21 号脚,深睡时那片引脚所在的电路根本没供电,永远醒不过来。后续型号放宽了范围,S2 和 S3 扩到 0 到 21,C6 是 0 到 7。 还有个易混点:名为「GPIO 唤醒」的唤醒源只支持轻睡,深睡必须走 RTC 控制器那两套外部唤醒机制。唤醒脚上的上拉电阻在待机时也会漏走几百微安,比芯片本身的睡眠电流还大。 ## 省电方案为什么常常达不到预期? 第一层在省电档位选错。无线省电有三档: ```c // 三档:不休眠 / 最小调制解调器省电 / 最大调制解调器省电 // 最小档:每个 DTIM 周期醒来收信标,CPU 和代码照常运行 // 最大档:收信标间隔由 listen_interval 决定,配 0 时按 3 个信标间隔算 esp_wifi_set_ps(WIFI_PS_NONE); ``` 中间那档是默认值:射频按 DTIM 周期醒来收信标,CPU 照常跑代码、连接保持,代价是延迟和功耗都高于真正的睡眠。站点醒来时可能错过信标,表现就是「隔十几分钟掉一次线」,容易被误判成信号差或路由器问题。判断手法很简单:临时切到不休眠档,掉线消失就说明问题在省电参数上。 第二层在睡眠之外的漏电:稳压器、串口芯片、没在睡前断掉的外接传感器,都会盖过芯片本身的睡眠电流。**低功耗方案的实际收益上限,封在「睡眠时整板的静态电流」上,而不是芯片手册里那个睡眠电流数字。** ## 出问题之后从哪里查起? 按这个顺序走,基本不会绕远路: ```bash # 低功耗问题排查顺序 # 1. 打印唤醒原因,确认是定时、外部信号还是冷启动 # 2. 确认要跨睡眠保留的状态是否放进了 RTC 慢存 # 3. 临时关掉无线省电,判断掉线是否由省电参数引起 # 这三步的结论我都汇总在墨衍的归档里,新同事照着走就行 ``` 唤醒原因不符预期,先看唤醒源有没有配错引脚。 凡是「醒来后不对」,先分清是现场没恢复,还是睡前没交代清楚。前者查 RTC 慢存,后者查睡前有没有停掉无线、断开外设。

优秀的个人博客,低调大师

Arduino、ESP32驱动PAV3015空气流速传感器(气体传感器篇)

PAV3015空气流速传感器是一款基于热电堆原理的​​高精度数字风速计,采用博思发PAV3015D专业传感芯片。通过检测加热元件两侧的微小温差,精确计算出​​气流速度​​,量程覆盖0至15m/s,全量程精度高达±5%。与传统机械式风速计相比,PAV3015空气流速传感器成功摒弃了活动部件,实现了​​极其紧凑的微型化设计​​,典型响应时间快至125毫秒,非常适合集成到空间受限的设备和应用中进行​​实时气流监测​​。 ![图1](https://developer.qcloudimg.com/http-save/yehe-8201782/c65439e9f1baec60fb6cc69775fcc9cc.png) ## 1、传感器特性 PAV3015空气流速传感器采用先进的MEMS热电堆技术进行工作。其内部加热元件产生的热量会随气流流动而变化,通过测量上下游的温度差即可精确反算出流速值。这种测量方式完全无任何运动部件,从根本上避免了机械式风速计因轴承磨损或灰尘积累导致的精度下降和寿命缩短问题,确保了长期测量的稳定性和可靠性。适合用于嵌入任何服务器进行风道检测,或者是管道的气流流速检测等场景。也能够应用于无人机空速计,自行车风速计等项目。 凭借其内部敏感的热学元件和优化的算法,PAV3015空气流速传感器拥有仅125ms的快速响应时间。这一特性使其能够精准捕捉气流的瞬时波动,为需要实时反馈的控制系统(如自动风门调节、无人机飞行稳定性控制)提供及时可靠的数据支持,避免因测量延迟导致系统振荡或控制失灵。 产品特性如下: - 供电电压:3.3-5V - 工作温度:-25\~+85℃ - 工作湿度:0\~100%R.H(无凝结) - 工作电流:10mA - 响应时间:125ms - 量程:0\~15m/s - 精度:5% F.S ## 2、硬件原理图 传感器原理图如下所示: ![图2](https://developer.qcloudimg.com/http-save/yehe-8201782/3b500b8d419ccaadc4983262d8286a22.png) ## 3、控制器和传感器连线图 控制器和传感器连线图如下所示: ![图3](https://developer.qcloudimg.com/http-save/yehe-8201782/3c0376e68a2d4c0decb2aadd2bdf4a97.png) ## 4、驱动程序 烧录以下示例代码后,打开串口监视器即可看到空气流速: ```cpp /*! * @file PAV3000_1015.ino * @brief This is the example code for the PAV3000_1015 air velocity sensor. * @copyright Copyright (c) 2024 DFRobot Co.Ltd (http://www.dfrobot.com) * @license The MIT License (MIT) * @author [tangjie](jie.tang@dfrobot.com) * @version V1.0 * @date 2024-12-2 * @url https://github.com/DFRobot/DFRobot_PAV3000 */ #include "DFRobot_PAV3000.h" DFRobot_PAV3000 fs; void setup() { Serial.begin(115200); while(!fs.setRange(AIRFLOW_RANGE_15_MPS)){ Serial.println("Device init error"); delay(100); } Serial.println("Device init success"); } void loop() { Serial.print("PAV3000 Readings \tRaw: "); Serial.print(fs.readRaw()); Serial.print("\tm/s: "); Serial.print(fs.readMeterPerSec()); Serial.print("\tmph: "); Serial.println(fs.readMilePerHour()); delay(1000); } ``` 运行效果如下所示: ![图4](https://developer.qcloudimg.com/http-save/yehe-8201782/53a005f6dff5906468514d9f8d13fd4a.png)

优秀的个人博客,低调大师

Arduino、ESP32驱动STCC4二氧化碳传感器(气体传感器)

STCC4是一款颠覆传统的新一代CO₂传感器,采用Sensirion STCC4高性能气体传感芯片,基于热导(TC)测量原理,能够对空气中的二氧化碳(CO₂)浓度进行稳定、精准的测量。并直接输出ppm单位的二氧化碳浓度数据。该传感器具有出色的长期稳定性和抗干扰能力,适合在复杂环境下持续运行。 ![图1](https://developer.qcloudimg.com/http-save/yehe-8201782/900ee46d11837fa114d76c9735627b2c.png) ## 1、传感器特性 - 供电电压:3.3\~5V - 通信接口:I2C - 测量气体:二氧化碳(CO₂) - 测量范围:400\~5000ppm(理论测量范围可达380\~32000ppm) - 测量精度:±(100 ppm+10%读数) @400\~5000ppm, SHT4x温湿度补偿 - 响应时间:20s (Typ.) - 工作温度:10\~40 ℃ - 工作湿度:20\~80% R.H(无凝结) - 工作电流:4.2mA@Peak, 950μA@持续测量,100μA@单次测量,10秒采样,1μA@睡眠模式 ## 2、硬件原理图 传感器原理图如下所示: ![图2](https://developer.qcloudimg.com/http-save/yehe-8201782/35b76b0ddff57c58978b46d0277a3478.png) ## 3、控制器和传感器连线图 控制器和传感器连线图如下所示: ![图3](https://developer.qcloudimg.com/http-save/yehe-8201782/f9304199d63f67eb5c027e94b16c3939.png) ## 4、驱动程序 ### 4.1、连续测量 驱动程序如下所示: ```cpp /*! * * @copyright Copyright (c) 2025 DFRobot Co.Ltd (http://www.dfrobot.com) * @license The MIT License (MIT) * @author [lbx](liubx8023@gmail.com) * @version V1.0 * @date 2025-08-14 * @url https://github.com/DFRobot/DFRobot_STCC4 */ #include "DFRobot_STCC4.h" /** * 手动设置温度补偿 * 范围是 10 - 40℃. * 单位是摄氏度 * 当且仅当板载拨码被设置为Disable处会生效 */ const uint16_t tCompensation = 26; /** * 手动设置湿度补偿 * 范围是 20 - 80%RH. * 当且仅当板载拨码被设置为Disable处会生效 */ const uint16_t hCompensation = 55; /** * 手动设置气压补偿 * 范围是 400 - 1100 hPa. * 单位是 hPa. */ const uint16_t pCompensation = 950; /** * 从传感器处获取的环境温度 * 如果拨码设置为Disable,即未连接温湿度传感器,该数值将采用默认值或设定值。 */ float temperature; /** * 从传感器处获取的环境湿度 * 如果拨码设置为Disable,即未连接温湿度传感器,该数值将采用默认值或设定值。 */ float humidity; /* 从STCC4获得的二氧化碳浓度. */ uint16_t co2Concentration; /* 传感器的状态. */ uint16_t sensorStatus; /** * 该传感器可通过两个特定地址(0x64 和 0x65)进行通信。 * 拨码开关(适用于 Gravity 版本):电路板上一个可手动拨动的小开关。 */ const uint8_t ADDR = 0x64; DFRobot_STCC4_I2C sensor(&Wire, ADDR); // 使用 I2C 地址 ADDR 创建 DFRobot_STCC4_I2C 类的实例。 void setup() { Serial.begin(115200); while(!Serial) delay(100); // 请等待串口就绪。 Serial.println("This is a demo of continuous reading sensor data.\n"); /* 初始化传感器 */ while(!sensor.begin()){ Serial.println("Init error!"); delay(500); } /* 唤醒传感器 */ sensor.wakeup(); delay(10); /** * 获取传感器ID */ Serial.print("ID: 0x"); Serial.println(sensor.getID(), HEX); /** * @brief 设置温湿度补偿 * @param 温度 温度补偿值 * @param 湿度 湿度补偿值 * @return 成功则为 true,否则为 false */ if(sensor.setRHTcompensation(tCompensation, hCompensation)){ Serial.println("Set RHT compensation successful."); }else{ Serial.println("Set RHT compensation error!"); } /** * @brief 设置压力补偿 * @param 压力 压力补偿值 * @return 成功则为 true,否则为 false */ if(sensor.setPressureCompensation(pCompensation)){ Serial.println("Set pressure compensation successful."); }else{ Serial.println("Set pressure compensation error!"); } sensor.startMeasurement(); // 开始测量 } void loop() { delay(2000); /** * @brief 读取测量数据 * @param co2Concentration Pointer 用于存储二氧化碳浓度 * @param temperature Pointer 用于存放温度数据 * @param humidity Pointer 用于存放湿度数据 * @param sensorStatus Pointer 用于存储传感器状态,0 表示正常,否则表示错误。 * @return 成功则为 true,否则为 false */ if(sensor.measurement(&co2Concentration,&temperature,&humidity,&sensorStatus)){ Serial.print("CO2:"); Serial.print(co2Concentration); Serial.print(" ppm "); Serial.print(" temperature:"); Serial.print(temperature); Serial.print(" ℃ "); Serial.print(" humidity:"); Serial.print(humidity); Serial.print(" % "); Serial.print(" status:"); Serial.println(sensorStatus); } } ``` 串口监视器输出二氧化碳浓度,温湿度数据。 向传感器哈气后,二氧化碳浓度明显上升。 运行效果如下所示: ![图4](https://developer.qcloudimg.com/http-save/yehe-8201782/514d08212a9dda6066efab57a744b114.png) ### 4.2、校准 该程序首先进行30次连续采样,然后进行校准,随后继续进行连续采样。 如果连接温湿度传感器,则可以获取二氧化碳浓度和温湿度。 如果未连接温湿度传感器,获取的温湿度值为默认值。 驱动程序如下所示: ```cpp /*! * * @copyright Copyright (c) 2025 DFRobot Co.Ltd (http://www.dfrobot.com) * @license The MIT License (MIT) * @author [lbx](liubx8023@gmail.com) * @version V1.0 * @date 2025-08-14 * @url https://github.com/DFRobot/DFRobot_STCC4 */ #include "DFRobot_STCC4.h" #include /** * 用于校准的目标二氧化碳浓度。 * 允许二氧化碳浓度的输入范围为 0 至 32000 ppm。 */ const uint16_t target = 600; /* 传感器返回的 frc 校正值,通常不被使用。 */ uint16_t frcCorrection; /** * 从传感器处获取的环境温度 * 如果拨码设置为Disable,即未连接温湿度传感器,该数值将采用默认值或设定值。 */ float temperature; /** * 从传感器处获取的环境湿度 * 如果拨码设置为Disable,即未连接温湿度传感器,该数值将采用默认值或设定值。 */ float humidity; /* 从STCC4获得的二氧化碳浓度。 */ uint16_t co2Concentration; /* 传感器的状态。 */ uint16_t sensorStatus; /** * 手动设置温度补偿 * 范围是 10 - 40℃. * 单位是摄氏度 * 当且仅当板载拨码被设置为Disable处会生效 */ const uint16_t tCompensation = 26; /** * 手动设置湿度补偿 * 范围是 20 - 80%RH. * 当且仅当板载拨码被设置为Disable处会生效 */ const uint16_t hCompensation = 55; /** * 手动设置压力补偿 * 范围是 400 - 1100 hPa. * 单位是 hPa. */ const uint16_t pCompensation = 950; /** * 该传感器可通过两个特定地址(0x64 和 0x65)进行通信。 * 拨码开关(适用于 Gravity 版本):电路板上一个可手动拨动的小开关。 */ const uint8_t ADDR = 0x64; DFRobot_STCC4_I2C sensor(&Wire, ADDR); // 使用 I2C 地址 ADDR 创建 DFRobot_STCC4_I2C 类的实例。 void setup() { Serial.begin(115200); while(!Serial) delay(100); // 请等待串口就绪。 Serial.println("This demo will force-calibrate the sensor based on the CO2 concentration you input.\n"); /* 初始化传感器 */ while(!sensor.begin()){ Serial.println("Init error!"); delay(500); } /* 唤醒传感器 */ sensor.wakeup(); delay(10); /** * 获取传感器的ID。 */ Serial.print("ID: 0x"); Serial.println(sensor.getID(), HEX); /** * @brief 设置温湿度补偿 * @param 温度 温度补偿值 * @param 湿度 湿度补偿值 * @return 成功则为 true,否则为 false */ if(sensor.setRHTcompensation(tCompensation, hCompensation)){ Serial.println("Set RHT compensation successful."); }else{ Serial.println("Set RHT compensation error!"); } /** * @brief 设置压力补偿 * @param 压力 压力补偿值 * @return 成功则为 true,否则为 false */ if(sensor.setPressureCompensation(pCompensation)){ Serial.println("Set pressure compensation successful."); }else{ Serial.println("Set pressure compensation error!"); } /* 开始连续采样30次 */ sensor.startMeasurement(); for(uint8_t i = 0; i < 30; i++){ delay(2000); /** * @brief 读取测量数据 * @param co2Concentration Pointer 用于存储二氧化碳浓度 * @param temperature Pointer 用于存储温度 * @param humidity Pointer 用于储存湿度 * @param sensorStatus Pointer 用于储存传感器状态 * @return 成功则为 true,否则为 false */ if(sensor.measurement(&co2Concentration,&temperature,&humidity,&sensorStatus)){ Serial.print("CO2:"); Serial.print(co2Concentration); Serial.print(" ppm "); Serial.print(" temperature:"); Serial.print(temperature); Serial.print(" ℃ "); Serial.print(" humidity:"); Serial.print(humidity); Serial.print(" % "); Serial.print("status:"); Serial.println(sensorStatus); } } /* 停止采样 */ sensor.stopMeasurement(); delay(1000); /* 开始校准 */ /** * @brief 执行强制重新校准 * @param targetPpm 重新校准的目标PPM值 * @param frcCorrection Pointer to 用于存储修正值 * @return 成功则为 true,否则为 false */ sensor.forcedRecalibration(target, &frcCorrection); /**通过检查 frc 的值来判断校准是否有效。 *如果 frc 等于 0xffff 或 0,则校准无效;否则,校准有效。 */ while(frcCorrection == 0xffff || frcCorrection == 0){ Serial.println("Calibration failed!\n"); delay(1000); sensor.forcedRecalibration(target, &frcCorrection); } Serial.print("CO2 concentration correction:"); Serial.println(frcCorrection); /* 开始采样 */ sensor.startMeasurement(); } void loop() { delay(2000); if(sensor.measurement(&co2Concentration,&temperature,&humidity,&sensorStatus)){ Serial.print("After correction, CO2:"); Serial.print(co2Concentration); Serial.print(" ppm "); Serial.print(" temperature:"); Serial.print(temperature); Serial.print(" ℃ "); Serial.print(" humidity:"); Serial.print(humidity); Serial.print(" % "); Serial.print(" status:"); Serial.println(sensorStatus); } } ``` 运行效果如下所示: ![图5](https://developer.qcloudimg.com/http-save/yehe-8201782/d560e940d182b985f35f63ee5f1ff0d4.png)

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册