微雪树莓派PICO笔记——8-PIO(可编程输入输出接口)_pio接口

  • IRQ中断表示位,PIO总共有8个中断表示位,可以用于同步状态机或者其他用途

  • 每个状态机都可以进行灵活的GPIO映射,映射方式有四种,分别为

    • 输入映射
    • 输出映射
    • 设置映射
    • 侧置映射
      ![[Pasted image 20220924125536.png]]
  • 详细了解GPIO映射

  • 首先我们需要知道映射寄存器都是32位的

  • 如果让其一位对应着一个GPIO引脚,我们应该用32个GPIO引脚对应
    ![[Pasted image 20220924125832.png]]

  • 即RP2040只有30个GPIO,我们应当假设其存在32个GPIO,也就是GPIO30和GPIO31,我们也可以戏称之为“消失的引脚”
    ![[Pasted image 20220924130045.png]]
![[Pasted image 20220924130031.png]]

  • 输入映射

  • 我们可以指定输入GPIO的初始引脚。这个初始引脚在PIO的输入映射中将会被视为引脚0,并将其他引脚按照顺序进行(循环)计数,这里就会遇到“消失的引脚”

    • 如果设定GPIO29为初始引脚也就是引脚0,然后依次递增
    • 经过GPIO30和GPIO31,引脚3也就是对应GPIO0

![[Pasted image 20220924130500.png]]

输出映射和输入映射接近,但需要设置输出引脚的数量,最高为32
设置和侧置映射与输出映射工作方式相同,但最多映射5个IO,设置和侧置映射引脚是允许重叠的

PIO指令详解

状态机类似于极简的内核,只允许很简单的二进制程序
其采用了接近机器语言的PIO汇编进行编写,其支持9种指令分别为:

  • JMP
  • WAIT
  • IN
  • OUT
  • PUSH
  • PULL
  • MOV
  • IRQ
  • SET

我们编写的PIO ASM指令会被编译器编译成对应的指令再交给PIO的状态机运行
![[Pasted image 20220924161427.png]]

PIO指令编码表
![[Pasted image 20220924161443.png]]

  • 首先我们可以看到一条指令的长度为16位,高3位用于标识这条指令功能或者作用

  • 12-8位则用于标识延迟或者侧置,7-0位则会根据指令的不同有不同的功能

  • JMP指令的作用是将达到某些条件的情况下将程序跳转到指定地址

    • JMP指令有两个参数,分别是condition和 target
      • target:允许数值0-31,因为 PIO只有32条指令空间
      • condition:
        • !X OR !Y: X,Y寄存器为0
        • X-- OR Y–: X,Y寄存器减1大于0
        • X != Y: X不等于Y
        • PIN: 输入引脚为高电平
        • !(OSER):输入移位寄存器OSR为0
          ![[Pasted image 20220924163200.png]]

当程序达成时,程序就会根据target跳转指定地址
当然可以不填写条件,则会无条件跳转到指定地址

  • WAIT指令的作用为在条件达成前等待

    • WAIT指令有三个参数,分别为Polarity极性 ,source源和Index指针
    • 极性是指等待目标出现0或者1
    • 源指对应等待的目标类型,可以为绝对GPIO,引脚映射后PIN和中断标志IRQ
    • 指针对应等待的目标编码
      ![[Pasted image 20220924164414.png]]
  • IN指针指令的作用将数据存入ISR寄存器中

    • IN指令有两个参数为Source源, Bitcount位数
    • 源指对应读取数据的源类型
    • 位数对应着读取数据的位数
      ![[Pasted image 20220924164657.png]]
  • OUT指令和IN指令功能相反,指令作用是将OSR输出到目标

  • OUT指令有两个参数destination目标和Bitcount位数

  • 目标对应着数据输出的目标

  • 位数对应着输出位数

  • ![[Pasted image 20220924164920.png]]

  • PUSH指令作用为将ISR内容推送到RX FIFO中并清空ISR

  • 有两个参数 ,为满 和 阻塞

  • 若为满为1,则ISR达到阈值,才可以进行推送

  • 若阻塞为1且RX FIFO达到阈值,则会将数据推送到ISR寄存器中

  • 否则会等待RX FIFO达到阈值,若为0则RX FIFO达不到阈值时,则不会进行推送
    -![[Pasted image 20220924165421.png]]

  • PULL指令的作用将TX FIFO数据读到OSR寄存器

  • 有两个参数为 空 和 阻塞

  • 若为空为1,则只有TXFIFO达到阈值,OSR才会接收TXFIFO的数据

  • 其中阻塞和PUSH的阻塞一致

  • ![[Pasted image 20220924165654.png]]

  • MOV指令的作用是将数据从源移动到目标寄存器中

  • 有三个参数 目标destination,源sourcr和操作Operation
  • 目标将数据写入的寄存器,可以为一下几种![[Pasted image 20220924170236.png]]

需要注意的是,EXEC解码寄存器,其作用为从外部读取一条指令并执行,理论上是可以执行外部的PIO代码的

  • 源为数据来源,可以为一下几种
    在这里插入图片描述

操作有三种

  • 在这里插入图片描述
  • IRQ指令的作用为设置或者清空中断标识
  • 有3个参数: 选项 , 中断标志位 和 REL
  • 选项有一下几种
  • ![[Pasted image 20220924170635.png]]
  • 中断标识可以为0-7
  • 若REL存在则中断标识irq_num和状态机编码sm_num进行模4加法运算
  • 若状态机2设置中断标识OX11,最后的中断值则为0X03
  • ![[Pasted image 20220924170856.png]]
  • SET指令会将数据写入目标地址,通常用于控制设置映射引脚
  • 参数 destination 目标地址
  • ![[Pasted image 20220924171056.png]]
  • data 数据为写入的数据,这里只能为绝对值
  • ![[Pasted image 20220924171136.png]]

PIO语法详解

![[Pasted image 20220924171202.png]]

以上就是PIO ASM的9个指令,但是如果你看过PIO ASM的程序,你会发现指令后面还会跟着一些小尾巴
比如说方括号带着一个数字,这代表延迟
![[Pasted image 20220924171343.png]]

在正常程序中我们的每一条指令执行所需要时间为1个时钟周期
但往往我们需要等待或者不需要这么快的速度,这时候我们就需要进行这个延迟
![[Pasted image 20220924171523.png]]

  • 现在有两种方法

    1. 使用一条无意义指令,这个指令将y寄存器的数值赋给y寄存器,这是一条没有意义的指令,但是他会占用状态机的一个时钟周期,还有个缺点,他会占用PIO的 指令空间,因为状态机的指令空间只能容纳32条指令,所以说不推荐这种用法
      ![[Pasted image 20220924171902.png]]
    2. 使用PIO ASM中的延迟特性
      这条指令会将PINS设置为1,之后延迟2个时钟周期,这就是PIO汇编的延迟特性![[Pasted image 20220924172043.png]]1

    当然延迟时间也是有特性的,最大的延迟时钟为31个时钟周期

  • 侧置Side-set

  • 我们可以在执行任何指令的同时改变被设置为侧置映射引脚,最多5个引脚

  • 需要注意的是,侧置和延迟是共享指令的12-8位,所以说侧置和延迟是无法完美兼容的,但是可以根据你的需求进行设置

  • ![[Pasted image 20220924172440.png]]

延迟的最大数值为31,换算成2进制也就是5位,也就是我们无法通过编码区分延迟和侧置
需要我们需要在程序里注明使用侧置位数

例程

.program spi_tx_fast
.side_set 1

loop:
out pins, 1  side 0
jmp loop     side 1

该程序会模拟SPI发送数据的过程
其会不断地将OSR寄存器中的数值(依次)通过pins对应的引脚输出
与此同时,侧置引脚会不断地进行翻转
实现了两个时钟周期输出一位数据并输出时钟信号,使我们的程序更小更快
![[Pasted image 20220924173057.png]]

使用侧置功能时,我们还需要声明使用的侧置位数
这里我们使用一位侧置位数,他会占用侧置/延迟字段的高一位
意味着我们的延迟只能占用4位,也就是其数值范围为0-15

正常情况下,程序会从地址为0的指令开始运行,运行到地址为31的指令运行完成后,再从地址0的指令重复开始运行
下面这段程序将会将一个引脚设置为输出并循环输出占空比为50%且周期为4个时钟周期的方波
该程序的PC指针不会在0-31之间往复,所以说PIO引入了程序包装
通过程序包装可以告诉我们的状态机,从哪里结束并且从哪里重新开始

.program squarewave
set pindirs, 1 ; Set pin to output 
again:
      set pins,1 [1];  Drive pin high  and delay for one cycle
      set pins, 0 ;    Drive pin low
      jmp again ;      Set PC to label 'again'

例程

当程序运行到.wrap 时,程序将会返回到.wrap_target并运行
与上一个程序的区别就是不需要jmp指令进行跳转,可以节省一个指令

      set pindirs, 1 ; Set pin to output
.wrap_target
      set pins, 1 [1] ; Drive pin high  and delay for one cycle
      set pins, 0 [1] ; Drive pin low  and delay for one cycle
.wrap

以上大致就是PIO的内容,虽然还有些小细节,但是不妨碍我们使用

函数详解

【MicroPython】PIO有关函数详解

MicroPython引入了一个新的@rp2.Asm_pio装饰器和rp2.PIO类.

  • PIO程序的定义和状态机的配置分为2个逻辑部分:
    1. 程序定义,包括使用了多少引脚,如果它们是in/out引脚。这在@rp2.asm_pio中定义。
    2. 程序,设置状态机的频率和绑定到哪个引脚。当设置一个状态机来运行特定的程序时,就会设置这些参数。

所有的程序配置(例如autopull)都是在@asm_pio装饰器中完成的,在状态机的构造函数中只需要设置频率和基础引脚。 只设置频率和基础引脚需要在StateMachine构造函数中设置。

@asm_pio装饰器

让其它函数不需要任何代码上的改动,增加额外的功能

@asm_pio(
    out_init=None,
    set_init=None,
    sideset_init=None,
    in_shiftdir=0,
    out_shiftdir=0,
    autopush=False,
    autopull=False,
    push_thresh=32,
    pull_thresh=32,
    fifo_join=0
)

  • out_init :输出引脚初始化
  • set_init :设置引脚初始化
  • sideset_init :侧置引脚初始化
  • 前三个引脚可以设置为rp2.PIN.OUT_HIGH 输出高电平
  •                                      PIN.OUT_LOW  输出低电平
    
    
* 需要注意的是MicroPython 是通过侧置引脚初始化来计算使用了多少个侧置引脚,并决定测置和延迟字段的结构
* in\_shiftdir : 数据输入方向
* out\_shiftdir:数据输出方向
* 以上决定了数据移动的方向
* 如输出情况下,数据向左移动,则OSR的高位先进行输出,若向右移动,则低位先进行输出
* autopush :自动推送,若开启,当ISR达到阈值,自动将ISR传输到RX-FIFO。
* autopull :自动拉取,若开启,当OSR达到阈值,自动将TX-FIFO传输到OSR。
* push\_thresh :推送阈值
* pull\_thresh :拉取阈值
* 字面意思,结合自动推送和自动拉取使用
* fifo\_join :fifo组合,指定一个FIFO,将另一个FIFO关闭并加入改FIFO,获取更深位数的FIFO.
* 如果我们指定TXFIFO,我们就会获得一个8位深度的TXFIFO(32bit\*8)


#### rp2.StateMachine函数详解


MicroPython 使用PIO是以状态机为单位的,所以我们的函数都是围绕状态机的


* rp2.StateMachine.init(sm\_id ,program, freq=-1, \*, in\_base=None, out\_base=None, set\_base=None, jmp\_pin=None, sideset\_base=None, in\_shiftdir=None, out\_shiftdir=None, push\_thresh=None, pull\_thresh=None)


	+ sm\_id:使用状态机ID,0-3为PIO0,4-7为PIO1
	+ program:PIO运行程序(状态机运行程序)
	+ freq:状态机运行频率,默认为系统时钟频率,
		- 时钟分频器的分配因子计算公式为“系统时钟频率/频率”,所以 可能存在轻微的舍入误差。
		- 最小可能的时钟分频器是系统时钟的 65536 分之一:所以在默认系统时钟频率 125MHz下,最小值为1908。
		- 要以较慢的频率运行状态机,需要使用“machine.freq()”降低系统时钟速度。
	+ in\_base:用于in()指令的第一个引脚
	+ out\_base:用于out()指令的第一个引脚
	+ set\_base:用于set()指令的第一个引脚
	+ jmp\_pin:用于jmp(pin, …)指令的第一个引脚
	+ sideset\_base:是用于侧置的第一个引脚。
	+ in\_shiftdir:ISR将移动的方向,可为PIO.SHIFT\_LEFT或者PIO.SHIFT\_RIGHT
	+ out\_shiftdir: OSR 将移动的方向,可为PIO.SHIFT\_LEFT或者PIO.SHIFT\_RIGHT
	+ push\_thresh:推送阈值
	+ pull\_thresh:拉取阈值
* StateMachine.active([value])


	+ 获取或设置状态机当前是否正在运行。
	+ 当value不为空时,设置状态机,反之获取运行状态。
* StateMachine.restart()


	+ 重新启动状态机并跳转到程序的开头。
* * StateMachine.exec(instr)


	+ 执行单个 PIO 指令。使用 `asm_pio_encode` 编码 来自给定指令字符串 *instr* 的指令。
* StateMachine.get(buf=None, shift=0)


	+ 从状态机的RX-FIFO中提取一个字。
	+ 如果FIFO为空,它会阻塞直到数据到达(即状态机推一个字)。
	+ shift为在返回之前右移位数
	+ 返回值是“word >> shift”
* StateMachine.put(value, shift=0)


	+ 将一个字推送到状态机的 TX FIFO。
	+ 如果 FIFO已满,它将阻塞直到有空间(即状态机拉一个字)。
	+ shift为在返回之前右移位数
	+ 返回值是“word >> shift”
* StateMachine.rx\_fifo()


	+ 返回状态机的 RX FIFO 中的字数。值为0表示 FIFO 为空。
	+ 用于在调用之前检查数据是否正在等待读取`StateMachine.get()`。
* StateMachine.tx\_fifo()


	+ 返回状态机的 RX FIFO 中的字数。值为0表示 FIFO 为空。
	+ 用于在调用之前检查数据是否正在等待读取`StateMachine.put()`。
* StateMachine.irq(handler=None, trigger=0|1, hard=False)


	+ 返回给定 StateMachine 的 IRQ 对象。


#### PIO\_ASM


1. JMP (condition) target


	1. target :跳转地址,允许数值0-31,因为PIO只有32条指令空间。
	2. condition :
		1. !X OR !Y:
		2. X– OR Y–
		3. X!=Y :
		4. PIN :
		5. !(OSRE):
	* JMP指令作用是将达到某些条件的情况下将程序跳转到指定地址.
	* 当条件达成时,程序就会根据target跳转指定地址
	* 当然也可以不填写条件,则会无条件跳转指定地址。
2. WAIT Polarity Source Index


	1. Polarity: 等待 0 OR 1
	2. Source:
		1. GPIO:绝对GPIO
		2. PIN:引脚映射后的引脚
		3. IRQ:中断标志
	3. Index:
		1. GPIO\_num:对应GPIO源,GPIO数值
		2. pin\_num:对应pin源,pin数值
		3. IRQ\_num:对应IRQ源,指定等待的引脚或者位,这里IRQ\_NUM也是支持使用(\_rel)
	* WAIT指令作用为在条件达成前等待
3. IN Source,Bitcount


	1. Source :
		1. PINS
		2. X
		3. Y
		4. NULL
		5. ISR
		6. OSR
	2. Bitcount :读取位数
	* IN指令作用将数据存入ISR寄存器
4. OUT destination,Bitcount


	1. destination :
		1. PINS
		2. X
		3. Y
		4. NULL
		5. PINDIRS
		6. PC
		7. ISR
		8. OSR
	2. Bitcount :读取位数
	* OUT和IN功能相反,指令作用将OSR寄存器输出到目标
5. PUSH (IfFull) (Block/noBloc k)


	1. IfFull,若为1则只有ISR到达阈值,才能推送.
	2. Block,若为1且RX FIFO达到阈值,就会进行将数据推送到ISR中,否则会等待RXFIO达到阈值,若为0则RXFIFO达不到阈值,则不会进行推送。
	* PUSH指令的作用为将ISR中内容推送到RX FIFO和清空ISR
6. PULL (IfEmpty) (Block/noBloc k)


	1. IfEmpty:若为1则只有OSR才会接收TXFIFO的数据。
	2. Block:1则TXFIFO为空则等待TXFIFO.
	* PUSH指令的作用将从TXFIFO数据读出到OSR寄存器。
7. MOV destinationmov,(Operation ),source


	1. destination:
		1. PINS:输出映射
		2. X:
		3. Y:
		4. EXEC:解码寄存器
		5. PC:PC寄存器(JUM)
		6. ISR
		7. EXEC:
	2. source:
		1. PINS:
		2. X
		3. Y
		4. NULL:空,用于清零
		5. STATUS:表示不同状态,如fifo满或者空。
		6. ISR:
		7. OSR:
	3. Operation:
		1. 00:不改变
		2. 01:位取反
		3. 10:位翻转,高低位互换。
	* MOV 指令其作用为将数据从源移动目标寄存器。
8. IRQ (option) irq\_num (\_rel)


	1. irq\_num:0-7 中断标志位
	2. option
		1. set(默认):设置
		2. nowait(默认):不等待清除
		3. wait:等待清除后,运行
		4. clear:清除
	3. \_rel:若存在则将irq\_num和状态机编码sm\_num相加并进行模四运行并将高位置1,若状态机2设置中断标识3,中断值则为0X11
	* irq\_flag=(sm\_id+irq\_num) %4 + 0x10
	* IRQ指令作用为设置或者清空中断标识
9. SET destination,data


	1. destination:目标地址
		1. PINS :SET映射引脚
		2. PINDIRS :引脚方向,将第一个映射GPIO1为输出,0为输入
		3. X : 暂存寄存器,暂存数据
		4. Y : 暂存寄存器,暂存数据
	2. data :数据, 0-31
	* set指令会将数据写入目标地址.通常用于控制设置映射引脚。


### 例程地址


* [Github仓库](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)


[MicroPython源码](https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0)


## 实操


原理图



> 
> Pico 的GPIO4连接到WS2812B的数据输入引脚  
>  ![![[Pasted image 20220924180415.png]]](https://img-blog.csdnimg.cn/c813b08e865441008e3b96ba0bde7fd2.png)
> 
> 
> 


* WS2812B是一款单总线驱动的控制电路与发光电路于一体的智能外控LED光源  
 - 可采用单线输出方式,串接LED使之输出动作同步;  
 - 数据协议采用单极性归零码  
 - 因为内部基础控制电路,显示更趋细腻平滑,解决拍摄画面暗条纹问题  
 - 数据发送速度可达800Kbps
* WS2812的通讯协议
* ![![[Pasted image 20220924181107.png]]](https://img-blog.csdnimg.cn/f3ece358495d4504923d14a84d0a3333.png)



> 
> 该协议与之前的协议不太一样,其是通过电平的时长来判断数据为0还是1  
>  如果我们需要发送数据0,需要先拉高电平220ns - 380 ns 紧接着拉低电平580ns-1us  
>  ![![[Pasted image 20220924181022.png]]](https://img-blog.csdnimg.cn/a61d622feb66454eb36f2f1346f68fe3.png)
> 
> 
> 



> 
> 数据1也是相同的原理,![![[Pasted image 20220924181056.png]]](https://img-blog.csdnimg.cn/b39c138ca21043bfb1b286373557f93f.png)
> 
> 
> 


ws2812传输数据结构如表格,且ws2812的数据发送是先进行高位发送,也就是数据是向左移动的  
 ![![[Pasted image 20220924181242.png]]](https://img-blog.csdnimg.cn/bf75fe520bb240c2b10e34347a181762.png)



先导入需要的库

import time
from machine import Pin
import rp2

WS2812的PIO驱动程序

通过asm_pio装饰器初始化pio

初始化了侧置引脚,因为这里只有一个初始化信息,说明侧置引脚的数量为1

数据输出移动方向为由右向做移动

开启自动拉取,当OSR达到阈值,自动将TXFIFO中的数据拉取到OSR中

拉取阈值为24位,也就是发送数据的长度

@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, Pull_thresh=24)

def ws2812():
T1 = 2
T2 = 5
T3 = 3
# wrap_target()和wrap()进行了程序包装,让程序在两者间运行
wrap_target()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值