news 2026/9/6 10:36:27

树莓派Pico ADC实战:用C语言面向对象封装PS2摇杆模块

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
树莓派Pico ADC实战:用C语言面向对象封装PS2摇杆模块

做嵌入式这段时间,我最常被问到的一个问题就是:ADC 到底怎么学?直接啃手册太枯燥,做项目又不知道从哪里上手。如果你也有这种感觉,我强烈建议你从这个小项目开始:用树莓派 Pico 读取一个 PS2 摇杆模块的数据。它不复杂,却能把 ADC 原理、硬件接线、采样滤波和代码架构全部串起来。更关键的是,这个项目天然适合用 C 语言里的面向对象思路来组织代码,写出来不会变成一坨面条代码。

我最初是直接拿adc_read()循环读,结果发现数据抖动、按键悬空、摇杆回中不准,一个都没少。后来重新梳理了硬件结构,又用结构体加函数指针把摇杆封装成一个独立模块,代码一下子清爽了很多,后面还想接舵机、做遥控手柄,都变得很容易。这篇文章就把整个过程的硬件结构、接线方式、代码封装思路和调试踩坑全部记录下来,适合刚学完 GPIO 和基础 C 语言、准备进阶 ADC 的嵌入式学习者。

1. 为什么做这个实验,以及整体设计思路

1.1 摇杆模块真的是“模拟”输入吗

很多人第一次拿到摇杆模块,会以为它像游戏手柄一样输出数字信号,直接读高低电平就行。实际完全不是。市面上最常见的 PS2 摇杆模块,两个轴向 VRx、VRy 输出的是连续变化的模拟电压,本质上是两个电位器在分压,只有中间的 SW 按键是数字量。

这个区分很重要,因为一旦接错思路,后面整个代码方向就偏了。摇杆的 X 轴和 Y 轴不能用gpio_get()去读,必须走 ADC 采集;而 SW 按键又不能用 ADC 通道去读,它是个按钮开关。所以这个实验看起来简单,实际上同时包含模拟量采集和数字量输入两类应用,非常适合当成一个完整的嵌入式入门练手项目。

我在给初学者讲这块的时候常说:一个摇杆模块等于“两个电位器加一个按键”。你在玩飞行游戏时推杆的手感,本质上就是手指在改变电位器滑臂的位置,进而改变分压比例。理解这句话,硬件原理就通了一半。

1.2 为什么选树莓派 Pico 来跑这个项目

选 Pico 而不是 STM32、ESP32 或者 Arduino,有几个原因。首先是 RP2040 这颗芯片的 ADC 资料相对完整,引脚也不复杂,GP26、GP27、GP28 三个 ADC 通道直接引出到排针,不用查复用表就能接线,对新手非常友好。STM32 的 ADC 功能更强,但引脚映射、ADC 时钟、采样周期配置一大堆,很多人在RCC那里就先迷路了。

其次是 Pico 可以用 C/C++ SDK,也可以用 MicroPython,还能用 Arduino 框架。同一个硬件,你可以先用 MicroPython 验证硬件,再用 C SDK 做严谨的工程化实现,学习梯度非常平滑。这个项目里我用的是 C 和 Pico SDK,因为要聊面向对象封装,C 才是最能体现“结构体加函数指针”这门手艺的地方。

当然,如果你手头已经有一块 STM32F103 或者 ESP32,也完全可以照着同样的思路移植。ADC 的核心原理是通用的,摇杆模块的接口也是一样的,只是引脚宏和库函数名字不同。理解 Pico 这一套,换成 STM32F ADC 获取的数据,逻辑完全不变。

1.3 从硬件到软件的“三层”设计

做这个项目之前,我给自己定了一个整体框架,避免想到哪写到哪。整个系统分三层:底层是摇杆模块的硬件结构,也就是电位器分压和按键回路;中间层是 Pico 的 ADC 初始化、采样和 GPIO 读取;上层是基于 ADC 原始值做的滤波、校准、死区判断和事件回调。

这样分层最大的好处是,硬件有问题不会赖到软件头上,软件要扩展也不用推翻硬件接线。比如后来我想加舵机控制,只需要在应用层新写一个servo_map()函数,从摇杆对象里拿处理过的 x、y 值,再映射成 PWM 占空比,摇杆底层一行都不用改。这就是嵌入式架构设计里很实用的一点点心得。

2. 硬件结构拆解:摇杆电位器和 Pico ADC 通道

2.1 摇杆模块内部结构

拆开一个 PS2 摇杆模块,能看到两个 10kΩ 电位器,一个控制 X 轴,一个控制 Y 轴,两个电位器的滑臂分别接到模块的 VRx 和 VRy 引脚。摇杆靠在中心位置时,两个电位器都处于中间抽头,输出电压大约是供电电压的一半;向某个方向推到底,对应轴的输出电压就会接近 0V 或供电电压。

模块上的 VCC 和 GND 就是电位器的两端供电,正常接 3.3V 和 GND。这里有个非常容易踩的坑:很多摇杆模块兼容 5V,但 Pico 的 ADC 参考电压是 3.3V,而且 ADC 引脚绝不是 5V 容忍引脚。如果你把 VCC 接到 5V,VRx 在高位时输出的电压会超过 3.3V,轻则读数长时间卡在 4095,重则直接烧坏 RP2040 的 ADC 输入级。实验级应用我还是建议统一用 3.3V 供电。

除了两个电位器,模块上还有一个 SW 引脚,对应摇杆垂直按下时的微动开关。按下时内部机械触点闭合,把 SW 引脚拉到 GND;松开时是悬空状态。所以这个按键需要外接上拉电阻,Pico 内部也有可编程上拉电阻,直接在初始化时用gpio_pull_up()打开即可。否则你会发现按键读数随机跳,一会儿高一会儿低。

2.2 Pico 的 ADC 硬件基础:12 位 SAR ADC 与采样限制

RP2040 内部集成的 ADC 是 12 位逐次逼近型 ADC,也就是面试八股里常说的 SAR ADC。它通过一个比较器逐位逼近输入电压,分辨率是 4096 个量化等级。Pico 的adc_read()返回 0 到 4095,0 对应 0V,4095 对应参考电压,而参考电压默认就是芯片的 3.3V 供电。

SAR ADC 的结构决定了它有几个实践上必须注意的细节。第一,采样过程会被“串扰”,如果输入源阻抗太高,采样保持电容来不及充满,读数就会偏低且不稳定。摇杆电位器的输出阻抗典型值是 10kΩ,并不算很低,所以在高精度场景下建议在 ADC 引脚对地并联一个 0.1μF 电容,给采样电容提供一个瞬时电荷池。第二,采样不是瞬间完成的,adc_read()内部要做采样和逐次逼近,连续读多个通道时中间要留一点时间,Pico SDK 本身已经处理了,但你自己在裸机编程时要注意采样周期不要给得太短。

之前看贺老师讲嵌入式那套 ADC 基础课时提到一个说法:ADC 读出来的值不是“真值”,而是“输入电压在参考电压下的量化投影”。这句话我特别认同。摇杆模块输出的是分压后的模拟电压,这个电压本身会随供电波动、随机械磨损漂移,所以后续必须做滤波和校准,不能把单次采样值当成真理。

2.3 接线示意图与供电注意事项

完整的接线特别简单,我用的是 Pico 的排针直插,没有额外转接板。接线对照关系如下:

摇杆模块引脚接 Pico 引脚说明
VCC3V3(OUT)供电,必须用 3.3V
GNDGND共地,不能省
VRxGP26 / ADC0X 轴模拟输出
VRyGP27 / ADC1Y 轴模拟输出
SWGP22按键输出,启用内部上拉

上电前最好拿万用表量一下 VCC 和 GND 之间有没有短路,再量一下 VRx 对 GND 的电阻,摇杆居中时应该在 5kΩ 左右,推到底会接近 0 或 10kΩ。这个动作看似多余,却能救你一次:我遇到过一盒模块里有两个是虚焊的,VRx 引脚碰一下就断,不量根本发现不了。

还有一个容易忽略的点,Pico 的 ADC 参考电压是芯片供电电压,不是独立的精密基准。如果你用 USB 供电,且同时带舵机或者电机,负载变化会导致 3.3V 波动,ADC 读数也会跟着抖。做这个摇杆实验没问题,但后续如果要驱动大电流外设,建议把模拟部分和数字部分分开供电,或者在软件里做参考电压补偿。

3. 先用最原始的方式读 ADC,再谈面向对象封装

3.1 裸读取:能跑,但问题很多

刚拿到模块时,我其实没写任何面向对象的逻辑,直接就是几行初始化代码加一个循环:

#include "pico/stdlib.h" #include "hardware/adc.h" #include "hardware/gpio.h" int main() { stdio_init_all(); adc_init(); adc_gpio_init(26); adc_gpio_init(27); gpio_init(22); gpio_pull_up(22); while (true) { adc_select_input(0); uint16_t x_raw = adc_read(); adc_select_input(1); uint16_t y_raw = adc_read(); uint16_t btn = !gpio_get(22); printf("x=%u y=%u btn=%u\n", x_raw, y_raw, btn); sleep_ms(10); } }

这段代码能在串口里看到数据,但它的问题很快暴露出来。首先是读数跳动明显,摇杆静止时 x 数值也能在十几二十个数之间来回跳;其次是不能区分“轻微偏离中心”和“用户真的有操作意图”,我又不想在应用层每次都用一堆if (abs(x - center) > threshold)去处理;按键更是没有消抖,按下一次经常会打印两次甚至三次。

更重要的是,这段代码只在这个 main 文件里能用,如果我新开一个舵机控制文件,又要重新复制一堆变量和逻辑,完全不可维护。这时候就轮到面向对象上场了。

3.2 C 语言的面向对象编程思路

很多人在嵌入式 C 里谈面向对象会本能地说“C 不支持 class”,但实际上 C 语言完全可以实现面向对象的三大核心概念:封装、继承、多态。封装就是结构体,把数据和操作这个数据的函数放一起;多态就是函数指针,调用方不需要关心具体实现;继承则可以通过结构体嵌套实现,虽然不如 C++ 优雅,但足够用。

网上有一份流传很广的资料叫《C 语言面向对象编程:嵌入式实战》,里面讲的正是这个思路。它没有玄学,核心套路就是“定义一个结构体作为对象的属性,再定义一组函数作为对象的方法,操作时把结构体指针传过去”。这样写出来的代码,每个外设都像一个小盒子,盒子有自己的状态,也有对外提供的接口,应用层只跟接口打交道。

用摇杆举例,我不需要担心应用层是舵机还是屏幕,它只要调用joystick_read(&joy)然后从joy.xjoy.y里拿值就行了。将来如果要把摇杆换成触摸板模拟输出,只要保证Joystick结构体对外接口不变,内部实现随便改,这就是封装带来的好处。

3.3 定义 Joystick 对象:结构体、操作函数、事件回调

下面这个结构体就是摇杆对象的雏形:

#define JOY_FILTER_N 16 typedef struct { uint8_t adc_ch_x; uint8_t adc_ch_y; uint8_t btn_gpio; uint16_t center_x; uint16_t center_y; uint16_t deadzone; uint16_t filter_buf[JOY_FILTER_N]; uint8_t filter_cnt; uint8_t filter_idx; int16_t x; int16_t y; uint8_t btn_state; uint8_t btn_event; void (*on_change)(int16_t x, int16_t y); void (*on_btn_press)(void); } Joystick;

字段分成三类:硬件配置、运行状态、事件回调。adc_ch_xadc_ch_ybtn_gpio是初始化时填的硬件映射;center_xcenter_y是校准后的中心值;filter_buf是滑动平均滤波的环形缓冲区;最后的on_changeon_btn_press是函数指针,相当于对象的事件接口。

你可能觉得函数指针很玄,其实它就是“把函数当作变量传进结构体”。比如在 main 里给摇杆绑定一个回调:joy.on_change = process_joystick_change;以后每次内部数据更新完,会自动调用process_joystick_change(),不需要在 while 循环里额外判断。这个模式在大型嵌入式项目里很常见,比如协议栈收到完整帧后触发一个回调,按键驱动检测到长按后触发另一个回调。

4. 核心代码实现:采样滤波、中心校准、按键消抖

4.1 为什么必须做滤波,滑动平均窗口怎么取

ADC 单次采样值抖动是正常现象。摇杆电位器本身有接触噪声,供电有纹波,电磁环境也会耦合进来。如果直接用原始值映射成舵机角度,舵机就会高频抖动,听起来像在“打嗝”。

最常见的滤波方式是滑动平均:维护一个缓冲区,每来一个新样本,就把它放进队尾,同时丢掉队头,输出取整个缓冲区的平均值。这个办法简单、实时性好,特别适合 ADC 这种周期性采样场景。代码实现如下:

static uint16_t moving_average(Joystick *js, uint16_t new_sample) { js->filter_buf[js->filter_idx] = new_sample; js->filter_idx = (js->filter_idx + 1) % JOY_FILTER_N; if (js->filter_cnt < JOY_FILTER_N) { js->filter_cnt++; } uint32_t sum = 0; for (uint8_t i = 0; i < js->filter_cnt; i++) { sum += js->filter_buf[i]; } return (uint16_t)(sum / js->filter_cnt); }

这里的窗口长度JOY_FILTER_N选择有讲究。窗口太大,平滑效果好但滞后明显,推杆后要很久才能反映到输出;窗口太小,滤波等于没做。我实测 8 到 16 之间比较合适,摇杆是人工操作,几十毫秒的响应延迟完全感知不到。我用 16 以后,静止时的抖动幅度从 ±15 压到了 ±3 以内,效果已经很好。

4.2 中心校准与死区处理

摇杆模块回中时,理论上 X、Y 都应该是 2048,实际上因为电位器制造误差和安装应力,中心值往往偏离几十甚至上百。所以需要在系统启动时让用户保持摇杆居中,读取 N 次取平均作为中心值。

校准函数可以这样设计:

void joystick_calibrate_center(Joystick *js) { uint32_t sum_x = 0; uint32_t sum_y = 0; uint8_t count = 20; for (uint8_t i = 0; i < count; i++) { adc_select_input(js->adc_ch_x); sum_x += adc_read(); adc_select_input(js->adc_ch_y); sum_y += adc_read(); sleep_ms(10); } js->center_x = sum_x / count; js->center_y = sum_y / count; js->filter_cnt = 0; js->filter_idx = 0; }

有了中心值,还要做死区处理。任何时候摇杆都不可能完美停在中心,手稍微抖一下,经过滤波后的值也会偏离中心几个数。如果不加死区,屏幕上的光标会轻微飘动,舵机也会小幅抖动。我在更新函数里做了这样的归一化:

int16_t dx = (int16_t)x_raw - (int16_t)js->center_x; if (dx > -js->deadzone && dx < js->deadzone) { dx = 0; } else { if (dx >= 0) dx -= js->deadzone; else dx += js->deadzone; }

死区大小我是默认 20,也就是大约 20 / 2048 ≈ 1% 的行程。太小了没效果,太大了会让摇杆在小角度操作时失去线性,手感很差。你也可以把死区值暴露成可配置参数,在调试阶段先用串口打印,观察实际数据,再现场调整。

4.3 按键消抖与事件输出

SW 按键是数字输入,但机械按键必然存在抖动。按下的一瞬间,触点会因弹性机械振荡产生几十毫秒的连续高低电平跳变,如果用gpio_get()直接读,很容易把一个“按下”误判成好几次。最轻量的消抖方案是“时间窗口确认”:检测到电平变化后,延时 5 到 20 毫秒再读一次,如果电平稳定,再认为按键状态真的变了。

我在摇杆对象里维护了一个上一状态字段:

uint8_t cur_btn = (gpio_get(js->btn_gpio) == 0) ? 1 : 0; if (cur_btn != js->btn_state) { sleep_ms(10); cur_btn = (gpio_get(js->btn_gpio) == 0) ? 1 : 0; if (cur_btn != js->btn_state) { js->btn_state = cur_btn; if (cur_btn == 1 && js->on_btn_press) { js->on_btn_press(); } } }

这里用了最朴素的延时函数消抖。因为更新循环本身是 10ms 一次,10ms 的延时不会造成明显卡顿。如果是在中断或实时性要求比较高的场景,建议改成状态机加时间戳的方式,不阻塞当前线程。对于这个摇杆实验,朴素的延时方案完全够用。

4.4 完整代码整合

其实上面几个功能拼起来,完整的更新函数大概长这样:

void joystick_update(Joystick *js) { adc_select_input(js->adc_ch_x); uint16_t raw_x = moving_average(js, adc_read()); adc_select_input(js->adc_ch_y); uint16_t raw_y = moving_average(js, adc_read()); // 去中心 + 死区处理 js->x = normalize(raw_x, js->center_x, js->deadzone); js->y = normalize(raw_y, js->center_y, js->deadzone); // 按键消抖与事件 uint8_t cur_btn = (gpio_get(js->btn_gpio) == 0) ? 1 : 0; if (cur_btn != js->btn_state) { sleep_ms(10); cur_btn = (gpio_get(js->btn_gpio) == 0) ? 1 : 0; if (cur_btn != js->btn_state) { js->btn_state = cur_btn; if (cur_btn == 1 && js->on_btn_press) { js->on_btn_press(); } } } if (js->on_change) { js->on_change(js->x, js->y); } }

main 函数就清爽了:初始化摇杆对象,绑定回调,然后在一个 10ms 循环里不停调用joystick_update(&joy)。中间那一堆滤波、校准、消抖细节全部被封装在模块内部,后面就算换到 STM32,也就是把adc_read()换成 HAL 库的HAL_ADC_GetValue(),外部的游戏逻辑不用动。

5. 接线、编译、烧录与调试实录

5.1 环境准备:Pico SDK 还是 Arduino

我在这个项目里用的是 Pico C SDK,因为要展示结构体和函数指针,C 是最合适的语言。环境搭建不复杂,核心步骤是拉取 SDK,配置PICO_SDK_PATH,然后写一个CMakeLists.txt

cmake_minimum_required(VERSION 3.13) include(pico_sdk_init.cmake) project(joystick_demo C CXX ASM) pico_sdk_init() add_executable(joystick_demo main.c joystick.c) target_link_libraries(joystick_demo pico_stdlib hardware_adc hardware_gpio) pico_add_extra_outputs(joystick_demo)

编译生成joystick_demo.uf2后,按住 Pico 上的 BOOTSEL 按钮插入 USB,松开按钮,把 uf2 文件拖进弹出的 U 盘,就烧录完成了。整个过程非常“傻瓜化”,这也是我推荐新手用 Pico 做 ADC 实验的原因之一,省去各种下载器接线。

如果你习惯 Arduino 环境,也可以把代码移植成 Arduino 框架。Pico 的 Arduino 核心已经把analogRead()封装好了,但要注意返回值范围。不同核心可能不一样,官方 arduino-pico 默认是 12 位,返回 0 到 4095,你需要先跑一个打印例程确认,而不是直接套用以前 AVR 板子的 0 到 1023。

5.2 实测数据:中心值、极限值、抖动幅度

我把编译好的固件烧进去,用串口助手观察了一组数据。摇杆自然回中时,X 轴大约在 1987 到 2011 之间跳,Y 轴大约在 2042 到 2059 之间跳,中心值并不在 2048 附近。这说明每个模块的机械回中精度都不一样,不校准直接用,一定会出现方向偏移。

把摇杆分别推到上下左右四个极限位置,读数分别接近 0 和 4095。X 轴推到底时接近 4095,Y 轴推到底时也接近 4095,但两端不是完全对称,有的轴最大值是 4088,最小值是 12。这是因为电位器行程的物理边界和电阻体不完全覆盖整个 ADC 量程。实际应用里不需要纠结这一点,反正映射到舵机角度或游戏坐标时,你会做归一化。

开启 16 点滑动平均滤波后,静止读数抖动从 ±15 左右降到了 ±3,效果非常明显。再开启 20 的死区处理后,静止时输出就是 0,不再有漂浮感。这个实验让我真正体会到了“硬件有噪声不可怕,软件滤波没思路才可怕”这句话。

5.3 我踩过的三个坑:共地、上拉、GPIO 复用

第一个坑就是供电不共地。最开始我图省事,用一个小面包板同时给摇杆和屏幕供电,结果摇杆模块的 GND 和 Pico 的 GND 之间没有完全连通,之间有几十欧姆的接触电阻。ADC 读到的数值会随着屏幕刷新而跳动,查了半天才发现是地线问题。记住:模拟电路里“共地”不是口号,是硬底线。

第二个坑是 SW 按键悬空。我一开始没有在软件里开内部上拉,SW 引脚直接浮空,GPIO 读到的值像抽奖一样。后来在gpio_init后立刻调用了gpio_pull_up(22),问题迎刃而解。如果你的摇杆模块比较特殊,板上已经带了上拉电阻,那开不开都行,但一般模块没有,所以建议统一在软件里打开内部上拉,成本最低。

第三个坑和 GPIO 复用有关。Pico 的 GP26、GP27 既是 ADC 引脚,也能当普通 GPIO 用。如果你在代码里先调用了gpio_set_function(26, GPIO_FUNC_SIO),后面再用adc_gpio_init(26),有时候不会报错,但确确实实会把引脚切回 ADC 功能。问题在于顺序不对会导致初始化失效。正确做法是用adc_gpio_init()统一设置,不要自己手动混用GPIO_FUNC_SIO

6. 常见问题速查与排查思路

6.1 常见问题表格

把我在实际调试中遇到的高频问题整理成了一张表,方便你遇到类似情况时快速定位。

现象可能原因排查办法
摇杆读数固定为 0摇杆 VCC 没接或 GND 断开用万用表量 VRx 对地电压
摇杆读数固定为 4095VCC 接到 5V 或 ADC 引脚短路确认 VCC 为 3.3V,断电检查接线
读数乱跳,和摇杆操作无关供电共地不良或滤波不足检查 GND 连通,增加滑动平均窗口
摇杆回中后输出不为 0没有做中心校准和死区处理调用中心校准,并将死区设为 20 左右
按键按一下触发多次缺少消抖增加 10ms 电平确认
按键状态完全不变SW 引脚悬空打开gpio_pull_up()
接上舵机后摇杆读数不稳舵机电流拉低 3.3V 参考电压摇杆和舵机分开供电,共地

这张表不是标准答案,但排查思路是通用的。ADC 类问题先查硬件通路,再查参考电压,最后才查软件逻辑,顺序不要反。

6.2 扩展:把摇杆数据映射到舵机控制

这个项目做完之后,最自然的扩展就是接一个舵机,做一个“摇杆控制云台”。舵机控制需要 50Hz 的 PWM,脉宽通常从 0.5ms 到 2.5ms 对应 0 到 180 度。Pico 的 PWM 模块可以直接产生这个信号。

映射思路很直观:经过死区处理后的 x 值范围大约在-20482048,把它线性映射到舵机脉宽即可:

uint16_t servo_pulse_from_x(int16_t x) { int32_t mapped = (int32_t)(x + 2048) * 2000 / 4096 + 500; return (uint16_t)mapped; }

这里mapped的单位是微秒,输出范围约 500us 到 2500us。你可以在实际调试时根据舵机品牌调整上下限。注意舵机是功率器件,尽量不要从 Pico 的 3.3V 引脚直接供电,否则会把 ADC 基准电压拉得乱七八糟。这也是我在 2.3 里强调供电分离的后续延续。

写到最后,再分享一个我个人的建议:这类实验不要只看代码跑通就结束。你可以在串口里盯着原始值,把手放在摇杆上慢慢推,感受数值连续变化的过程;也可以故意把滤波关掉,听一听舵机发出的抖动声。只有亲手对比过“滤波前”和“滤波后”的差别,才能真正理解为什么要做这些处理。嵌入式说到底是一门实践的功夫,ADC 尤其如此,多花时间在观察数据和修正参数上,会比背一百个面试题更有用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 10:32:52

STM32 MPU6050滤波与姿态解算实战:滑动窗口、低通滤波到互补滤波

1. 项目概述与需求拆解1.1 MPU6050这颗传感器&#xff0c;到底难在哪玩过 stm32 的人&#xff0c;十有八九都碰过 mpu6050 这颗六轴传感器。它便宜、资料多、例程遍地都是&#xff0c;但也正因为用的人多&#xff0c;大家踩过的坑才格外五花八门——尤其是"滤波"这两…

作者头像 李华
网站建设 2026/9/6 10:30:47

基于MicroPython的ADS1115高精度ADC驱动与滤波实践

如果你手里正好有一块ADC模块&#xff0c;又刚好在MicroPython环境里做数据采集&#xff0c;那这篇内容应该能帮你少走不少弯路。我这次整理的是ADS1115这颗16位ADC从硬件接线、I2C通信、寄存器驱动&#xff0c;到采样触发和滤波处理的完整实践记录。文章里所有代码我都在ESP32…

作者头像 李华
网站建设 2026/9/6 10:30:35

用嵌入式开发DIY美人鱼主题智能手链:硬件拆解与制作指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 10:25:47

深入CMSIS-DSP:从源码审计到工业固件落地的完整实践指南

1. 这套库到底解决什么问题&#xff1a;从项目背景和架构全景说起如果你在 Cortex-M 内核上做过任何形式的数字信号处理&#xff0c;比如 FIR/IIR 滤波、FFT 频谱分析、矩阵求逆&#xff0c;或者 PID 控制器里的微分项平滑&#xff0c;那你大概率已经听说过 CMSIS-DSP。这是 AR…

作者头像 李华
网站建设 2026/9/6 10:25:03

AMS2471J铝合金硫酸阳极氧化标准解读:工艺要求与实用要点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 10:23:01

UL 510A胶带选型全解析:从认证标准到产线品控实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华