You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

树莓派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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 07:53:18