树莓派 Pico 中的 PIO(Programmable Input/Output)是一种灵活的外设,允许用户通过编程实现自定义的硬件接口和协议。
核心功能:
- PIO 可以独立于主处理器运行,执行特定的输入/输出任务。
- 支持多种协议,如 I2C、SPI、UART 等,并且可以自定义协议。
主要特点:
- 灵活性:用户可以通过编写 PIO 程序来实现特定的硬件功能。
- 高效性:PIO 可以以非常高的速度执行任务,减少主处理器的负担。
- 可编程性:PIO 程序使用汇编语言编写,允许精确控制硬件行为。
应用场景:
- 实现自定义通信协议。
- 控制复杂的硬件设备。
- 处理高速数据流。
下面我们用PIO来点一个灯,先看代码
from rp2 import PIO, StateMachine, asm_pio
from machine import Pin
import time
LED_PIN = 5
FREQUENCY = 10000
DELAY_TIME = 1
led = Pin(LED_PIN)
@asm_pio(set_init=PIO.OUT_LOW)
def led_quarter():
set(pins, 0)[2]
set(pins, 1)
@asm_pio(set_init=PIO.OUT_LOW)
def led_half():
set(pins, 0)
set(pins, 1)
@asm_pio(set_init=PIO.OUT_HIGH)
def led_full():
set(pins, 1)
# 将状态机和对应的函数存储在列表中
state_machines_brightness = [
(1, led_quarter),
(2, led_half),
(3, led_full)
]
# 创建状态机列表
state_machines = []
for sm_id, func in state_machines_brightness:
sm = StateMachine(sm_id, func, freq=FREQUENCY, set_base=led)
state_machines.append(sm)
while True:
for sm in state_machines:
sm.active(1)
time.sleep(DELAY_TIME)
sm.active(0)
代码里出现了平时我们比较少遇到的装饰器(Decorator),这里简单介绍一下。
在Python中,装饰器(Decorator)允许你在不修改原有函数或类定义的基础上,增加新的功能。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新函数。这使得你可以在运行时动态地添加、修改或替换函数的行为。
如果上面的定义理解起来费劲儿的话,那我来打一个比喻。你发一个快递的时候会把东西装在一个黄色纸盒子里,这个就是我们常用的函数,那么如果这是一个生日礼物那我会用一个漂亮的纸再把黄色的纸盒子包装一下,这就是装饰器(Decorator)这样我就可以在原函数执行前执行后再执行一些任务,起到对原函数的包装作用。
举个例子,当我们写了一个函数的时候我想知道这个函数执行花了多少时间,那我可以在函数体头部尾部分别取时间戳来计算执行时间,但是这就破坏了原函数的完整性。这时就可以使用装饰类来完成这个任务,请看代码
import time
from time import perf_counter, sleep
from functools import wraps
from typing import Callable, Any
def get_time(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs) -> Any:
# Note that timing your code once isn't the most reliable option
# for timing your code. Look into the timeit module for more accurate
# timing.
start_time: float = perf_counter()
result: Any = func(*args, **kwargs)
end_time: float = perf_counter()
print(f'"{func.__name__}()" took {end_time - start_time:.3f} seconds to execute')
return result
return wrapper
# Sample function 1
@get_time
def connect() -> None:
print('Connecting...')
sleep(2)
print('Connected!')
# Sample function 2
@get_time
def fifty_million_loops() -> None:
fifty_million: int = int(5e7)
print('Looping...')
for _ in range(fifty_million):
pass
print('Done looping!')
def main() -> None:
fifty_million_loops()
connect()
if __name__ == '__main__':
main()
这里的函数get_time就是一个装饰器,如果有别的函数想要使用这个装饰器就在函数上面加上
@get_time
def foo():
好了了解装饰器以后我们再回来看PIO的例子
@asm_pio 就是一个装饰器,他在rp2的包中已经定义好了,我们不需要再实现一个了
@asm_pio(set_init=PIO.OUT_LOW) 定义了PIO初始状态,这里为低电压。
下面我们再学习一个概念--状态机
状态机,亦被称为有限状态自动机(Finite State Machine,FSM),是对现实事物运行规则进行抽象的数学模型。它融合了状态寄存器和组合逻辑电路,能够依据控制信号在预设状态下进行状态转换,成为协调相关信号动作、执行特定操作的控制核心。
简单的说就是我提前定义好几个状态,分别是什么样的,当然根据条件一个状态可以转变为另一个状态也可以其他什么状态,比如气体,液体,固体之间的互相转化就可以理解为一组状态机。
我们的例子里定义了三个状态机(Pico最大支持8个)
我定义三个状态分别是,¼亮 ½亮 和全亮
state_machines_brightness = [
(1, led_quarter),
(2, led_half),
(3, led_full)
]
分别对应三个函数
@asm_pio(set_init=PIO.OUT_LOW)
def led_quarter():
set(pins, 0)[2]
set(pins, 1)
@asm_pio(set_init=PIO.OUT_LOW)
def led_half():
set(pins, 0)
set(pins, 1)
@asm_pio(set_init=PIO.OUT_HIGH)
def led_full():
set(pins, 1)
- set 指令:在 PIO 编程里,set 指令用于设置内部寄存器或者引脚的状态。格式为 set(dest, value),其中 dest 是目标寄存器或者引脚,value 是要设置的值。
- pins:这是一个特殊的目标,表示 PIO 状态机控制的引脚。在代码里,pins 对应通过 StateMachine 构造函数中 set_base 参数指定的引脚,也就是 led 引脚(Pin(5))。
- 0:代表把引脚电平设置为低电平。
- 1:代表把引脚电平设置为高电平。
full最好理解,初始就是高电压,然后一直保持高电压,也就是常亮
half这里通过以FREQUENCY 的频率(例子中是 10000 Hz)不断改变pin脚高低电压来实现半亮
quarter中最诡异的代码是这句 set(pins, 0)[2]
set(pins, 0)[2] 是 PIO 汇编指令,其作用是把指定引脚的电平设置为低电平(逻辑 0),并且让 PIO 状态机在执行下一条指令前等待 2 个时钟周期。也就是0 空 空 1 = 0 0 0 1
- [2]:这是延迟修饰符,意思是在执行完 set 指令后,状态机暂停 2 个时钟周期,再执行下一条指令。时钟周期由 StateMachine 构造函数中的 freq 参数决定,。
所以在 led_quarter 函数里,set(pins, 0)[2] 先把 LED 引脚置为低电平,等待 2 个时钟周期后,再通过 set(pins, 1) 把引脚置为高电平。借助控制高低电平的持续时间,能调节 LED 的亮度,这里实现的是四分之一亮度。
好了点灯贴写完了,舒服了
更多RP2350视频请访问
https://www.bilibili.com/video/BV18G55zrEqX/
树莓派Pico自虐之旅
https://www.bilibili.com/video/BV1aU4y1H7J5
评论