prettyprint

2022年10月14日 星期五

Raspberry Pi Pico PIO(Programmable IO) Episode 3: DHT11 (Humidity and Temperature sensor)

本文介紹使用Raspberry Pi Pico PIO功能來讀取DHT11溫濕度資料,顯示在1602A LCD上。


一、硬體接線:

LCD 1602A:     Raspberry Pi Pico

VSS:                GND
VDD:              Pin40(VBUS), 5V
VO:                10k可變電阻
RS:                 Pin 11
RW:                GND
E:                  Pin 10
D4:               Pin 6
D5:               Pin 7
D6:               Pin 8
D7:               Pin 9
A:                5V
K:                GND

DHT11        Raspberry Pi Pico
Pin2:             Pin 16

VDD:         5V


二、PIO程式碼與DHT11 datasheet對照說明:







設定autopush=True, push_thresh=32所以前4bytes共32bits,StateMachine自動push到RX_FIFO中。
最後第五個byte(CRC)因為未滿threshold所以需要下push指定到RX_FIFO。

三、成果影片:



四、程式碼:
 DHT11.py
from machine import Pin
import rp2
from utime import sleep_ms
class DHT11():
    """ output humidity(rh) and temperature(temp), call get_data to get values """
    @rp2.asm_pio(set_init=rp2.PIO.OUT_HIGH, autopush=True,  push_thresh=32, in_shiftdir=rp2.PIO.SHIFT_LEFT)
    def dht11():
        wrap_target()
        set(pindirs,1)                # set pin as output
        set(pins,0)                   # set low for 20ms (at least 18ms)
        set(x,29)                     #freq=500000, 1 cycle = 2us (30*30*11*2 = 19.8ms)
        label("start_loop_1")
        set(y,29)
        label("start_loop_2")
        jmp(y_dec, "start_loop_2") [10]
        jmp(x_dec, "start_loop_1")
        set(pins,1) [14]             # pull-up for 2us*15 = 30us (20~40 us)
        set(pindirs,0)               # set pin as input
        wait(0,pin,0) [19] 
        nop() [19]                   # wait for low 2*(20+20)=80us
        wait(1,pin,0) [19] 
        nop()  [19]                  # wait for high 2*(20+20)=80us
        
        set(x,4)                     # 5 bytes
        label("bytes")
        set(y,7)                     # each byte: 8 bits
        label("bits")
        wait(0,pin,0) [14]           # LOW:  wait for 2us*15=30us (50us)  
        wait(1,pin,0) [19]           # HIGH: wait at least 2*20=40 us (26~28us: 0, 70us: 1)
        in_(pins,1)   
        jmp(y_dec,"bits")            # autopush and push_thresh=32, auto push 4 data byte
        jmp(x_dec,"bytes")
        push()                       # push last one CRC byte
        nop() [31]                   #transmition completed, low for 50us and pull-high
        wait(0,pin,0)                # block for next
        
        wrap()
    
    def __init__(self, pin_num, smid=0):
        self.pin = Pin(pin_num, Pin.IN, Pin.PULL_UP)
        self.sm=rp2.StateMachine(smid, self.dht11, freq=500000, in_base=self.pin, set_base=self.pin)
        self.sm.active(1)
        self.rh=0.0
        self.temp=0.0
    
    def get_data(self):
        self.sm.restart()
        data=self.sm.get()
        checksum=self.sm.get()
        bytes=[]
        for i in range(3,-1,-1):
            bytes.append((data>>8*i)&0xff)
        if ((bytes[0]+bytes[1]+bytes[2]+bytes[3]) == checksum):
            self.rh = float('{}.{}'.format(bytes[0], bytes[1]))
            self.temp = float('{}.{}'.format(bytes[2], bytes[3]))
        else:
            self.rh=0.0
            self.temp=0.0
        del bytes
        sleep_ms(1120) #Sampling period at intervals should be no less than 1 second.
LCD1602A.py
# base on https://github.com/wjdp/micropython-lcd/blob/master/lcd.py

from machine import Pin
from utime import sleep_us

class LCD1602A():
    
    PIN_NAMES = ['RS','E','D4','D5','D6','D7']
    
    # Define some device constants
    LCD_WIDTH = 16    # Maximum characters per line
    # Designation of T/F for character and command modes
    LCD_CHR = True
    LCD_CMD = False

    LINES = {
        0: 0x80, # LCD RAM address for the 1st line
        1: 0xC0, # LCD RAM address for the 2nd line
        # Add more if desired
    }

    # Timing constants
    E_PULSE = 50
    E_DELAY = 50

    def __init__(self, rs, e,*,d4=None, d5=None, d6=None, d7=None):
        self.pins={}
        self.PINS=[rs, e, d4, d5, d6, d7]
        # Initialise pins
        for pin, pin_name in zip(self.PINS, self.PIN_NAMES):
            self.pins['LCD_'+pin_name] = Pin(pin, Pin.OUT)
        # Initialise display
        self.lcd_byte(0x33,self.LCD_CMD)
        self.lcd_byte(0x32,self.LCD_CMD)
        self.lcd_byte(0x28,self.LCD_CMD)
        self.lcd_byte(0x0C,self.LCD_CMD)
        self.lcd_byte(0x06,self.LCD_CMD)
        self.lcd_byte(0x01,self.LCD_CMD)
        

    def clear(self):
        # Clear the display
        self.lcd_byte(0x01,self.LCD_CMD)

    def set_line(self, line):
        # Set the line that we're going to print to
        self.lcd_byte(self.LINES[line], self.LCD_CMD)

    def set_string(self, message):
        # Pad string out to LCD_WIDTH
        # message = message.ljust(LCD_WIDTH," ")
        m_length = len(message)
        if m_length < self.LCD_WIDTH:
            short = self.LCD_WIDTH - m_length
            blanks=str()
            for i in range(short):
                blanks+=' '
            message+=blanks
        for i in range(self.LCD_WIDTH):
            self.lcd_byte(ord(message[i]), self.LCD_CHR)

    def lcd_byte(self, bits, mode):
        # Send byte to data pins
        # bits = data
        # mode = True  for character
        #        False for command

        self.pin_action('LCD_RS', mode) # RS

        # High bits
        self.pin_action('LCD_D4', False)
        self.pin_action('LCD_D5', False)
        self.pin_action('LCD_D6', False)
        self.pin_action('LCD_D7', False)
        if bits&0x10==0x10:
            self.pin_action('LCD_D4', True)
        if bits&0x20==0x20:
            self.pin_action('LCD_D5', True)
        if bits&0x40==0x40:
            self.pin_action('LCD_D6', True)
        if bits&0x80==0x80:
            self.pin_action('LCD_D7', True)

        # Toggle 'Enable' pin
        self.udelay(self.E_DELAY)
        self.pin_action('LCD_E', True)
        self.udelay(self.E_PULSE)
        self.pin_action('LCD_E', False)
        self.udelay(self.E_DELAY)

        # Low bits
        self.pin_action('LCD_D4', False)
        self.pin_action('LCD_D5', False)
        self.pin_action('LCD_D6', False)
        self.pin_action('LCD_D7', False)
        if bits&0x01==0x01:
            self.pin_action('LCD_D4', True)
        if bits&0x02==0x02:
            self.pin_action('LCD_D5', True)
        if bits&0x04==0x04:
            self.pin_action('LCD_D6', True)
        if bits&0x08==0x08:
            self.pin_action('LCD_D7', True)

        # Toggle 'Enable' pin
        self.udelay(self.E_DELAY)
        self.pin_action('LCD_E', True)
        self.udelay(self.E_PULSE)
        self.pin_action('LCD_E', False)
        self.udelay(self.E_DELAY)

    def udelay(self, us):
        # Delay by us microseconds, set as function for portability
        #pyb.udelay(us)
        sleep_us(us)

    def pin_action(self, pin, high):
        # Pin high/low functions, set as function for portability
        if high:
            self.pins[pin].on()
        else:
            self.pins[pin].off()
            
            

main.py
from machine import Pin
from DHT import DHT11
from LCD1602A import LCD1602A
import utime


lcd=LCD1602A(11,10, d4=6,d5=7,d6=8,d7=9)
dht=DHT11(16)

while True:
    dht.get_data()
    lcd.set_line(0)
    lcd.set_string("Temp:"+str(dht.temp)+"C")
    lcd.set_line(1)
    lcd.set_string("RH:  "+str(dht.rh)+"%")
    #utime.sleep(0.5)

2022年10月8日 星期六

Raspberry Pi Pico PIO(Programmable IO) Episode 2: IR remote controller

 本篇文章介紹使用Raspberry Pi Pico的PIO(Programmable IO)功能來建立一個紅外線遙控器。測試使用NEC 與SAMSUNG protocol。

NEC protocol測試設備使用中華電信MOD機上盒,SAMSUNG protocol使用SAMSUNG TV測試。

使用元件:
  1. 4X4 matrix keypad
  2. IR LED 940nm
  3. 200歐姆電阻
  4. Raspberry Pi Pico
使用MicroPython語言與Thonny開發環境,作業系統為FreeBSD 13.1 + KDE Plasma。

一、紅外線遙控器協定簡介:

使用頻率約為38KHz,
  1. pulse: period 26.3 us, duty factor=1/3
  2. space: period 26.3us的low
  3. pulse burst: 為一連串pulse組成。
如下圖所示:



NEC Protocol:
  1. logical 0:一個維持562.5µs pulse burst 跟著一個維持562.5µs space, 時間長度為1.125ms
  2. logical 1: 一個維持562.5µs pulse burst 跟著一個維持1687.5µs space, 時間長度為2.25ms
一個按鍵按下時傳送的資料如下:
總時間長度為108ms
  1. 9ms pulse burst
  2. 4.5ms space
  3. 8-bit(八個logical 0 or logical 1組成)位址
  4. 8-bit反向位址(不反向亦可)
  5. 8-bit command
  6. 8-bit logical inverse of the command
  7. 562.5µs pulse burst結束訊息
  8. 剩下送出space直到108ms。
    如下圖所示:


若按鍵按下可送出多的repeat code:
  1. 9ms pulse burst
  2. 2.25ms space
  3. 562.5us pulse space
  4. 剩下送出space直到108ms。
SAMSUNG protocol:
  1. Start bits: 4.5ms pulse burst + 4.5ms space
  2. 32 bits address + command(logical 0: 590us pulse+590us space, logical 1: 590us pulse burst+1690us space)
  3. stop bits: 590us pulse burst + 590us space

二、4x4 matrix keypad:

本文將前一篇的程式碼精簡為在一個StateMachine中完成。詳細說明在下一節。

三、程式碼說明:

  • 腳位接線:IR LED接GPIO16,4x4 matrix keypad左至右pin1~pin8分別接GPIO2~9。
  • RP2040有兩個PIO blocks,為PIO0與PIO1,每個PIO有4個StateMachine共用大小為32指令的Instruction Memory,因此每個PIO的程式碼不能多於32指令。
  • PIO0 StateMachine(0)負責keypad,PIO1 StateMachine(4)負責IR LED(GIPO16)訊號發送。
  • 每個StateMachine有ClockDiv register因此每個StateMachine可以擁有自己的clock frequency。
  • StateMachine共有9個指令(jmp, wait, in_, out, pull, push, mov, irq, set)每個instruction只需要一個clock cycle即可完成執行。
keypad的StateMachine(0)負責按鍵,所以速度使用較慢freq=2000,每個clcye使用1/2000sec=0.5ms

IR遙控器使用的頻率約為38kHz,我們使用的pulse duty factor為1/3,每個pulse或space使用3個clock cycle,所以StateMachine(4)的freq=3*28k=114k。

pulse:1 clock cycle HIGH, 2 clock cycles LOW

space: 3 clock cycles LOW(延續前一個狀態LOW)

  • NEC IR protocol:
    message: (pulse burst) --> (space) --> (address+command) --> (pulse burst) --> (space)
    repeat code: (pulse burst) --> (space) -->  (pulse burst) --> (space)
  • SAMSUNG IR protocol:
     message格式為:(pulse burst) --> (space) --> (address+command) --> (pulse burst) --> (space)
每個StateMachine的instruction memory只有32 instructions大小,因此加入pseudo address+command的概念,則每個message可視為執行兩次(pulse burst) --> (space) --> (address+command)。
  • NEC IR protocol:
    message: (pulse burst) --> (space) --> (address+command) --> (pulse burst) --> (space)-->(pseudo)
    repeat code: (pulse burst) --> (space)-->(pseudo) -->  (pulse burst) --> (space)-->(pseudo)
  • SAMSUNG IR protocol:
     message格式為:(pulse burst) --> (space) --> (address+command) --> (pulse burst) --> (space)-->(pseudo)
每個pulse或space的時間為1/38k sec=26.3us, 
9ms = 342x26.3us
4.45ms = 171*26.3us
2.25ms = 86*26.3us
562.5us=21*26.3us
將所需的時間換成次數輸入到StateMachine的TX FIFO中,


完整程式碼附在文章後面。

四、成果影片:



五、程式碼

from machine import Pin
import rp2

@rp2.asm_pio(out_init=rp2.PIO.OUT_LOW,set_init=rp2.PIO.OUT_LOW,out_shiftdir=1, autopull=False, pull_thresh=32)
def sendNECIRCommand():
    wrap_target()
    set(y,1)
    label("loop")
    pull()
    mov(x,osr)
    label("pulse")
    set(pins,1)
    set(pins,0)
    jmp(x_dec,"pulse")
    pull()
    mov(x,osr)
    label("space")
    jmp(x_dec, "space") [2]
    #check if command data
    pull()
    mov(x, osr)
    jmp(not_x, "return_here")
    jmp("command")
    label("return_here")
    jmp(y_dec, "loop")
    irq(rel(0))
    wait(1,pin,0)
    
    label("command")
    #address and command
    set(y,31)
    label("bit")
    set(x, 20)
    label("data_pulse")
    set(pins,1)
    set(pins,0)
    jmp(x_dec, "data_pulse") #total cycles 3*21=63, 553us
    set(x,20)
    label("data_space")
    jmp(x_dec, "data_space") [2] # total cycle:3*21=62, 553us
    out(x,1)
    jmp(not_x, "not_x_bit")
    set(x,20)
    label("data_space_1")
    jmp(x_dec, "data_space_1")[5] # 6*21=126, 1105us (1105+552)=1657us
    label("not_x_bit")
    jmp(y_dec,"bit")
    jmp("return_here")
    
    wrap() # total 29 instructions


@rp2.asm_pio(set_init=(rp2.PIO.OUT_LOW,)*4)
def keypad():
    wrap_target()
    label("set_row_1")
    set(pins, 1) [31]  #set row_1 High and wait for button bounce(0.5ms*32=16ms)
    set(x,0x1)
    in_(pins,4)
    mov(y, isr)
    jmp(not_y,"set_row_2")
    jmp("rx_fifo")
    
    label("set_row_2")
    set(pins, 2) [31] #set row_2 High and wait for button bounce
    set(x,0x2)
    in_(pins,4)
    mov(y, isr)
    jmp(not_y,"set_row_3")
    jmp("rx_fifo")
    
    label("set_row_3")
    set(pins, 4) [31] #set row_3 High and wait for button bounce
    set(x,0x4)
    in_(pins,4)
    mov(y, isr)
    jmp(not_y,"set_row_4")
    jmp("rx_fifo")
    
    label("set_row_4")
    set(pins, 8) [31] #set row_4 High and wait for button bounce
    set(x,0x8)
    in_(pins,4)
    mov(y, isr)
    jmp(not_y,"set_row_1")
  
    
    label("rx_fifo")
    push()              #push y(col) 
    in_(x,4)[2]
    push()              #and then x(row)
    irq(rel(0))
    
    wait(0,pin,0)  # check whether key is released
    wait(0,pin,1)
    wait(0,pin,2)
    wait(0,pin,3)
    wrap()   # total 31 instructions
#NEC    
keys=[[0xde21bebe,0xdd22bebe,0xdc23bebe,0xc936bebe], # 1,2,3,A => 1,2,3,VOL+
      [0xdb24bebe,0xda25bebe,0xd926bebe,0xc837bebe], # 4,5,6,B => 1,2,3,VOL-
      [0xd827bebe,0xd728bebe,0xd629bebe,0x847bbebe], # 7,8,9,C => 1,2,3,CH+
      [0x8f70bebe,0xdf20bebe,0x807fbebe,0x837cbebe]] # *,0,#,D => 1,2,3,CH-
#SANSUNG
#keys=[[0xfb040707,0xfa050707,0xf9060707,0xf8070707], # 1,2,3,A => 1,2,3,VOL+
#      [0xf7080707,0xf6090707,0xf50a0707,0xf40b0707], # 4,5,6,B => 1,2,3,VOL-
#      [0xf30c0707,0xf20d0707,0xf10e0707,0xed120707], # 7,8,9,C => 1,2,3,CH+
#      [0xfd020707,0xee110707,0x97680707,0xef100707]] # *,0,#,D => 1,2,3,CH-

def sendIRCommand(cmd):
    #NEC
    smIR.restart()
    smIR.put(341)
    smIR.put(170)
    smIR.put(cmd)
    smIR.put(20)
    smIR.put(1290)
    smIR.put(0)
    #send repeat once
    smIR.put(341)
    smIR.put(85)
    smIR.put(0)
    smIR.put(20)
    smIR.put(3653)
    smIR.put(0)
    
    #SAMSUNG
    #smIR.restart()
    #smIR.put(170)
    #smIR.put(170)
    #smIR.put(cmd)
    #smIR.put(20)
    #smIR.put(20)
    #smIR.put(0)


def keypad_irq(sm):
    y=sm.get()
    x=sm.get()
    for i in range(4):
        if x >> i == 1:
            break;
    for j in range(4):
        if y >> j == 1:
            break;
    sendIRCommand(keys[i][j])

IRpin=Pin(16, Pin.OUT, Pin.PULL_DOWN)
smkeypad=rp2.StateMachine(0, keypad, freq=2000, set_base=Pin(2), in_base=Pin(6))
smIR=rp2.StateMachine(4, sendNECIRCommand, freq=114000, set_base=IRpin) #freq=3*38k=114k
smkeypad.irq(keypad_irq)
smkeypad.active(1)
smIR.active(1)


2022年10月1日 星期六

Raspberry Pi Pico PIO(Programmable IO) Episode 1: 4x4 matrix keypad

一、Raspberry PIO簡述: 

Raspberry Pi pico比較獨特的是具有PIO(programmable IO),相當於具有8小型的專門處理IO的處理器,分為兩個PIO區塊,每個區塊有4個StateMachine,其架構如下圖所示:

(PIO block, 取自RP2040 datasheet)
  1. 每個PIO block的4個StateMachine共用32 Instructions 的Instruction Memory,雖然小但也夠用。
  2. 每個StateMachine有4個32 bits output FIFO與4個32 bits input FIFO,有可結合成單一方向的8個32 bits FIFO。
  3. 有兩個通用32 bits register: X and Y
  4. ISR 與OSR: input shift register and output shift register
  5. Clock Div: 可除頻1~65536。

(StateMachine, 取自RP2040 datasheet)
指令集:
(取自RP2040 datasheet)


二、4x4 Keypad:


腳位從左到右為pin1~pin8,pin1~4同一時間只能有一個腳位設為High,當pin1為high時,按下key 1,則pin5輸出為hight。

三、實做Raspberry Pi Pico PIO:

使用MicroPython程式語言來實作。
4x4 keypad與raspberry pi pico腳位接線如上圖所示。

pin 1~4同時間只能有一個腳位為高電位,使用PIO1 block的StateMachine(4) set(pins,4)指令達成需求,程式碼如下:

@rp2.asm_pio(set_init=(rp2.PIO.OUT_LOW,rp2.PIO.OUT_LOW,rp2.PIO.OUT_LOW,rp2.PIO.OUT_LOW))
def scanRowHigh():
    wrap_target()
    set(pins, 1) [3] # each pin is high for at least 8 cycles (1+3+1+1+1+1)
    wait(0,pin,0)
    wait(0,pin,1)
    wait(0,pin,2)
    wait(0,pin,3) 
    set(pins, 2) [3] 
    wait(0,pin,0)
    wait(0,pin,1)
    wait(0,pin,2)
    wait(0,pin,3) 
    set(pins, 4) [3] 
    wait(0,pin,0)
    wait(0,pin,1)
    wait(0,pin,2)
    wait(0,pin,3) 
    set(pins, 8) [3]
    wait(0,pin,0)
    wait(0,pin,1)
    wait(0,pin,2)
    wait(0,pin,3) 
    wrap()

smr = rp2.StateMachine(4, scanRowHigh, freq=2000, set_base=Pin(2), in_base=col1pin)
smr.active(1)

第一行(1,4,7,*)鍵按下時,則pin5為高電位,使用PIO0 StateMachine(0) 的指令jmp(pin, "which_row")來判斷是否有按鍵按下。若有按鍵按下則使用in_(pins,4)來讀取GPIO2~5是那一個pin是高電位,則可判斷是那一個按鍵按下。接下來觸發irq,使用指令irq(rel(0))輸出按鍵內容。最後等待按鍵釋放(wait(0, pin, 0)。第二行按鍵使用PIO0 StateMachine(1) ,第三行按鍵使用PIO0 StateMachine(2),第四行按鍵使用PIO0 StateMachine(3)。程式碼如下所示。

@rp2.asm_pio()
def keypad():
    wrap_target()
    label("start")
    jmp(pin, "which_row") 
    jmp("start")
    label("which_row")
    in_(pins,4)
    push() 
    irq(rel(0)) [31] #wait for button bounce(at least 30ms: (32+32)*0.5ms)
    label("delay")
    nop() [31]
    jmp(pin, "delay") # wait for key to be released
    wrap()
    
def getkeyvalue(sm):
    row=-1
    if sm == rp2.StateMachine(0):
        row=0
    elif sm == rp2.StateMachine(1):
        row=1
    elif sm == rp2.StateMachine(2):
        row=2
    elif sm == rp2.StateMachine(3):
        row=3
    
    while sm.rx_fifo() :
        term = sm.get()
        for i in range(4):
            if term >> i == 1:
                print(keyvalues[row][i])
                break;

keyvalues=[["1","4","7","*"],
           ["2","5","8","0"],
           ["3","6","9","#"],
           ["A","B","C","D"]]

col1pin=Pin(6, Pin.IN, Pin.PULL_DOWN)
col2pin=Pin(7, Pin.IN, Pin.PULL_DOWN)
col3pin=Pin(8, Pin.IN, Pin.PULL_DOWN)
col4pin=Pin(9, Pin.IN, Pin.PULL_DOWN)

smc1=rp2.StateMachine(0, keypad, freq=2000,  in_base=Pin(2),jmp_pin=col1pin)
smc2=rp2.StateMachine(1, keypad, freq=2000,  in_base=Pin(2),jmp_pin=col2pin)
smc3=rp2.StateMachine(2, keypad, freq=2000,  in_base=Pin(2),jmp_pin=col3pin)
smc4=rp2.StateMachine(3, keypad, freq=2000,  in_base=Pin(2),jmp_pin=col4pin)

smc1.irq(getkeyvalue)
smc2.irq(getkeyvalue)
smc3.irq(getkeyvalue)
smc4.irq(getkeyvalue)
smc1.active(1)
smc2.active(1)
smc3.active(1)
smc4.active(1)

四、成果影片:


五、完整程式碼:

第二個程式碼為修正第二版,程式碼更簡潔。
👉第一版
from machine import Pin
import rp2

@rp2.asm_pio(set_init=(rp2.PIO.OUT_LOW,rp2.PIO.OUT_LOW,rp2.PIO.OUT_LOW,rp2.PIO.OUT_LOW))
def scanRowHigh():
    wrap_target()
    set(pins, 1) [3] 
    wait(0,pin,0)
    wait(0,pin,1)
    wait(0,pin,2)
    wait(0,pin,3) 
    set(pins, 2) [3] 
    wait(0,pin,0)
    wait(0,pin,1)
    wait(0,pin,2)
    wait(0,pin,3) 
    set(pins, 4) [3] 
    wait(0,pin,0)
    wait(0,pin,1)
    wait(0,pin,2)
    wait(0,pin,3) 
    set(pins, 8) [3]
    wait(0,pin,0)
    wait(0,pin,1)
    wait(0,pin,2)
    wait(0,pin,3) 
    wrap()
    
@rp2.asm_pio()
def keypad():
    wrap_target()
    label("start")
    jmp(pin, "which_row") 
    jmp("start")
    label("which_row")
    in_(pins,4)
    push() 
    irq(rel(0)) [31] 
    label("delay")
    nop() [31]
    jmp(pin, "delay") # wait for key to be released
    wrap()
    
def getkeyvalue(sm):
    row=-1
    if sm == rp2.StateMachine(0):
        row=0
    elif sm == rp2.StateMachine(1):
        row=1
    elif sm == rp2.StateMachine(2):
        row=2
    elif sm == rp2.StateMachine(3):
        row=3
    
    while sm.rx_fifo() :
        term = sm.get()
        for i in range(4):
            if term >> i == 1:
                print(keyvalues[row][i])
                break;

keyvalues=[["1","4","7","*"],
           ["2","5","8","0"],
           ["3","6","9","#"],
           ["A","B","C","D"]]

col1pin=Pin(6, Pin.IN, Pin.PULL_DOWN)
col2pin=Pin(7, Pin.IN, Pin.PULL_DOWN)
col3pin=Pin(8, Pin.IN, Pin.PULL_DOWN)
col4pin=Pin(9, Pin.IN, Pin.PULL_DOWN)

smc1=rp2.StateMachine(0, keypad, freq=2000,  in_base=Pin(2),jmp_pin=col1pin)
smc2=rp2.StateMachine(1, keypad, freq=2000,  in_base=Pin(2),jmp_pin=col2pin)
smc3=rp2.StateMachine(2, keypad, freq=2000,  in_base=Pin(2),jmp_pin=col3pin)
smc4=rp2.StateMachine(3, keypad, freq=2000,  in_base=Pin(2),jmp_pin=col4pin)

smr = rp2.StateMachine(4, scanRowHigh, freq=2000, set_base=Pin(2), in_base=col1pin)
smr.active(1)

smc1.irq(getkeyvalue)
smc2.irq(getkeyvalue)
smc3.irq(getkeyvalue)
smc4.irq(getkeyvalue)
smc1.active(1)
smc2.active(1)
smc3.active(1)
smc4.active(1)
👉第二版
from machine import Pin
import rp2

@rp2.asm_pio(set_init=(rp2.PIO.OUT_LOW,)*4)
def keypad():
    wrap_target()
    label("set_row_1")
    set(pins, 1) [31]  #set row_1 High and wait for button bounce
    set(x,0x1)
    in_(pins,4)
    mov(y, isr)
    jmp(not_y,"set_row_2")
    jmp("rx_fifo")
    
    label("set_row_2")
    set(pins, 2) [31]
    set(x,0x2)
    in_(pins,4)
    mov(y, isr)
    jmp(not_y,"set_row_3")
    jmp("rx_fifo")
    
    label("set_row_3")
    set(pins, 4) [31]
    set(x,0x4)
    in_(pins,4)
    mov(y, isr)
    jmp(not_y,"set_row_4")
    jmp("rx_fifo")
    
    label("set_row_4")
    set(pins, 8) [31]
    set(x,0x8)
    in_(pins,4)
    mov(y, isr)
    jmp(not_y,"set_row_1")
  
    
    label("rx_fifo")
    push()              #push y(col) 
    in_(x,4)[2]
    push()              #and then x(row)
    irq(rel(0))
    
    wait(0,pin,0)  # check whether key is released
    wait(0,pin,1)
    wait(0,pin,2)
    wait(0,pin,3)
    wrap()   # total 31 instructions
    
keys=[["1","2","3","A"],
      ["4","5","6","B"],
      ["7","8","9","C"],
      ["*","0","#","D"]]

def keypad_irq(sm):
    y=sm.get()
    x=sm.get()
    for i in range(4):
        if x >> i == 1:
            break;
    for j in range(4):
        if y >> j == 1:
            break;
    print(keys[i][j])
    
smkeypad=rp2.StateMachine(0, keypad, freq=2000, set_base=Pin(2), in_base=Pin(6))
smkeypad.irq(keypad_irq)
smkeypad.active(1)