本篇文章介紹使用Raspberry Pi Pico的PIO(Programmable IO)功能來建立一個紅外線遙控器。測試使用NEC 與SAMSUNG protocol。
NEC protocol測試設備使用中華電信MOD機上盒,SAMSUNG protocol使用SAMSUNG TV測試。
使用元件:
- 4X4 matrix keypad
- IR LED 940nm
- 200歐姆電阻
- Raspberry Pi Pico
使用MicroPython語言與Thonny開發環境,作業系統為FreeBSD 13.1 + KDE Plasma。
一、紅外線遙控器協定簡介:
使用頻率約為38KHz,
- pulse: period 26.3 us, duty factor=1/3
- space: period 26.3us的low
- pulse burst: 為一連串pulse組成。
NEC Protocol:
- logical 0:一個維持562.5µs pulse burst 跟著一個維持562.5µs space, 時間長度為1.125ms
- logical 1: 一個維持562.5µs pulse burst 跟著一個維持1687.5µs space, 時間長度為2.25ms
一個按鍵按下時傳送的資料如下:
總時間長度為108ms
總時間長度為108ms
- 9ms pulse burst
- 4.5ms space
- 8-bit(八個logical 0 or logical 1組成)位址
- 8-bit反向位址(不反向亦可)
- 8-bit command
- 8-bit logical inverse of the command
- 562.5µs pulse burst結束訊息
- 剩下送出space直到108ms。
如下圖所示:
若按鍵按下可送出多的repeat code:
- 9ms pulse burst
- 2.25ms space
- 562.5us pulse space
- 剩下送出space直到108ms。
SAMSUNG protocol:
- Start bits: 4.5ms pulse burst + 4.5ms space
- 32 bits address + command(logical 0: 590us pulse+590us space, logical 1: 590us pulse burst+1690us space)
- 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)
沒有留言:
張貼留言