mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
13c43350a3
Change from reading MSB->LSB to LSB->MSB Fixes https://github.com/tinygo-org/drivers/issues/422
226 lines
7.5 KiB
Go
226 lines
7.5 KiB
Go
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 << 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 & 0x00ff0000) >> 16)
|
|
invCmd := uint8((ir.data.Code & 0xff000000) >> 24)
|
|
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 & 0xff)
|
|
addrHigh := uint8((ir.data.Code & 0xff00) >> 8)
|
|
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
|
|
}
|