prettyprint

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)


沒有留言:

張貼留言