mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-04 15:07:46 +00:00
Add 'irremote' package as a basic infra-red driver
Currently contains an IR Receiver device only. An IR Sender device will be added shortly Supports receiving only NEC (extended address) protocol including repeat codes so far
This commit is contained in:
@@ -231,13 +231,15 @@ endif
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/ssd1289/main.go
|
||||
@md5sum ./build/test.uf2
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/irremote/main.go
|
||||
@md5sum ./build/test.hex
|
||||
|
||||
DRIVERS = $(wildcard */)
|
||||
NOTESTS = build examples flash semihosting pcd8544 shiftregister st7789 microphone mcp3008 gps microbitmatrix \
|
||||
hcsr04 ssd1331 ws2812 thermistor apa102 easystepper ssd1351 ili9341 wifinina shifter hub75 \
|
||||
hd44780 buzzer ssd1306 espat l9110x st7735 bmi160 l293x keypad4x4 max72xx p1am tone tm1637 \
|
||||
pcf8563 mcp2515 servo sdcard rtl8720dn image cmd i2csoft hts221 lps22hb apds9960 axp192 xpt2046 \
|
||||
ft6336 sx126x ssd1289
|
||||
ft6336 sx126x ssd1289 irremote
|
||||
TESTS = $(filter-out $(addsuffix /%,$(NOTESTS)),$(DRIVERS))
|
||||
|
||||
unit-test:
|
||||
|
||||
@@ -87,6 +87,7 @@ The following 78 devices are supported.
|
||||
| [software I2C driver](https://www.ti.com/lit/an/slva704/slva704.pdf) | GPIO |
|
||||
| [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI |
|
||||
| [INA260 Volt/Amp/Power meter](https://www.ti.com/lit/ds/symlink/ina260.pdf) | I2C |
|
||||
| [Infrared remote control](https://en.wikipedia.org/wiki/Consumer_IR) | GPIO |
|
||||
| [IS31FL3731 matrix LED driver](https://www.lumissil.com/assets/pdf/core/IS31FL3731_DS.pdf) | I2C |
|
||||
| [4x4 Membrane Keypad](https://cdn.sparkfun.com/assets/f/f/a/5/0/DS-16038.pdf) | GPIO |
|
||||
| [L293x motor driver](https://www.ti.com/lit/ds/symlink/l293d.pdf) | GPIO/PWM |
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/irremote"
|
||||
)
|
||||
|
||||
var irCmdButtons = map[uint16]string{
|
||||
0xA2: "POWER",
|
||||
0xE2: "FUNC/STOP",
|
||||
0x62: "VOL+",
|
||||
0x22: "FAST BACK",
|
||||
0x02: "PAUSE",
|
||||
0xC2: "FAST FORWARD",
|
||||
0xE0: "DOWN",
|
||||
0xA8: "VOL-",
|
||||
0x90: "UP",
|
||||
0x98: "EQ",
|
||||
0xB0: "ST/REPT",
|
||||
0x68: "0",
|
||||
0x30: "1",
|
||||
0x18: "2",
|
||||
0x7A: "3",
|
||||
0x10: "4",
|
||||
0x38: "5",
|
||||
0x5A: "6",
|
||||
0x42: "7",
|
||||
0x4A: "8",
|
||||
0x52: "9",
|
||||
}
|
||||
|
||||
var (
|
||||
pinIRIn = machine.GP26
|
||||
ir irremote.ReceiverDevice
|
||||
)
|
||||
|
||||
func setupPins() {
|
||||
ir = irremote.NewReceiver(pinIRIn)
|
||||
ir.Configure()
|
||||
}
|
||||
|
||||
func irCallback(data irremote.Data) {
|
||||
msg := "Command: " + irCmdButtons[data.Command]
|
||||
if data.Flags&irremote.DataFlagIsRepeat != 0 {
|
||||
msg = msg + " (REPEAT)"
|
||||
}
|
||||
println(msg)
|
||||
}
|
||||
|
||||
func main() {
|
||||
setupPins()
|
||||
ir.SetCommandHandler(irCallback)
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package irremote // import "tinygo.org/x/drivers/irremote"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NEC protocol references
|
||||
// https://www.sbprojects.net/knowledge/ir/nec.php
|
||||
// https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol
|
||||
// https://simple-circuit.com/arduino-nec-remote-control-decoder/
|
||||
|
||||
// Data encapsulates the data received by the ReceiverDevice.
|
||||
type Data struct {
|
||||
// Code is the raw IR data received.
|
||||
Code uint32
|
||||
// Address is the decoded address from the IR data received.
|
||||
Address uint16
|
||||
// Command is the decoded command from the IR data recieved
|
||||
Command uint16
|
||||
// Flags provides additional information about the IR data received. See DataFlags
|
||||
Flags DataFlags
|
||||
}
|
||||
|
||||
// DataFlags provides bitwise flags representing various information about recieved IR data.
|
||||
type DataFlags uint16
|
||||
|
||||
// Valid values for DataFlags
|
||||
const (
|
||||
// DataFlagIsRepeat set indicates that the IR data is a repeat commmand
|
||||
DataFlagIsRepeat DataFlags = 1 << iota
|
||||
)
|
||||
|
||||
// CommandHandler defines the callback function used to provide IR data received by the ReceiverDevice.
|
||||
type CommandHandler func(data Data)
|
||||
|
||||
// nec_ir_state represents the various internal states used to decode the NEC IR protocol commands
|
||||
type nec_ir_state uint8
|
||||
|
||||
// Valid values for nec_ir_state
|
||||
const (
|
||||
lead_pulse_start nec_ir_state = iota // Start receiving IR data, beginning of 9ms lead pulse
|
||||
lead_space_start // End of 9ms lead pulse, start of 4.5ms space
|
||||
lead_space_end // End of 4.5ms space, start of 562µs pulse
|
||||
bit_read_start // End of 562µs pulse, start of 562µs or 1687µs space
|
||||
bit_read_end // End of 562µs or 1687µs space
|
||||
trail_pulse_end // End of 562µs trailing pulse
|
||||
)
|
||||
|
||||
// ReceiverDevice is the device for receiving IR commands
|
||||
type ReceiverDevice struct {
|
||||
pin machine.Pin // IR input pin.
|
||||
ch CommandHandler // client callback function
|
||||
necState nec_ir_state // internal state machine
|
||||
data Data // decoded data for client
|
||||
lastTime time.Time // used to track states
|
||||
bitIndex int // tracks which bit (0-31) of necCode is being read
|
||||
}
|
||||
|
||||
// NewReceiver returns a new IR receiver device
|
||||
func NewReceiver(pin machine.Pin) ReceiverDevice {
|
||||
return ReceiverDevice{pin: pin}
|
||||
}
|
||||
|
||||
// Configure configures the input pin for the IR receiver device
|
||||
func (ir *ReceiverDevice) Configure() {
|
||||
// The IR receiver sends logic HIGH when NOT receiving IR, and logic LOW when receiving IR
|
||||
ir.pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
}
|
||||
|
||||
// SetCommandHandler is used to start or stop receiving IR commands via a callback function (pass nil to stop)
|
||||
func (ir *ReceiverDevice) SetCommandHandler(ch CommandHandler) {
|
||||
ir.ch = ch
|
||||
ir.resetStateMachine()
|
||||
if ch != nil {
|
||||
// Start monitoring IR output pin for changes
|
||||
ir.pin.SetInterrupt(machine.PinFalling|machine.PinRising, ir.pinChange)
|
||||
} else {
|
||||
// Stop monitoring IR output pin for changes
|
||||
ir.pin.SetInterrupt(0, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Internal helper function to reset state machine on protocol failure
|
||||
func (ir *ReceiverDevice) resetStateMachine() {
|
||||
ir.data = Data{}
|
||||
ir.bitIndex = 0
|
||||
ir.necState = lead_pulse_start
|
||||
}
|
||||
|
||||
// Internal pin rising/falling edge interrupt handler
|
||||
func (ir *ReceiverDevice) pinChange(pin machine.Pin) {
|
||||
/* Currently TinyGo is sending machine.NoPin (0xff) for all pins, at least on RP2040
|
||||
if pin != ir.pin {
|
||||
return // This is not the pin you're looking for
|
||||
}
|
||||
*/
|
||||
now := time.Now()
|
||||
duration := now.Sub(ir.lastTime)
|
||||
ir.lastTime = now
|
||||
switch ir.necState {
|
||||
case lead_pulse_start:
|
||||
if !ir.pin.Get() {
|
||||
// IR is 'on' (pin is pulled high and sent low when IR is received)
|
||||
ir.necState = lead_space_start // move to next state
|
||||
}
|
||||
case lead_space_start:
|
||||
if duration > time.Microsecond*9500 || duration < time.Microsecond*8500 {
|
||||
// Invalid interval for 9ms lead pulse. Reset
|
||||
ir.resetStateMachine()
|
||||
} else {
|
||||
// 9ms lead pulse detected, move to next state
|
||||
ir.necState = lead_space_end
|
||||
}
|
||||
case lead_space_end:
|
||||
if duration > time.Microsecond*5000 || duration < time.Microsecond*1750 {
|
||||
// Invalid interval for 4.5ms lead space OR 2.25ms repeat space. Reset
|
||||
ir.resetStateMachine()
|
||||
} else {
|
||||
// 4.5ms lead space OR 2.25ms repeat space detected
|
||||
if duration > time.Microsecond*3000 {
|
||||
// 4.5ms lead space detected, new code incoming, move to next state
|
||||
ir.resetStateMachine()
|
||||
ir.necState = bit_read_start
|
||||
} else {
|
||||
// 2.25ms repeat space detected.
|
||||
if ir.data.Code != 0 {
|
||||
// Valid repeat code. Invoke client callback with repeat flag set
|
||||
ir.data.Flags |= DataFlagIsRepeat
|
||||
if ir.ch != nil {
|
||||
ir.ch(ir.data)
|
||||
}
|
||||
ir.necState = lead_pulse_start
|
||||
} else {
|
||||
// ir.data is not in a valid state for a repeat. Reset
|
||||
ir.resetStateMachine()
|
||||
}
|
||||
}
|
||||
}
|
||||
case bit_read_start:
|
||||
if duration > time.Microsecond*700 || duration < time.Microsecond*400 {
|
||||
// Invalid interval for 562.5µs pulse. Reset
|
||||
ir.resetStateMachine()
|
||||
} else {
|
||||
// 562.5µs pulse detected, move to next state
|
||||
ir.necState = bit_read_end
|
||||
}
|
||||
case bit_read_end:
|
||||
if duration > time.Microsecond*1800 || duration < time.Microsecond*400 {
|
||||
// Invalid interval for 562.5µs space OR 1687.5µs space. Reset
|
||||
ir.resetStateMachine()
|
||||
} else {
|
||||
// 562.5µs OR 1687.5µs space detected
|
||||
mask := uint32((1 << (31 - ir.bitIndex)))
|
||||
if duration > time.Microsecond*1000 {
|
||||
// 1687.5µs space detected (logic 1) - Set bit
|
||||
ir.data.Code |= mask
|
||||
} else {
|
||||
// 562.5µs space detected (logic 0) - Clear bit
|
||||
ir.data.Code &^= mask
|
||||
}
|
||||
|
||||
ir.bitIndex++
|
||||
if ir.bitIndex > 31 {
|
||||
// We've read all bits for this code, move to next state
|
||||
ir.necState = trail_pulse_end
|
||||
} else {
|
||||
// Read next bit
|
||||
ir.necState = bit_read_start
|
||||
}
|
||||
}
|
||||
case trail_pulse_end:
|
||||
if duration > time.Microsecond*700 || duration < time.Microsecond*400 {
|
||||
// Invalid interval for trailing 562.5µs pulse. Reset
|
||||
ir.resetStateMachine()
|
||||
} else {
|
||||
// 562.5µs trailing pulse detected. Decode & validate data
|
||||
err := ir.decode()
|
||||
if err == irDecodeErrorNone {
|
||||
// Valid data, invoke client callback
|
||||
if ir.ch != nil {
|
||||
ir.ch(ir.data)
|
||||
}
|
||||
// around we go again. Note: we don't resetStateMachine() since repeat codes are now possible
|
||||
ir.necState = lead_pulse_start
|
||||
} else {
|
||||
ir.resetStateMachine()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error type for NEC format decoding
|
||||
type irDecodeError int
|
||||
|
||||
// Valid values for irDecodeError
|
||||
const (
|
||||
irDecodeErrorNone irDecodeError = iota // no error occurred
|
||||
irDecodeErrorInverseCheckFail // validation of inverse cmd does not match cmd
|
||||
)
|
||||
|
||||
func (ir *ReceiverDevice) decode() irDecodeError {
|
||||
// Decode cmd and inverse cmd and perform validation check
|
||||
cmd := uint8((ir.data.Code & 0xff00) >> 8)
|
||||
invCmd := uint8(ir.data.Code & 0xff)
|
||||
if cmd != ^invCmd {
|
||||
// Validation failure. cmd and inverse cmd do not match
|
||||
return irDecodeErrorInverseCheckFail
|
||||
}
|
||||
// cmd validation pass, decode address
|
||||
ir.data.Command = uint16(cmd)
|
||||
addrLow := uint8((ir.data.Code & 0xff000000) >> 24)
|
||||
addrHigh := uint8((ir.data.Code & 0x00ff0000) >> 16)
|
||||
if addrHigh == ^addrLow {
|
||||
// addrHigh is inverse of addrLow. This is not a valid 16-bit address in extended NEC coding
|
||||
// since it is indistinguishable from 8-bit address with inverse validation. Use the 8-bit address
|
||||
ir.data.Address = uint16(addrLow)
|
||||
} else {
|
||||
// 16-bit extended NEC address
|
||||
ir.data.Address = (uint16(addrHigh) << 8) | uint16(addrLow)
|
||||
}
|
||||
// Clear repeat flag
|
||||
ir.data.Flags &^= DataFlagIsRepeat
|
||||
return irDecodeErrorNone
|
||||
}
|
||||
Reference in New Issue
Block a user