树莓派Pico:如何通过PIO发送2位信号控制GPIO驱动步进电机
树莓派Pico PIO双步进电机控制方案(无DMA、移除延时)
核心需求拆解
- 用PIO模块控制GPIO16、17,通过FIFO发送2位序列(0x00/0x01/0x10/0x11)对应不同引脚输出
- C语言实现
pio_signal = signal_1 + 2*signal_2的信号计算逻辑 - 移除原代码中
sleep_us(5)导致的速度瓶颈,用PIO接管GPIO输出 - 保留步进电机的平滑加减速逻辑,不使用DMA
步骤1:PIO程序编写(补充关键逻辑)
先给出极简PIO汇编代码,确保能从FIFO取数据并映射到目标引脚:
.program step_motor_pio pull block ; 阻塞等待从FIFO取数据 out pins, 2 ; 输出2位到引脚(对应GPIO16、17,需配置引脚映射)
如果需要硬件级固定延时(替代原sleep_us(5)),可以给PIO程序加内置延时,完全不占用CPU:
.program step_motor_pio pull block out pins, 2 set x, 31 ; 延时计数,可根据需求调整数值(对应约5us@125MHz时钟) delay_loop: jmp x-- delay_loop ; 硬件循环延时
步骤2:C语言信号计算与PIO初始化
信号计算逻辑实现
两种等价写法,任选其一:
// 写法1:和你原逻辑完全一致的算术运算 uint8_t signal_1 = /* 电机1电平:1=置高,0=置低 */; uint8_t signal_2 = /* 电机2电平:1=置高,0=置低 */; uint8_t pio_signal = signal_1 + (signal_2 << 1); // 等价于signal_1 + 2*signal_2 // 写法2:位运算(更直观体现2位序列的位对应关系) uint8_t pio_signal = (signal_2 << 1) | signal_1; // bit1对应GPIO17,bit0对应GPIO16
PIO初始化代码
#include "pico/stdlib.h" #include "hardware/pio.h" #include "step_motor.pio.h" // PIO程序编译后生成的头文件 #define MOTOR1_PIN 16 #define MOTOR2_PIN 17 PIO pio = pio0; // 可选pio0或pio1 uint sm = 0; // 状态机编号 void pio_motor_init() { // 加载PIO程序到PIO内存 uint offset = pio_add_program(pio, &step_motor_pio_program); // 初始化状态机,指定起始引脚为GPIO16(自动覆盖16、17两个引脚) step_motor_pio_program_init(pio, sm, offset, MOTOR1_PIN); // 设置引脚为输出方向 pio_set_pindirs_with_mask(pio, sm, (1 << MOTOR1_PIN) | (1 << MOTOR2_PIN), (1 << MOTOR1_PIN) | (1 << MOTOR2_PIN)); // 启动状态机 pio_sm_set_enabled(pio, sm, true); }
步骤3:替换原代码的GPIO操作与延时
原代码中类似这样的瓶颈逻辑:
// 原代码(存在延时瓶颈) gpio_put(MOTOR1_PIN, signal_1); gpio_put(MOTOR2_PIN, signal_2); sleep_us(5);
直接替换为向PIO FIFO发送信号,彻底移除软件延时:
// 新代码:无软件延时,由PIO硬件处理输出 pio_sm_put_blocking(pio, sm, pio_signal);
pio_sm_put_blocking会自动等待FIFO有空闲位置,确保数据不会丢失,PIO会立即处理数据并输出引脚电平。
步骤4:保留平滑加减速逻辑
原代码的加减速曲线逻辑完全可以保留,只需要把GPIO操作+延时的部分替换成PIO FIFO发送即可。示例:
// 简化的加减速循环示例 void run_motors_with_ramp() { int speed = 0; const int max_speed = 100; const int accel = 2; // 加速阶段 while (speed < max_speed) { // 根据步进方向计算当前电机的signal_1、signal_2 uint8_t signal_1 = (speed % 2 == 0) ? 1 : 0; uint8_t signal_2 = (speed % 4 == 0) ? 1 : 0; uint8_t pio_signal = (signal_2 << 1) | signal_1; // 发送到PIO pio_sm_put_blocking(pio, sm, pio_signal); // 原加减速逻辑保留 speed += accel; } // 匀速阶段 while (/* 你的运行条件 */) { uint8_t signal_1 = /* 你的逻辑 */; uint8_t signal_2 = /* 你的逻辑 */; pio_sm_put_blocking(pio, sm, (signal_2 << 1) | signal_1); } // 减速阶段(反向加速逻辑) while (speed > 0) { uint8_t signal_1 = /* 你的逻辑 */; uint8_t signal_2 = /* 你的逻辑 */; pio_sm_put_blocking(pio, sm, (signal_2 << 1) | signal_1); speed -= accel; } }
关键注意事项
- PIO状态机FIFO深度为4,
pio_sm_put_blocking会自动处理满FIFO的等待,无需额外判断 - 若需要更高输出速度,可调整PIO时钟分频:
pio_sm_set_clkdiv(pio, sm, 0.5)可让PIO时钟翻倍(最高250MHz) - 若使用带内置延时的PIO程序,需根据实际时钟频率调整
set x, 31中的数值,确保延时符合步进电机控制器要求
内容的提问来源于stack exchange,提问作者Nuke
相关产品推荐
相关产品推荐

