4.19 立创·梁山派GD32F470驱动X9C103S数字电位器模块实战(按键控制阻值调节)
立创·梁山派GD32F470驱动X9C103S数字电位器模块实战(按键控制阻值调节)
最近在做一个需要调节模拟信号的项目,比如音量或者LED亮度,用传统的机械电位器总觉得不够“智能”,每次调节还得手动拧。后来发现了X9C103S这种数字电位器,完全可以用单片机通过程序来控制阻值,特别适合用在需要自动调节或者远程控制的场合。正好手头有立创的梁山派开发板(主控是GD32F470ZGT6),今天就带大家一步步实现用梁山派驱动X9C103S模块,并且通过两个按键来控制阻值增加和减少。
这个教程非常适合刚接触嵌入式硬件驱动或者想了解数字电位器用法的朋友。我会从模块原理讲起,然后手把手教你怎么接线、写代码,最后实现按键控制。跟着做下来,你就能掌握这个实用的技能了。
1. 认识X9C103S数字电位器模块
在动手之前,咱们先搞清楚要驱动的对象是什么。X9C103S本质上是一个可以用数字信号控制的电阻,你可以把它想象成一个“电子版”的旋钮电位器。
1.1 模块是什么?能干什么?
这个模块的核心是一颗X9C103芯片。它内部有一个总阻值为10kΩ的电阻阵列,但这个电阻被分成了99份,加上两个端点,一共是100个抽头点。滑动端(就是可移动的触点)可以连接到这100个点中的任意一个,从而改变VW(滑动端)与VL或VH之间的电阻值。
它能干什么呢?
- 模拟信号调节:比如音频设备的音量控制、显示屏的背光亮度调节。
- 电压基准设定:为一些需要可调参考电压的电路提供基准。
- 替代机械电位器:在需要程序控制、记忆位置或者防尘防震的场合使用。
模块工作电压是3V到5V,和咱们的梁山派(通常用3.3V或5V)可以直接匹配,非常方便。
1.2 核心控制引脚与时序
模块和单片机打交道,主要靠三个控制引脚,它们就像给这个“电子电位器”下达命令的按钮:
| 引脚名称 | 功能描述 |
|---|---|
| CS (片选) | 片选信号,低电平有效。只有CS为低时,模块才“听”其他引脚的命令。 |
| INC (增量) | 脉冲输入引脚,下降沿触发(即电平从高变低时有效)。每来一个下降沿,滑动端就移动一个台阶。 |
| U/D (方向) | 控制滑动端移动方向。高电平时,滑动端向VH方向移动(阻值增加);低电平时,向VL方向移动(阻值减小)。 |
这三个引脚电平状态的组合,决定了模块处于哪种工作模式,原文的表格总结得非常清楚:
| CS | INC | U/D | 工作方式 |
|---|---|---|---|
| 低 | 下降沿 | 高 | 滑动端向上移动(阻值增加) |
| 低 | 下降沿 | 低 | 滑动端向下移动(阻值减小) |
| 上升沿 | 高 | 任意 | 存储当前位置到非易失存储器 |
| 高 | 任意 | 任意 | 等待模式(低功耗) |
| 上升沿 | 低 | 任意 | 不存储,返回等待模式 |
注意:这里说的“向上/向下”滑动,是相对于U/D引脚选择的电气方向。简单理解就是,U/D=1,滑动端靠近VH,VW和VL之间的电阻变大;U/D=0,则相反。
最关键的一点:模块有记忆功能!当你完成调节后,在INC为高、CS从低变高(上升沿)的瞬间,当前的滑动端位置会被保存到芯片内部的非易失存储器中。下次模块重新上电,它会自动恢复到上次保存的位置,这个特性非常实用。
2. 硬件连接:把模块接到梁山派上
知道了原理,接下来就是动手连接。根据原文提供的引脚对照表,连接非常简单。
2.1 引脚连接对照表
你需要准备杜邦线,按照下表将X9C103S模块与梁山派开发板连接起来:
| X9C103S模块引脚 | 梁山派开发板引脚 | 说明 |
|---|---|---|
| VCC | 5V | 模块电源正极 |
| GND | GND | 模块电源地,与梁山派共地 |
| CS | PA4 | 片选控制信号 |
| INC | PA2 | 增量脉冲信号 |
| U/D | PA3 | 方向控制信号 |
| VH | 5V | 电阻高端,接电源 |
| VL | GND | 电阻低端,接地 |
| VW | 悬空或接你的电路 | 滑动端,输出可调电压/电阻 |
同时,我们还需要两个按键:
- KEY_UP (上调键)连接到PA0
- KEY_DOWN (下调键)连接到PA1
提示:VH和VL这里分别接了5V和GND,这意味着滑动端VW的输出电压范围是0V-5V。如果你需要其他电压范围,可以改变VH和VL的接法。VW引脚就是你要用的“可调端”,可以接到后续电路的输入。
2.2 连接示意图与注意事项
连接好之后,硬件部分就准备好了。有几点需要注意:
- 电源一定要共地:模块的GND必须和梁山派的GND连接在一起,这是所有信号正常工作的基础。
- VH/VL电压范围:芯片手册规定VH和VL之间的电压不能超过±5V。我们这里接的是0-5V,是安全的。
- 滑动端电流:VW引脚最大只能输出或吸入1mA电流,所以不能直接驱动大负载(比如电机、大功率LED)。它通常用于提供控制信号,比如接到运放、比较器或者MOS管的栅极。
3. 软件驱动:编写控制代码
硬件连好了,现在让梁山派“发号施令”。我们按照功能模块来编写代码。
3.1 引脚定义与初始化 (x9c103s.h & x9c103s.c)
首先,我们创建一个头文件来定义引脚和函数。
x9c103s.h
#ifndef _BSP_X9C103S_H #define _BSP_X9C103S_H #include "gd32f4xx.h" #include "systick.h" // 定义控制引脚对应的GPIO #define RCU_X9C103S RCU_GPIOA // 使用GPIOA的时钟 #define PORT_X9C103S GPIOA // 使用GPIOA端口 #define PIN_INC GPIO_PIN_2 // INC信号 -> PA2 #define PIN_U_D GPIO_PIN_3 // U/D信号 -> PA3 #define PIN_CS GPIO_PIN_4 // CS信号 -> PA4 // 声明外部变量,用于按键标志位 extern char flag_UP; extern char flag_DO; // 函数声明 void delay_us(uint32_t i); // 微秒延时函数 void x9c103s_gpio_config(void); // 初始化控制引脚 void volume(void); // 电位器控制主函数 #endif接下来是源文件,我们先实现一个精准的微秒延时函数,因为控制时序对延时有一定要求。
x9c103s.c (部分1: 延时函数)
#include "x9c103s.h" // 全局变量,用于控制每次调节的步数(台阶数) unsigned int STEP = 2; /** * @brief 微秒级延时函数 * @param i: 需要延时的微秒数 * @note 基于SysTick定时器实现,时钟频率假设为120MHz。 * 计算公式:LOAD = (时钟频率/1000000) * i - 1 * 这里120MHz / 1MHz = 120,所以是120*i。原文代码为9*i,适用于72MHz。 * 请根据你的系统时钟调整。 */ void delay_us(uint32_t i) { uint32_t temp; // 设置重装载值。如果你的系统时钟是120MHz,这里应该是 120*i SysTick->LOAD = 120 * i; SysTick->CTRL = 0x01; // 使能SysTick,使用内核时钟 SysTick->VAL = 0; // 清空当前值 do { temp = SysTick->CTRL; // 读取控制及状态寄存器 } while((temp & 0x01) && (!(temp & (1<<16)))); // 等待COUNTFLAG标志置位(计时到) SysTick->CTRL = 0; // 关闭定时器 SysTick->VAL = 0; // 清空当前值 } // 一个简单的毫秒延时,用于按键消抖等不要求精确的场景 void delay_1ms(uint32_t ms) { for(uint32_t i=0; i<ms; i++) { delay_us(1000); // 调用1000次微秒延时 } }然后是GPIO初始化函数,把PA2、PA3、PA4设置为推挽输出模式。
x9c103s.c (部分2: GPIO初始化)
/** * @brief 初始化X9C103S的控制引脚 * @note 将INC、U/D、CS引脚配置为高速推挽输出,并初始化为高电平(模块默认等待状态) */ void x9c103s_gpio_config(void) { // 1. 打开GPIOA的时钟 rcu_periph_clock_enable(RCU_X9C103S); // 2. 配置引脚为输出模式,无上拉下拉 gpio_mode_set(PORT_X9C103S, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, PIN_INC | PIN_U_D | PIN_CS); // 3. 配置为推挽输出,高速模式(50MHz) gpio_output_options_set(PORT_X9C103S, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, PIN_INC | PIN_U_D | PIN_CS); // 4. 初始状态:CS=1 (不选中),INC=1, U/D=1 (方向默认向上) gpio_bit_write(PORT_X9C103S, PIN_CS, SET); gpio_bit_write(PORT_X9C103S, PIN_INC, SET); gpio_bit_write(PORT_X9C103S, PIN_U_D, SET); }3.2 核心控制函数:实现阻值调节
这是最关键的代码,它严格按照X9C103S的时序来操作。我们写一个volume()函数,它会根据全局标志位flag_UP和flag_DO来决定是增加还是减少阻值。
x9c103s.c (部分3: 控制函数)
/** * @brief X9C103S数字电位器控制函数 * @note 检查标志位,执行阻值增加或减少操作,操作完成后清除标志位。 * 每次触发,滑动端移动5个台阶(STEP=5)。 */ void volume(void) { // 情况1:需要增加阻值(向上滑动) if(flag_UP == 1) { // 步骤1: 选中芯片,CS拉低 gpio_bit_write(PORT_X9C103S, PIN_CS, RESET); // CS = 0; delay_1ms(1); // 短暂延时,确保信号稳定 // 步骤2: 设置方向为增加(U/D = 1) gpio_bit_write(PORT_X9C103S, PIN_U_D, SET); // U/D = 1; delay_1ms(1); // 步骤3: 发送5个INC脉冲(下降沿),滑动端移动5个台阶 for(STEP = 5; STEP > 0; STEP--) { gpio_bit_write(PORT_X9C103S, PIN_INC, RESET); // INC = 0; (产生下降沿) delay_1ms(1); gpio_bit_write(PORT_X9C103S, PIN_INC, SET); // INC = 1; (恢复高电平) delay_1ms(1); } // 步骤4: 取消选中,并在INC为高时产生CS上升沿,将当前位置保存! gpio_bit_write(PORT_X9C103S, PIN_CS, SET); // CS = 1; (此时INC=1,满足存储条件) delay_1ms(1); // 步骤5: 清除标志位,等待下一次按键 flag_UP = 0; } // 情况2:需要减少阻值(向下滑动) if(flag_DO == 1) { // 步骤1: 选中芯片 gpio_bit_write(PORT_X9C103S, PIN_CS, RESET); // CS = 0; delay_1ms(1); // 步骤2: 设置方向为减少(U/D = 0) gpio_bit_write(PORT_X9C103S, PIN_U_D, RESET); // U/D = 0; delay_1ms(1); // 步骤3: 发送5个INC脉冲 for(STEP = 5; STEP > 0; STEP--) { gpio_bit_write(PORT_X9C103S, PIN_INC, RESET); // INC = 0; delay_1ms(1); gpio_bit_write(PORT_X9C103S, PIN_INC, SET); // INC = 1; delay_1ms(1); } // 步骤4: 取消选中并保存位置 gpio_bit_write(PORT_X9C103S, PIN_CS, SET); // CS = 1; delay_1ms(1); // 步骤5: 清除标志位 flag_DO = 0; } }代码逻辑解读:这个函数就像一个状态机。当
flag_UP被置1时,它执行一整套“选中->设方向->发脉冲->保存”的流程,让滑动端向上移动5步。flag_DO同理。每次按键只触发一次完整的调节流程。
3.3 按键扫描与标志位设置
我们需要用按键来控制flag_UP和flag_DO。修改梁山派官方例程中的按键驱动文件。
bsp_key.h (修改后)
#ifndef _BSP_KEY_H #define _BSP_KEY_H #include "gd32f4xx.h" #include "systick.h" // 定义按键引脚 #define BSP_KEY_RCU RCU_GPIOA #define BSP_KEY_PORT GPIOA #define KEY_UP_PIN GPIO_PIN_0 // 上调键 -> PA0 #define KEY_DO_PIN GPIO_PIN_1 // 下调键 -> PA1 void key_gpio_config(void); void key_scan(void); #endifbsp_key.c (修改按键扫描函数)
// 声明外部变量,在main.c中定义 extern char flag_UP; extern char flag_DO; /** * @brief 按键扫描函数 * @note 检测PA0和PA1的按键,按下后设置相应的标志位,并串口打印信息。 * 采用了延时消抖和松手检测。 */ void key_scan(void) { // 扫描上调键(KEY_UP) - 假设按下为低电平 if(gpio_input_bit_get(BSP_KEY_PORT, KEY_UP_PIN) == RESET) // 检测到低电平 { delay_1ms(20); // 延时约20ms,消除机械抖动 if(gpio_input_bit_get(BSP_KEY_PORT, KEY_UP_PIN) == RESET) // 再次确认按下 { flag_UP = 1; // 设置上调标志位 printf("Key UP pressed! Adjusting resistance UP.\r\n"); // 等待按键松开(防止长按连续触发) while(gpio_input_bit_get(BSP_KEY_PORT, KEY_UP_PIN) == RESET); printf("Key UP released.\r\n"); } } // 扫描下调键(KEY_DOWN) - 假设按下为低电平 if(gpio_input_bit_get(BSP_KEY_PORT, KEY_DO_PIN) == RESET) { delay_1ms(20); if(gpio_input_bit_get(BSP_KEY_PORT, KEY_DO_PIN) == RESET) { flag_DO = 1; // 设置下调标志位 printf("Key DOWN pressed! Adjusting resistance DOWN.\r\n"); while(gpio_input_bit_get(BSP_KEY_PORT, KEY_DO_PIN) == RESET); printf("Key DOWN released.\r\n"); } } }注意:这里假设按键是低电平有效(即按键按下时,引脚被拉到GND)。如果你的按键电路是按下为高电平,需要把
RESET改成SET。
3.4 主程序:把所有功能串起来
最后,在main.c里初始化所有外设,并在循环中不断扫描按键和执行电位器控制。
main.c
#include "gd32f4xx.h" #include "systick.h" #include "bsp_led.h" // LED头文件,用于调试指示 #include "bsp_key.h" // 按键头文件 #include "bsp_usart.h" // 串口头文件,用于打印信息 #include "x9c103s.h" // 数字电位器头文件 // 定义全局标志位,在x9c103s.c和bsp_key.c中会用到 char flag_UP = 0; // 上调标志 char flag_DO = 0; // 下调标志 int main(void) { // 1. 系统基础初始化 systick_config(); // 初始化系统滴答定时器,为delay函数提供时钟 led_gpio_config(); // 初始化LED(可选,用于调试指示) key_gpio_config(); // 初始化按键GPIO usart_gpio_config(9600U); // 初始化串口0,波特率9600,用于打印信息 // 2. 初始化数字电位器控制引脚 x9c103s_gpio_config(); printf("System Started. Press KEY_UP or KEY_DOWN to adjust resistance.\r\n"); // 3. 主循环 while(1) { key_scan(); // 扫描按键,会设置 flag_UP 或 flag_DO volume(); // 根据标志位控制电位器 // 这里可以添加其他任务... } }4. 实验验证与调试心得
代码编译下载后,就可以开始测试了。
验证步骤:
- 用万用表的电阻档,测量模块上VW和VL(即GND)两个引脚之间的电阻。
- 按下连接在PA0上的“上调”按键,你应该会看到串口助手打印
"Key UP pressed!",同时万用表显示的电阻值会阶梯式地增加(每次大约增加10kΩ/100 = 100Ω,因为咱们代码里设置了STEP=5,所以一次按增加约500Ω)。 - 按下连接在PA1上的“下调”按键,电阻值会阶梯式地减小。
- 测试记忆功能:调节到一个阻值后,先松开按键,然后给整个系统(或仅模块)断电再上电。重新测量VW和VL间的电阻,它应该会恢复到断电前最后保存的那个位置。
调试中可能遇到的坑:
- 阻值没变化:首先检查硬件连接,尤其是CS、INC、U/D这三根线是否接对了梁山派的PA4、PA2、PA3。然后用逻辑分析仪或者示波器抓一下这三个引脚的波形,看是否符合前面讲的时序图(CS先拉低,U/D设置好,然后INC出现下降沿脉冲)。
- 变化方向反了:检查U/D引脚的电平设置。在
volume()函数里,增加阻值时U/D=1,减少时U/D=0。如果反了,调换一下逻辑即可。 - 按键不灵敏或连按:调整
key_scan()函数中的消抖延时delay_1ms(20),如果按键机械特性较差,可以适当增加这个时间。松手检测while循环保证了每次按下只触发一次动作。 - 存储功能失效:确保在
volume()函数里,每次调节结束后,是在INC=1的情况下将CS从0拉高到1的。这个上升沿是存储动作的触发条件。
这个项目成功的关键,就在于严格遵循芯片的时序。代码中的delay_1ms(1)延时是为了保证信号稳定,实际根据你的单片机速度,可能可以更短,但初次调试建议保留。掌握了这个驱动方法,你就可以轻松地用程序来控制模拟量了,比如做个手机APP调光的台灯,或者自动增益控制的放大器,想法可以很多。
