本篇文章介紹使用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)
沒有留言:
張貼留言