尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

树莓派Pico摇杆模块实战:ADC读取、滤波与模块化驱动设计

树莓派Pico摇杆模块实战:ADC读取、滤波与模块化驱动设计 最近在做一期基于树莓派Pico的小项目顺手把摇杆Joystick读取这块完整重新梳理了一遍。嵌入式里的ADC读取单看原理好像不复杂但真要从硬件结构到代码组织都做得稳、做得像样还是有不少门道。这篇文章我会从摇杆模块内部结构讲起把Pico的ADC通道、实际接线、以及一套用C语言模拟面向对象思路的驱动代码全部过一遍。适合已经会点灯、跑过串口但准备往传感器采集和模块化代码方向进阶的同学。1. 先搞清楚摇杆模块到底是个什么东西很多朋友拿到摇杆模块上来就接GPIO却发现数值各种怪异关键还是没看明白模块内部到底长什么样。摇杆模块不是数字设备它本质上就是一个“两轴电位器 一个按键”的组合理解清楚这层结构后面接线和代码才有依据。1.1 摇杆内部结构两个电位器加一个按键市面上常见的PS2摇杆模块正面是摇杆帽背面通常有5个引脚VRx、VRy、SW、VCC和GND。摇杆往不同方向拨动实际上是在转动两个互相垂直的电位器转轴。电位器你可以直接理解成一个可调电阻分压器一边接VCC一边接GND中间抽头输出一个随旋转位置变化的电压。摇杆在X轴上的位置决定VRx引脚输出电压Y轴位置决定VRy引脚输出电压。这两个电压都是模拟量范围从0V到VCC。比如VCC接的是3.3V那么摇杆从一端拨到另一端VRx的电平大约会从0V平滑变化到3.3V中间位置附近一般在1.65V左右。SW引脚则是一个普通按键往下压摇杆帽的时候内部触点接通输出电平发生跳变用来做“确认”或“按下”操作。这里有一个很容易被忽略的细节很多摇杆模块PCB上还带了LED指示灯和一个10kΩ左右的限流电阻这是给指示灯用的。LED和信号输出是两个完全独立的电路但有些模块的LED会直接吃VCC电流如果你用Pico的3.3V给模块供电问题不大如果电源走的是5V信号线上的高电平也会被拉到接近5V这是后面接线要避开的坑。1.2 ADC采样原理与几个关键参数既然摇杆输出的是模拟电压Pico要“看懂”它就需要ADC模数转换器。RP2040内部集成了一个12位逐次逼近型ADCSAR ADC工作频率默认由PLL提供典型时钟48MHz转换时通过可编程分频器调节。逐次逼近型ADC的核心思路有点像是“二分猜数字”先用一半参考电压和输入比较高了还是低了再折半逼近12位分辨率就需要12次比较。好处是结构简单、功耗低、响应快缺点是抗干扰能力一般对参考电压和电源噪声比较敏感。对摇杆这种缓慢变化的信号来说采样速率根本不是瓶颈。RP2040的ADC单次转换时间在2μs量级哪怕你要每秒采几千次都绰绰有余。真正的瓶颈反而是参考电压的稳定性和整个采样链路中的噪声。你需要记住三个关键参数分辨率12位ADC读数的原始范围是0~4095。参考电压VREF默认等于供电电压3.3V。也就是说0V对应ADC值03.3V对应4095。采样周期ADC在两次转换之间需要一定时间。RP2040里每个通道可以单独设置采样时间默认用内部CAPTURE寄存器配置。对摇杆实验采样周期设成几微秒到几十微秒都够用。有人会问既然Pico的ADC是12位为什么串口打印出来经常不是稳定的值而是一堆跳动的数字这件事放到后面滤波部分细讲先记住结论ADC读出来的“原始值”并不等于“真实值”它是带有噪声的待处理信号。1.3 为什么要用面向对象的方式去写驱动很多Pico入门教程写摇杆都是直接在main函数里初始化GPIO和ADC然后一个while循环里不停读数值。初看很直观但一旦你要同时接两个摇杆或者再用同一套逻辑去控制舵机、小车代码就会迅速膨胀成一片浆糊。我之前尝试过一个项目主函数里塞了摇杆初始化、滤波、按键扫描、PWM输出结果换个硬件平台或者加一个功能就要复制粘贴一大段。后来我花了点时间用C语言结构体加相关操作函数把摇杆封装成一个“对象”整个思路立刻清晰了。这也是很多嵌入式开发团队在C代码里实践“面向对象”的常见方式不用C不用复杂继承而是利用结构体组织数据、用函数指针组织行为照样能把模块化思想落地。这种做法的核心价值有三个第一每个对象拥有自己独立的状态不会被全局变量污染第二初始化、读取、校准等行为都集中到一组函数里调用方只需要关心接口第三跨模块复用的时候不需要修改内部实现只需要创建新对象实例。后面我会展示一套完整的C代码可以边看边体会。2. Pico摇杆硬件接线与应用场景分析原理解清楚之后接下来把Pico和摇杆实打实接起来。很多新手在接入时容易踩到电源电压和ADC参考电压不匹配的坑这一节我会把接线图纸直接换成文字表格照着连就行。2.1 RP2040的ADC通道与GPIO引脚对应关系树莓派Pico使用的是RP2040芯片内部ADC一共有5个输入通道但并非所有通道都引到GPIO引脚。Pico板子上实际可用的ADC通道如下ADC通道GPIO引脚可以接什么ADC0GP26通用模拟输入摇杆X轴、电位器等ADC1GP27通用模拟输入摇杆Y轴等ADC2GP28通用模拟输入第三个模拟传感器ADC3GP29板上VSYS电压采样通过分压内部通道4无板载温度传感器不可外部接信号特别注意GP29通常用来测电池电压或USB电压因为它被板子上的分压电路占用。如果你把外部信号强行接到GP29上会和VSYS采样冲突得到的数值是外部信号和系统电压混合后的结果没什么意义。Pico虽然有多路ADC通道但芯片内部只有一个ADC转换器。它实际上是通过模拟开关快速切换不同通道来“分时复用”的所以官方数据手册里也强调多次采样时如果切换通道最好丢弃第一次转换结果因为模拟开关切换后需要一点稳定时间。2.2 摇杆实际接线方案我这边用的是一个典型PS2摇杆模块一共5个引脚。推荐接线如下摇杆模块引脚接到Pico哪里说明VCC3.3V供电压跟ADC参考电压一致GNDGND公共地必须接VRxGP26对应ADC0读X轴VRyGP27对应ADC1读Y轴SWGP22数字输入配内部上拉这里最想强调的一点VCC必须接3.3V不要接5V。RP2040的ADC参考电压是3.3V如果模块供电用5VVRx/VRy在高位时输出会逼近5V远超ADC允许输入范围轻则读数一直卡在4095重则损坏GPIO内部保护二极管。很多“接线明明对了但读值不对”的问题十有八九是供电电压用错了。至于SW按键引脚模块内部通常已经接了一个上拉电阻到VCC。按下摇杆时SW引脚会被拉到GND所以Pico端要启用GPIO内部上拉读取逻辑为按键未按下读到高电平按下读到低电平。有些人用软件轮询有些人用中断但对于摇杆这种轻触按键轮询加简单去抖就够了。2.3 供电、参考电压和噪声处理所有ADC应用都躲不开噪声。Pico的3.3V是由板载RT6150B buck-boost电源芯片提供的纹波通常在几十毫伏以内直接给摇杆供电基本没问题。但如果你同时驱动舵机、电机或其他大电流负载电源就会被拉出尖峰反映到ADC就是采样值乱跳。这时最好在摇杆模块的VCC和GND引脚之间加一个10μF电解电容和一个0.1μF陶瓷电容做电源去耦模拟地和数字地保持单点连接能减少很多诡异问题。另外一个经验是ADC引脚的导线不要和PWM线、舵机信号线扎在一起。PWM高速切换会产生串扰摇杆这种高阻模拟信号特别容易被干扰。我试过把摇杆信号线和舵机线绑在一起走结果摇杆不动ADC值也会在中间20到30个LSB范围内来回跳后来把线分开后静下来了。硬件上的一点小细节往往比软件里折腾十天半月的滤波更有效。3. 面向对象代码设计把摇杆封装成一个“类”这节是重头戏。我用C语言配合Pico SDK写一个模块化的摇杆驱动并把面向对象思路落地。你看到的这套代码不是标准库也不是什么高深框架而是嵌入式里非常实用的“结构体 方法函数”封装风格。哪怕你以后换到STM32、ESP32这套思想也完全能平移过去。3.1 驱动文件结构与设计原则先定一个简单目录结构pico_joystick_demo/ ├── main.c ├── joystick.c └── joystick.h这样的分层很直接joystick.h负责对外暴露接口和类型定义joystick.c负责具体实现main.c只负责调用。以后要改滤波算法或者换引脚只动joystick.c和joystick.hmain.c基本不用碰。设计原则可以总结成一句话数据私有、行为接口化。所谓“数据私有”是指摇杆内部每个引脚的配置、当前值、校准偏移量都以结构体成员的方式存在不让外部代码直接乱改。“行为接口化”是指所有操作都通过形如Joystick_Init、Joystick_Read这样的函数来完成调用方不用关心实现细节。3.2 joystick.h 头文件设计先看头文件#ifndef __JOYSTICK_H__ #define __JOYSTICK_H__ #include pico/stdlib.h #include hardware/adc.h #define JOYSTICK_ADC_RESOLUTION 4095u #define JOYSTICK_DEAD_ZONE 16 typedef enum { JOYSTICK_AXIS_X 0, JOYSTICK_AXIS_Y } JoystickAxis; typedef struct { uint8_t adc_pin_x; // 对应GPIO引脚 uint8_t adc_pin_y; // 对应GPIO引脚 uint8_t btn_pin; // SW按键引脚编号 uint16_t raw_x; // 最新的X轴原始ADC值 uint16_t raw_y; // 最新的Y轴原始ADC值 bool btn_state; // 当前按键状态true表示按下 int16_t offset_x; // X轴中心校准偏移 int16_t offset_y; // Y轴中心校准偏移 } Joystick; // 初始化摇杆对象需要在调用前配置好结构体中的引脚 void Joystick_Init(Joystick *js); // 同步读取所有模拟值和按键状态 void Joystick_Read(Joystick *js); // 获取带死区的归一化值范围 -1.0 ~ 1.0 float Joystick_GetX(const Joystick *js); float Joystick_GetY(const Joystick *js); // 校准中心位置 void Joystick_CalibrateCenter(Joystick *js); #endif看到这里你可能已经嗅到点“类”的味道了。Joystick结构体就是数据成员Joystick_Init、Joystick_Read这些函数就是成员方法。调用方拿到一个Joystick变量就等于拿到一个“摇杆对象”。上电后先赋好引脚号再调用Joystick_Init后续读取完全不碰寄存器细节这就是接口带来的好处。3.3 joystick.c 核心实现下面是实现文件。我特意把“滑动平均滤波”放进读取逻辑里让代码开箱即用。这里的滑动平均不是简单的N点取均值而是维护一个累加器每来一个新值先减掉最旧值再加上新值然后求平均。这样比每轮都重新求和效率高很多适合嵌入式循环高频调用。#include stdlib.h #include string.h #include joystick.h #define SAMPLE_BUF_SIZE 8 typedef struct { uint16_t buf[SAMPLE_BUF_SIZE]; uint8_t index; uint32_t sum; } AverageFilter; static AverageFilter filter_x; static AverageFilter filter_y; static uint16_t adc_read_filtered(AverageFilter *f, uint8_t adc_channel) { uint16_t val adc_read(); f-sum - f-buf[f-index]; f-buf[f-index] val; f-sum val; f-index (f-index 1) % SAMPLE_BUF_SIZE; return (uint16_t)(f-sum / SAMPLE_BUF_SIZE); } void Joystick_Init(Joystick *js) { adc_init(); gpio_init(js-btn_pin); gpio_pull_up(js-btn_pin); gpio_set_dir(js-btn_pin, GPIO_IN); // 配置X轴通道 adc_gpio_init(js-adc_pin_x); // 配置Y轴通道 adc_gpio_init(js-adc_pin_y); memset(filter_x, 0, sizeof(filter_x)); memset(filter_y, 0, sizeof(filter_y)); // 默认先读几轮数据给滤波缓存充盈 for (int i 0; i SAMPLE_BUF_SIZE; i) { adc_select_input(js-adc_pin_x - 26); adc_read_filtered(filter_x, js-adc_pin_x - 26); adc_select_input(js-adc_pin_y - 26); adc_read_filtered(filter_y, js-adc_pin_y - 26); } js-offset_x 0; js-offset_y 0; js-btn_state false; Joystick_CalibrateCenter(js); } void Joystick_Read(Joystick *js) { // 注意这里假设adc引脚都是ADC0/ADC1通道号等于GPIO号-26 adc_select_input(js-adc_pin_x - 26); js-raw_x adc_read_filtered(filter_x, js-adc_pin_x - 26); adc_select_input(js-adc_pin_y - 26); js-raw_y adc_read_filtered(filter_y, js-adc_pin_y - 26); // 按键是低有效按下读到低电平 js-btn_state (gpio_get(js-btn_pin) 0); } float Joystick_GetX(const Joystick *js) { // 归一化后范围约为 -1.0 ~ 1.0带中点偏移和死区 int32_t x (int32_t)js-raw_x - 2048 js-offset_x; if (x -JOYSTICK_DEAD_ZONE x JOYSTICK_DEAD_ZONE) { return 0.0f; } if (x 0) { x - JOYSTICK_DEAD_ZONE; } else { x JOYSTICK_DEAD_ZONE; } float norm (float)x / 2048.0f; if (norm 1.0f) norm 1.0f; if (norm -1.0f) norm -1.0f; return norm; } float Joystick_GetY(const Joystick *js) { int32_t y (int32_t)js-raw_y - 2048 js-offset_y; if (y -JOYSTICK_DEAD_ZONE y JOYSTICK_DEAD_ZONE) { return 0.0f; } if (y 0) { y - JOYSTICK_DEAD_ZONE; } else { y JOYSTICK_DEAD_ZONE; } float norm (float)y / 2048.0f; if (norm 1.0f) norm 1.0f; if (norm -1.0f) norm -1.0f; return norm; } void Joystick_CalibrateCenter(Joystick *js) { // 连续采集多组数据取平均值作为中心偏移 int16_t base_x 2048; int16_t base_y 2048; if (Joystick_Read) { // 这里为了校准临时读原始值不经过偏移 adc_select_input(js-adc_pin_x - 26); uint16_t sum_x 0; adc_select_input(js-adc_pin_y - 26); uint16_t sum_y 0; for (int i 0; i 16; i) { adc_select_input(js-adc_pin_x - 26); sum_x adc_read(); adc_select_input(js-adc_pin_y - 26); sum_y adc_read(); } base_x sum_x / 16; base_y sum_y / 16; } js-offset_x 2048 - base_x; js-offset_y 2048 - base_y; }这个实现有几个地方是经验之谈我展开说一下。第一adc_gpio_init函数会完成GPIO复用和模拟输入使能但不会替你选择ADC通道。RP2040的ADC多通道共享一个转换器每次转换前必须调用adc_select_input选中要采的通道。我代码里用adc_pin_x - 26得到通道号因为GP26对应ADC0、GP27对应ADC1。你如果换了引脚记得保证引脚在26到29这个范围内。第二滑动平均滤波器我直接定义成了static全局量放在joystick.c内部。这意味着这个驱动文件目前只支持一个摇杆实例。如果你要支持两个摇杆就需要把滤波器塞进Joystick结构体因为每个对象都要有自己独立的滤波状态。这是典型的“全局变量面向对象不彻底”的问题你可以试着改进它。第三校准函数稍微有点粗糙但在实际项目里挺好用。上电时摇杆应该处于自然居中状态这时候采集16次取平均值作为“机械中心”。后续归一化时用这个偏移量把X/Y中心拉回到2048附近。摇杆用久了中心漂移也可以通过调用这个函数重新校准。3.4 main.c 调用示例调用端代码非常简洁#include stdio.h #include pico/stdlib.h #include joystick.h int main(void) { stdio_init_all(); sleep_ms(1000); Joystick js; js.adc_pin_x 26; js.adc_pin_y 27; js.btn_pin 22; Joystick_Init(js); while (1) { Joystick_Read(js); printf(X: %.2f Y: %.2f Btn: %d\n, Joystick_GetX(js), Joystick_GetY(js), js.btn_state ? 1 : 0); sleep_ms(10); } }就这几行完成了初始化、读取、打印全套流程。以后你想改滤波参数、换引脚、加通道基本上都不会动main.c。这种解耦方式在代码规模小的时候看不出优势一旦工程到几千行维护成本差距会特别明显。4. ADC数据滤波与校准实操中真正拉开差距的部分ADC读数和滤波这关是摇杆实验里最“磨人”但也最“涨功”的地方。很多同学第一次跑起来看到串口输出的X轴数值在1800和2200之间来回跳第一反应是怀疑硬件坏了。其实不是这是ADC的常态。4.1 为什么RAW值会跳RP2040的ADC是12位但最后几位并不是完全稳定的。主要原因有三个一是参考电压本身有微小纹波持续影响转换结果二是电位器碳膜表面的接触电阻会随着拨动产生微小变化三是芯片内部采样保持电容在充电过程中会引入少量噪声。这些噪声反映到数值上通常就是几个到几十个LSB的抖动。如果你用手轻轻碰摇杆模块的金属外壳很可能看到数值波动更明显这是因为人体耦合了工频干扰进去。摇杆信号本质上是接近直流的慢变量高频噪声带宽很宽所以必须靠软件滤波把噪声压下去。4.2 常用ADC滤波函数对比我试过三种滤波方式这里把特点列出来滤波方式实现难度实时性效果滑动平均低中等需缓存N个样本能明显平滑但对突发毛刺抑制一般中值滤波低中等需排序对脉冲毛刺效果好但连续抖动抑制一般一阶低通滤波最低高只需两个变量平滑效果不错但会有相位滞后滑动平均的代码已经在joystick.c里实现了。这里再补充一个一阶低通滤波的参考实现它更省RAM适合在定时中断里使用// alpha取0~1.0越接近0滤波越强动态响应越慢 #define ALPHA 0.3f static float filtered_value 0.0f; float lowpass_filter(float raw) { filtered_value filtered_value ALPHA * (raw - filtered_value); return filtered_value; }这个滤波器的原理可以理解成“新值只信30%旧值保留70%”。alpha越小输出越平滑但是摇杆快速拨动时数值跟手程度会变差。对摇杆这种信号alpha取0.2到0.4比较合适。中值滤波适合去除异常的尖峰比如电机启动瞬间造成的干扰。实现方式一般是对N个样本排序后取中间值。缺点是排序耗时如果放在高频循环里建议N别取太大5到7就够。在实际项目中我常用“滑动平均 一阶低通”组合拳先用8点滑动平均把大部分白噪声削掉再用alpha0.3的一阶低通做最终平滑。代价是内存和CPU多一点但对Pico来说绰绰有余。4.3 中点偏移校准与映射除了抖动另一个常见问题就是“回不到中点”。摇杆模块在出厂时机械中心对应的输出电压并不严格等于VCC的一半。也就是说把摇杆放到自然位置ADC读数可能是2050也可能只有2020。如果程序直接把ADC值除以4095再乘以3.3V去换算电压误差不大但如果你要映射到-1.0到1.0的摇杆坐标中心点误差就会被放大。我一般的做法是上电后自动校准摇杆居中连续采样16到32次取平均作为当前设备的中心值。代码里的Joystick_CalibrateCenter函数就是干这个的。校准后的偏移量保存在offset_x和offset_y里后续每次读取都先用raw_x减去偏移再做归一化。死区也很重要。摇杆即使“居中”数值也不可能完全稳定在同一个点。如果没有死区程序在摇杆没碰的情况下可能会不断输出一个很小的非零值导致小车自己缓慢漂移、云台朝一侧抖。我设置的死区是±16换算到归一化坐标大约是±0.0078。用的时候可以根据实际手感调整死区设太大摇杆会变“钝”设太小又起不到防抖作用。5. 常见问题排查与经验备忘在实际调试摇杆的时候绝对绕不开各种奇怪现象。我把这几年遇到的高频问题和排查思路整理成一个表方便你对照。现象可能原因排查方法ADC值一直4095或0VCC用了5V导致超量程或者地线没接好或者选错了通道先万用表量VRx对地电压再确认adc_select_input通道号是否正确X轴和Y轴互相干扰两个通道切换太频繁模拟开关没稳定滤波缓存交叉污染每次切换通道后丢第一次采样增加采样周期检查滤波缓存是否独立SW按键一直低电平引脚上拉没开模块内部焊点问题按键被卡住加gpio_pull_up用万用表测量SW对地电压换一个引脚验证数据波动特别大电源纹波大、导线干扰加去耦电容信号线远离PWM线降低采样周期增强滤波代码运行到一半卡死结构体未初始化就调用读取函数ADC通道号越界确保调用Joystick_Init前给引脚赋值检查引脚号是否在26~29摇杆回中后输出非零中心偏移未校准调用Joystick_CalibrateCenter检查死区配置这里想单独提一个很容易被忽略的调试坑串口打印函数不要太频繁。如果while循环里不加延迟以RP2040主频133MHz跑串口打印会占掉大量时间而你在电脑上看到的数值都是经过漫长的串口缓冲排队之后的“过时数据”反而会把现象带偏。调试时建议打印频率控制在50Hz到100Hz也就是循环里加sleep_ms(10)或sleep_ms(20)。另外观察ADC线性度有一个非常土但很好用的方法把摇杆拨到最左端记下ADC值再拨到最右端记下ADC值然后慢慢推过整个行程看串口打印的值是否单调递增。如果中间出现跳变或倒退多半是模块的电位器碳膜有问题或者接线接触不良。这个方法能帮你快速判断是硬件问题还是软件问题。还有一个面向对象封装里容易踩的坑结构体里的函数指针如果有的话一定要在Init里正确赋值否则调用时会跳到随机地址直接hardfault。我最初的驱动版本就用了函数指针来模拟多态结果有一个对象忘记调用Init板子一运行就死掉。后来我干脆把接口简化成普通函数把变化点集中在结构体数据里避免为了“像面向对象”而过度设计。这个度要自己把握。6. 扩展玩法摇杆控制舵机、简单手柄摇杆实验做完别急着收工它最大的价值在于可以作为“输入设备”驱动其他模块。这里说几个我实测过的高频扩展方向。6.1 用摇杆控制Pico舵机树莓派Pico有硬件PWM控制舵机很简单。核心逻辑就是把摇杆的归一化坐标映射到舵机脉宽范围。市面上常见舵机如SG90要求PWM周期20ms高电平脉宽从0.5ms到2.5ms对应0°到180°。如果你让X轴控制水平舵机Y轴控制垂直舵机只需要把Joystick_GetX返回的-1.0到1.0映射到0.5ms到2.5ms即可。uint16_t map_servo_pulse(float joy_value) { // 映射到 -90° ~ 90° 的概念方便转向 float angle joy_value * 90.0f; float pulse_us 1500.0f angle * 11.11f; // 约11.11us/度 if (pulse_us 2500.0f) pulse_us 2500.0f; if (pulse_us 500.0f) pulse_us 500.0f; return (uint16_t)pulse_us; }这个公式就是把舵机线性近似处理了。实际舵机脉宽和角度的对应关系可能有一点非线性但在大多数DIY场景里完全够用。接舵机时舵机电源最好单独供电尤其是摇杆模块和Pico共用一个3.3V时驱动舵机瞬间大电流会把ADC参考电压拉垮读数会猛然跳变。实测教训是至少把舵机电源和摇杆模块电源在物理上分开一点或者加一个大电容。6.2 多摇杆组合时的ADC分配策略如果你想做一个双摇杆手柄比如左摇杆控制移动、右摇杆控制视角就需要考虑RP2040的ADC通道分配。Pico只有ADC0、ADC1、ADC2三个外部可用通道而一个摇杆要占2个通道双摇杆就需要4个通道明显不够用。常见的解决方案有三种第一外接模拟多路开关CD4051用GPIO控制通道选择线扩展ADC输入路数第二改用带更多ADC通道的板子比如RP2040的兄弟芯片或STM32系列第三把摇杆按键单独放到数字GPIO只把X/Y模拟量接入ADC0~ADC2双摇杆只采X、Y另一个摇杆舍弃一个轴。第三种方案最省钱但体验打折。6.3 和STM32等平台横向迁移这套面向对象的设计思路可以很轻松地迁移到STM32等其他平台。STM32的HAL库本身就提供ADC多通道DMA采样你甚至可以让DMA自动轮流采集两个通道然后软件里用我们这套结构体去保存数据。32位MCU上把Joystick结构体稍微改一下把adc_pin_x改成ADC_HandleTypeDef指针和Channel编号即可。核心思想不变模块只暴露初始化、读取和获取坐标的接口内部爱用DMA还是中断都行上层完全无感。说实话摇杆实验的电路不难难的是把代码组织得清晰、可靠、可复用。我在实际写的过程中因为图省事一开始把滤波、初始化全塞在主函数里结果想复用的时候只能复制粘贴后来改成结构体封装整个思路一下就顺了。最后再分享一个小技巧给每个ADC采样通道固定一个采样周期不要频繁切换通道一旦你发现读数异常先把滤波和串口打印频率降下来八成问题就能定位到。嵌入式就是这么一个需要“较真”的领域把ADC这关过了后面很多传感器项目都会顺很多。
返回列表