RP2040 PICOSDK食用指南——PIO3复杂的UART例子

UART传输的设备。

我这样做的方式是,我的传输线在高电平下空闲,然后我想发送一个8位字符,告诉设备我将向您发送一个位字符

 我首先发起一个起始位,因此我将把传输线拉低一位时间

您的位时间就是您的波特率,好的?所以我们把它拉低一点时间

然后我将我想要发送字符的每一位都记录下来。并且我将我正在与之通信的设备已经经过编程以知道波特率。

因此他知道它看到起始位时它会启动它的时钟然后他知道应该以多快的速度进行采样以获取每个后续位。

因此,我发送出每个位,然后用停止位结束我的传输,即我的传输线在一个时间内处于高位状态

这是一个简单的协议

.pio_version 0 // 仅需要PIO版本0

.program uart_tx
.side_set 1 opt ;表示可选 我留出一个引脚以便与pio的sid设置功能一起使用那里的opt可选意味着如果那里的指令没有与之关联的一侧,他只会保留上次设置的值

; 一个8n1格式的UART发送程序。
; OUT引脚0和side-set引脚0都映射到UART TX引脚。

    pull       side 1 [7]  ; 置位停止位,或在线路空闲时暂停
    set x, 7   side 0 [7]  ; 预加载位计数器,在8个时钟周期内置位起始位
bitloop:                   ; 此循环将运行8次(8n1 UART)
    out pins, 1            ; 将1位数据从OSR移到第一个OUT引脚
    jmp x-- bitloop   [6]  ; 每次循环迭代为8个周期。


% c-sdk {
#include "hardware/clocks.h"

static inline void uart_tx_program_init(PIO pio, uint sm, uint offset, uint pin_tx, uint baud) {
    // 告诉PIO初始时在所选引脚上驱动输出高电平,然后通过IO多路复用器将PIO映射到该引脚。 这两条指令可以设置的引脚设置为高电平或者低电平,将此引脚设置为高电平以启动
    pio_sm_set_pins_with_mask64(pio, sm, 1ull << pin_tx, 1ull << pin_tx);
    pio_sm_set_pindirs_with_mask64(pio, sm, 1ull << pin_tx, 1ull << pin_tx);
    pio_gpio_init(pio, pin_tx);

    pio_sm_config c = uart_tx_program_get_default_config(offset); //为PIO设置

    // OUT向右移位,不自拉动
    sm_config_set_out_shift(&c, true, false, 32);

    // 我们将OUT和side-set都映射到同一个引脚,因为有时
    // 我们需要将用户数据置位到引脚上(使用OUT),有时
    // 需要置位恒定值(起始/停止位)
    sm_config_set_out_pins(&c, pin_tx, 1);
    sm_config_set_sideset_pins(&c, pin_tx);

    // 我们只需要TX,所以使用8深度的FIFO!
    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);

    // 状态机每8个执行周期传输1位。
    float div = (float)clock_get_hz(clk_sys) / (8 * baud);
    sm_config_set_clkdiv(&c, div);

    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

static inline void uart_tx_program_putc(PIO pio, uint sm, char c) {
    pio_sm_put_blocking(pio, sm, (uint32_t)c);
}

static inline void uart_tx_program_puts(PIO pio, uint sm, const char *s) {
    while (*s)
        uart_tx_program_putc(pio, sm, *s++);
}

%}
.side_set 1 opt ; 表示我们需要用side set来控制一个引脚
那么这个PIO做什么呢,这是一个8IN1 UART发送程序
对于我们在传输线上发送的每个位,每个位都会有8个PIO时钟周期与之相关。也就是说我们每八个周期就会更改一次传输引脚上的数据
这意味着我们需要减慢时钟
side1 [7]的意思是在执行完这条指令的一个时间的指令后,在此行上在等待7条指令的时间
这与我们每位强行执行8条PIO指令有关
我们做在这里等待 TX线处于高位,C程序向它发出8位的指令,如果TX FIFO中出现某些内容我们就会清除它。
这会将其移动到输出移位寄存器。
接下来我们要调用一个设置命令,命令内容是:请将Xscrach寄存器的值设置为7,我们将使用X Scrach寄存器来计数指令
所以我们把它设置为7.同时将side的设置引脚设置为低电平,然后等待7个指令时间也就是另外7个指令时间。
所以,设置指令占用一个指令时间。在同一条指令中,我们将传输线拉低,然后等待另外7个指令时间。
这就是起始位,好的我们在这里等待传输线保持高电平,TX FIFO中出现了一些信号,我们将其拉低,并通过延迟命令强制保持低电平8个PIO指令周期

然后我们进入一个叫做位循环的程序标签。
我们做的第一件事就是调用引脚1,这样我们就从输出移位寄存器中取出一位,如果我们要发送字符,那么,那里有八位,我们移出一位
移出的位会被发送到引脚。我们稍后会看到。我上次提到过,不同的命令可以使用重叠的引脚
所以,SIDE SITE引脚和输出引脚是同一个引脚。我们已将其配置为同一个引脚。稍后我们会看到这一点
但是这表示要转到传输线,取出TX FIFO中的第一位,如果是1,则将传输线设置为高;如果是0则设置为低
然后我们执行这条跳转指令
这意味着,如果X暂存寄存器中的值,大于0请等待另外6个PIO周期 然后跳回位循环,执行完成后X暂存寄存器减一
总的作用是我们的TX FIFO中有八位,它取出第一位,将其发送到PIN,等待引脚上的数据在八个指令周期内保持不变
然后它跳转并将下一位移出,等待,再移出下一位,它使用X暂存寄存器来计数所有8位,一旦X暂存寄存器为零,他就不会执行此跳转。
他不会跳回位循环,而是跳转到程序顶部的弹出指令(拉取指令)并等待用户将其他内容放入TX FIFO
如果多个指令共享一个PIN 他们是有一个优先级的 要去查文档了
所以PIO程序相对简单,我们等待一个字符出现,发送起始位并初始化X暂存器,然后使用暂存器对发送的每个位进行计数
我们使用这条延迟指令来确保每个位持续正确的时间,在所有的设置中,我们设置了所有的时序
sm_config_set_out_shift(&c, true, false, 32);
我们正在配置一些与out指令相关的细节,第一个参数是指向我们正在设置的配置对象的指针。第二个参数表示请将数据向右移出,如果为false我们将向左移出数据,基本上
我们是将数据向最低有效位方向移出,还是向最高有效位方向移出?
这里的false表示我们不使用自动上拉,如果设置为true,我们可以使用最后一个参数来设置移位计数器的值,以便进行隐式上拉
在本例中我们没有使用该功能,但是如果要使用我们要在这里设置它
sm_config_set_out_pins(&c, pin_tx, 1);
sm_config_set_sideset_pins(&c, pin_tx);
然后,在这里我们将关联out指令要操作的引脚和side sit
指令augment要操作的引脚。您会注意到这两个引脚都指向TX引脚。这意味着out指令和side set指令都控制同一个引脚。
它们都控制着UR传输引脚

由于这个PIO程序只控制着数据从ARM发送到PIO进行传输,所以我们不需要双向FIFO。
所以我们进行FIFO连接表示我们只以这种方式发送数据,请将这两个FIFO连接在一起
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);

然后是我们根据波特率设置时钟分频器。记住波特率是指您希望通过此UR通道每秒传输的位数。假设我们要将波特率设置为1152000
    // 状态机每8个执行周期传输1位。
    float div = (float)clock_get_hz(clk_sys) / (8 * baud);
    sm_config_set_clkdiv(&c, div);
我们需要计算需要多少时钟分频器才能达到我们想要的波特率。好吧,我们会问系统的时钟频率,除非我们改变它都是125MHZ
然后我们将它除以波特率的8倍,之所以是波特率的8倍,是因为每位有8个PIO指令执行时间
如果我们将时钟除以这么多,就意味着我们减缓了状态机的速度使得8个PIO指令中执行的时间需要1/8的波特率秒数
如果不分频的话会需要很大的延迟数字,但是Delay/side-set只有五位

 你会注意到与每条指令相关的延迟位范围只有5位,这意味着你不能将一条指令延迟超过31个指令时间。

而且这个范围与side set共享

所以每次你请求一个额外的side set引脚时,你都会从延迟字段中窃取一个位

实际上在我们只有一个side set引脚的程序中我们只能延迟2~4位

    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
初始化状态机,并且关联到PIO上


 

posted @ 2025-07-08 23:12  mcwhirr  阅读(181)  评论(0)    收藏  举报