差别
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
mp_pio [2021/10/13 11:57] gongyusu [2. 来自maker.io] |
mp_pio [2021/10/13 21:48] (当前版本) gongyusu |
||
---|---|---|---|
行 1: | 行 1: | ||
## 可编程PIO的使用 | ## 可编程PIO的使用 | ||
+ | |||
+ | ### 1. 关于PIO | ||
+ | |||
来自[[https://www.taterli.com/7568/|TaterLi的博客文章]] | 来自[[https://www.taterli.com/7568/|TaterLi的博客文章]] | ||
- | RP2040中有2个相同的PIO块,每个PIO块都有专用的连接到总线结构,GPIO和中断控制器.单个PIO块的示意图如图所示. | + | |
+ | RP2040中有2个相同的PIO块, 每个PIO块都有专用的连接到总线结构,GPIO和中断控制器.单个PIO块的示意图如图所示. | ||
{{ ::sm_pio.png | }}<WRAP centeralign> Pico的PIO Block的功能框图(1/2) </WRAP> | {{ ::sm_pio.png | }}<WRAP centeralign> Pico的PIO Block的功能框图(1/2) </WRAP> | ||
+ | |||
PIO是一种通用的硬件接口,它可以支持多种IO标准.包括实现以下功能: | PIO是一种通用的硬件接口,它可以支持多种IO标准.包括实现以下功能: | ||
+ | * 8080/6080 并行接口 | ||
+ | * I2C | ||
+ | * I2S | ||
+ | * SDIO | ||
+ | * SPI/DSPI/QSPI | ||
+ | * UART | ||
+ | * DPI/VGA (利用电阻网络) | ||
- | 8080/6080 并行接口 | + | PIO和处理器一样,都是可以编程的,有两个PIO块,每个块有四个状态机,可以独立执行顺序程序来操作GPIO和传输数据.与通用处理器不同的是,PIO状态机对IO的专业化程度很高(highly specialised),它注重确定性、精确的时序, 并与固定功能硬件紧密结合,每个状态机都配备有以下内容: |
- | I2C | + | |
- | I2S | + | |
- | SDIO | + | |
- | SPI/DSPI/QSPI | + | |
- | UART | + | |
- | DPI/VGA (利用电阻网络) | + | |
- | PIO和处理器一样,都是可以编程的.有两个PIO块,每个块有四个状态机,可以独立执行顺序程序来操作GPIO和传输数据.与通用处理器不同的是,PIO状态机对IO的专业化程度很高(highly specialised),它注重确定性,精确的时序,并与固定功能硬件紧密结合.每个状态机都配备有以下内容: | + | |
* 两个32位移位寄存器 (任意方向/任意移位数) | * 两个32位移位寄存器 (任意方向/任意移位数) | ||
* 两个32位临时寄存器 | * 两个32位临时寄存器 | ||
- | * 4 * 32B FIFO (双向) 或 8 * 32 FIFO (单向) | + | * 4*32B FIFO (双向) 或8*32 FIFO (单向) |
* 小数分频器 (16整数 + 8小数) | * 小数分频器 (16整数 + 8小数) | ||
* 可编程GPIO映射 | * 可编程GPIO映射 | ||
* DMA/IRQ | * DMA/IRQ | ||
- | * 每个状态机及其支持的硬件,占用的硅面积与SPI/I2C大致相同.然而,PIO状态机可以动态地配置和重新配置,以实现许多不同的接口,自由度很高. | + | * 每个状态机及其支持的硬件,占用的硅面积与SPI/I2C大致相同。然而,PIO状态机可以动态地配置和重新配置,以实现许多不同的接口,自由度很高. |
以类似软件的方式使状态机可编程,而不是像CPLD那样的完全可配置的逻辑结构,可以在相同的成本和功率范围内提供更多的硬件接口,这也为那些希望通过直接编程而不是使用PIO库中的预制接口来利用PIO的全部灵活性的人提供了一个更熟悉的编程模型和更简单的工具流程. | 以类似软件的方式使状态机可编程,而不是像CPLD那样的完全可配置的逻辑结构,可以在相同的成本和功率范围内提供更多的硬件接口,这也为那些希望通过直接编程而不是使用PIO库中的预制接口来利用PIO的全部灵活性的人提供了一个更熟悉的编程模型和更简单的工具流程. | ||
行 25: | 行 30: | ||
PIO具有很高的性能以及灵活性,这得益于每个状态机内部精心挑选的一组固定功能硬件.在输出DPI时,当使用48MHz系统时钟运行时,PIO可以在活动扫描线期间维持360Mb/s的速度.在这个例子中,一个状态机负责处理帧/扫描线时序和生成像素时钟,而另一个状态机负责处理像素数据,并解包运行长度编码的扫描线. | PIO具有很高的性能以及灵活性,这得益于每个状态机内部精心挑选的一组固定功能硬件.在输出DPI时,当使用48MHz系统时钟运行时,PIO可以在活动扫描线期间维持360Mb/s的速度.在这个例子中,一个状态机负责处理帧/扫描线时序和生成像素时钟,而另一个状态机负责处理像素数据,并解包运行长度编码的扫描线. | ||
- | 状态机的输入和输出最多可以映射到32个GPIO(RP2040限制为30个GPIO),所有状态机都可以独立地同时访问任何GPIO.例如,标准UART代码允许TX/RX/CTS/RTS成为任意四个GPIO.I2C允许SDA/SCL也是如此.可用的自由度取决于给定的PIO程序究竟如何选择使用PIO的引脚映射资源,但至少,一个接口可以自由地选择一些数量的GPIO. | + | 状态机的输入和输出最多可以映射到32个GPIO(RP2040限制为30个GPIO),所有状态机都可以独立地同时访问任何GPIO.例如,标准UART代码允许TX/RX/CTS/RTS成为任意四个GPIO.I2C允许SDA/SCL也是如此.可用的自由度取决于给定的PIO程序究竟如何选择使用PIO的引脚映射资源,但至少,一个接口可以自由地选择一些数量的GPIO。 |
- | 四个状态机从一个共享指令存储器中执行,系统软件将程序加载到这个存储器中,配置状态机和IO映射,然后设置状态机运行.PIO程序的来源多种多样: | + | 四个状态机从一个共享指令存储器中执行,系统软件将程序加载到这个存储器中,配置状态机和IO映射,然后设置状态机运行.PIO程序的来源多种多样: |
* 由用户直接组装 | * 由用户直接组装 | ||
* 从PIO库中抽取 | * 从PIO库中抽取 | ||
* 由用户软件编程生成 | * 由用户软件编程生成 | ||
- | * 从这一点上看.状态机一般是自主的,系统软件通过DMA/IRQ和控制寄存器进行交互,与RP2040上的其他外设一样.对于比较复杂的接口,PIO提供了一套小而灵活的基元,使系统软件可以更多地亲自动手处理状态机控制流程. | + | |
+ | 从这一点上看.状态机一般是自主的,系统软件通过DMA/IRQ和控制寄存器进行交互,与RP2040上的其它外设一样,对于比较复杂的接口,PIO提供了一套小而灵活的基元,使系统软件可以更多地亲自动手处理状态机控制流程。 | ||
PIO状态机执行短小的二进制程序. | PIO状态机执行短小的二进制程序. | ||
行 123: | 行 129: | ||
### 2. 来自maker.io | ### 2. 来自maker.io | ||
+ | [[https://www.digikey.com/en/maker/projects/raspberry-pi-pico-and-rp2040-micropython-part-3-pio/3079f9f9522743d09bb65997642e0831|在MicroPython中使用PIO]] | ||
+ | |||
每个PIO有4个“状态机”。这些状态机的运行方式就像微小的、非常有限的处理器,能够运行共享指令内存中的指令。指令存储器最多可以容纳32条指令。然而,每个状态机都可以从内存中的任何地方提取指令。 | 每个PIO有4个“状态机”。这些状态机的运行方式就像微小的、非常有限的处理器,能够运行共享指令内存中的指令。指令存储器最多可以容纳32条指令。然而,每个状态机都可以从内存中的任何地方提取指令。 | ||
行 192: | 行 200: | ||
</code> | </code> | ||
+ | PIO程序包含在blink()函数中。注意,要在PIO中使用它,必须使用@rp2.asm_pio[[https://realpython.com/primer-on-python-decorators/|装饰器]]。在装饰器的参数中,我们设置了引脚方向和FIFO方向。 | ||
+ | 我们可以在每个状态机中使用一个wrap()和wrap_target()函数。这允许我们在没有JMP指令的情况下自动循环。 | ||
+ | |||
+ | set()函数是set指令的包装器。第一个参数是目标,我们有一些选项,如“pin”、“X寄存器”、“Y寄存器”等等。“引脚”是指将指定的GPIO设置为给定值。第二个参数是值。所以,我们用1来设置引脚高,用0来设置引脚低。 | ||
+ | |||
+ | 每个指令都有一个时钟周期(在除法器之后)。我们最多可以将状态机延迟31个额外周期。我们用函数调用后的括号来实现这一点。所以,set()需要1个周期,我们延迟19个周期,这整行总共需要20个周期。 | ||
+ | |||
+ | 然后,我们在80个周期(4个nop()命令,每个命令有额外的19个周期延迟)中不做任何操作。 | ||
+ | |||
+ | 所以,我们设置一个管脚高,等待总共100个周期。然后我们重复这一过程,引脚低电平100个周期。 | ||
+ | |||
+ | 如果我们设置时钟分频器,使状态机工作在2khz,这意味着我们将以2khz/200 = 10hz的频率闪烁LED。 | ||
+ | |||
+ | 在程序的主要部分中,我们从rp2模块创建了一个StateMachine对象。第一个参数是我们希望使用的状态机。状态机0到3在PIO 0,状态机4到7在PIO 1。因此,您必须为这个参数选择一个0到7之间的数字。接下来,我们设置所需的频率(对于Pico,在2000到125_000_000之间),然后是我们希望使用的引脚。 | ||
+ | |||
+ | 注意,我们可以将多个引脚分配给一个状态机,但它们必须是连续的。当声明StateMachine对象时,我们选择基脚(例如pin 25)。在装饰器中,我们将使用元组来设置管脚的方向。例如(基脚设置为25),set_init=(rp2.PIO.OUT_LOW, rp2.PIO.OUT_HIGH)会设置引脚25输出默认的低值,引脚26输出默认的高值。 | ||
+ | |||
+ | 最后,我们可以使用sm.active(1)和sm.active(0)启动和停止状态机。 | ||
+ | |||
+ | 如果您运行该程序,您应该看到LED快速闪烁(10hz),但它将每秒钟启动和停止闪烁(正如我们告诉状态机启动和停止)。 | ||
+ | |||
+ | #### 使用PIO作为库 | ||
+ | 使用PIO还有很多我们没有涉及的内容(例如side-setting)。如果您希望了解关于PIO如何工作的更多细节,我鼓励您阅读RP2040数据表。 | ||
+ | 然而,这并不能阻止我们使用别人的代码!Raspberry Pi在GitHub repo中有许多很棒的PIO示例。让我们使用PWM示例来创建我们自己的MicroPython库。注意,下面的示例来自该存储库。 | ||
+ | 在一个新的Thonny文件中,输入以下代码: | ||
+ | <code python> | ||
+ | from machine import Pin | ||
+ | from rp2 import PIO, StateMachine, asm_pio | ||
+ | |||
+ | @asm_pio(sideset_init=PIO.OUT_LOW) | ||
+ | def pwm_prog(): | ||
+ | pull(noblock) .side(0) | ||
+ | mov(x, osr) # Keep most recent pull data stashed in X, for recycling by noblock | ||
+ | mov(y, isr) # ISR must be preloaded with PWM count max | ||
+ | label("pwmloop") | ||
+ | jmp(x_not_y, "skip") | ||
+ | nop() .side(1) | ||
+ | label("skip") | ||
+ | jmp(y_dec, "pwmloop") | ||
+ | |||
+ | |||
+ | class PIOPWM: | ||
+ | def __init__(self, sm_id, pin, max_count, count_freq): | ||
+ | self._sm = StateMachine(sm_id, pwm_prog, freq=2 * count_freq, sideset_base=Pin(pin)) | ||
+ | # Use exec() to load max count into ISR | ||
+ | self._sm.put(max_count) | ||
+ | self._sm.exec("pull()") | ||
+ | self._sm.exec("mov(isr, osr)") | ||
+ | self._sm.active(1) | ||
+ | self._max_count = max_count | ||
+ | |||
+ | def set(self, value): | ||
+ | # Minimum value is -1 (completely turn off), 0 actually still produces narrow pulse | ||
+ | value = max(value, -1) | ||
+ | value = min(value, self._max_count) | ||
+ | self._sm.put(value) | ||
+ | </code> | ||
+ | 保存到你Pico的/lib目录下:piopwm.py | ||
+ | 创建一个新的文件: | ||
+ | <code python> | ||
+ | import utime | ||
+ | from piopwm import PIOPWM | ||
+ | |||
+ | # Create PIOPWM object used to fade LED | ||
+ | # (state machine 0, pin 25, max PWM count of 65535, PWM freq of 10 MHz) | ||
+ | pwm = PIOPWM(0, 25, max_count=(256 ** 2) - 1, count_freq=10_000_000) | ||
+ | |||
+ | # Loop forever | ||
+ | while True: | ||
+ | | ||
+ | # Fade LED in | ||
+ | for i in range(256): | ||
+ | pwm.set(i ** 2) | ||
+ | utime.sleep(0.01) | ||
+ | | ||
+ | # Fade LED out | ||
+ | for i in reversed(range(256)): | ||
+ | pwm.set(i ** 2) | ||
+ | utime.sleep(0.01) | ||
+ | </code> | ||
+ | |||
+ | 在这里,我们导入在PIOPWM模块中创建的PIOPWM类。我们可以使用它来启动一个PIO实例,为我们处理脉冲宽度调制(PWM) ! | ||
+ | 我们在引脚25上设置了最大计数65535和状态机频率10mhz。然后我们通过pwm.set()函数增加亮度来淡出板载LED,并通过降低亮度来淡出。 | ||
+ | 将代码保存为Pico的顶级目录中的main.py。运行它,你应该看到LED慢慢淡入淡出。 |