tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/irremote/receiver.go (about) 1 package irremote // import "tinygo.org/x/drivers/irremote" 2 3 import ( 4 "machine" 5 "time" 6 ) 7 8 // NEC protocol references 9 // https://www.sbprojects.net/knowledge/ir/nec.php 10 // https://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol 11 // https://simple-circuit.com/arduino-nec-remote-control-decoder/ 12 13 // Data encapsulates the data received by the ReceiverDevice. 14 type Data struct { 15 // Code is the raw IR data received. 16 Code uint32 17 // Address is the decoded address from the IR data received. 18 Address uint16 19 // Command is the decoded command from the IR data recieved 20 Command uint16 21 // Flags provides additional information about the IR data received. See DataFlags 22 Flags DataFlags 23 } 24 25 // DataFlags provides bitwise flags representing various information about recieved IR data. 26 type DataFlags uint16 27 28 // Valid values for DataFlags 29 const ( 30 // DataFlagIsRepeat set indicates that the IR data is a repeat commmand 31 DataFlagIsRepeat DataFlags = 1 << iota 32 ) 33 34 // CommandHandler defines the callback function used to provide IR data received by the ReceiverDevice. 35 type CommandHandler func(data Data) 36 37 // nec_ir_state represents the various internal states used to decode the NEC IR protocol commands 38 type nec_ir_state uint8 39 40 // Valid values for nec_ir_state 41 const ( 42 lead_pulse_start nec_ir_state = iota // Start receiving IR data, beginning of 9ms lead pulse 43 lead_space_start // End of 9ms lead pulse, start of 4.5ms space 44 lead_space_end // End of 4.5ms space, start of 562µs pulse 45 bit_read_start // End of 562µs pulse, start of 562µs or 1687µs space 46 bit_read_end // End of 562µs or 1687µs space 47 trail_pulse_end // End of 562µs trailing pulse 48 ) 49 50 // ReceiverDevice is the device for receiving IR commands 51 type ReceiverDevice struct { 52 pin machine.Pin // IR input pin. 53 ch CommandHandler // client callback function 54 necState nec_ir_state // internal state machine 55 data Data // decoded data for client 56 lastTime time.Time // used to track states 57 bitIndex int // tracks which bit (0-31) of necCode is being read 58 } 59 60 // NewReceiver returns a new IR receiver device 61 func NewReceiver(pin machine.Pin) ReceiverDevice { 62 return ReceiverDevice{pin: pin} 63 } 64 65 // Configure configures the input pin for the IR receiver device 66 func (ir *ReceiverDevice) Configure() { 67 // The IR receiver sends logic HIGH when NOT receiving IR, and logic LOW when receiving IR 68 ir.pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) 69 } 70 71 // SetCommandHandler is used to start or stop receiving IR commands via a callback function (pass nil to stop) 72 func (ir *ReceiverDevice) SetCommandHandler(ch CommandHandler) { 73 ir.ch = ch 74 ir.resetStateMachine() 75 if ch != nil { 76 // Start monitoring IR output pin for changes 77 ir.pin.SetInterrupt(machine.PinFalling|machine.PinRising, ir.pinChange) 78 } else { 79 // Stop monitoring IR output pin for changes 80 ir.pin.SetInterrupt(0, nil) 81 } 82 } 83 84 // Internal helper function to reset state machine on protocol failure 85 func (ir *ReceiverDevice) resetStateMachine() { 86 ir.data = Data{} 87 ir.bitIndex = 0 88 ir.necState = lead_pulse_start 89 } 90 91 // Internal pin rising/falling edge interrupt handler 92 func (ir *ReceiverDevice) pinChange(pin machine.Pin) { 93 /* Currently TinyGo is sending machine.NoPin (0xff) for all pins, at least on RP2040 94 if pin != ir.pin { 95 return // This is not the pin you're looking for 96 } 97 */ 98 now := time.Now() 99 duration := now.Sub(ir.lastTime) 100 ir.lastTime = now 101 switch ir.necState { 102 case lead_pulse_start: 103 if !ir.pin.Get() { 104 // IR is 'on' (pin is pulled high and sent low when IR is received) 105 ir.necState = lead_space_start // move to next state 106 } 107 case lead_space_start: 108 if duration > time.Microsecond*9500 || duration < time.Microsecond*8500 { 109 // Invalid interval for 9ms lead pulse. Reset 110 ir.resetStateMachine() 111 } else { 112 // 9ms lead pulse detected, move to next state 113 ir.necState = lead_space_end 114 } 115 case lead_space_end: 116 if duration > time.Microsecond*5000 || duration < time.Microsecond*1750 { 117 // Invalid interval for 4.5ms lead space OR 2.25ms repeat space. Reset 118 ir.resetStateMachine() 119 } else { 120 // 4.5ms lead space OR 2.25ms repeat space detected 121 if duration > time.Microsecond*3000 { 122 // 4.5ms lead space detected, new code incoming, move to next state 123 ir.resetStateMachine() 124 ir.necState = bit_read_start 125 } else { 126 // 2.25ms repeat space detected. 127 if ir.data.Code != 0 { 128 // Valid repeat code. Invoke client callback with repeat flag set 129 ir.data.Flags |= DataFlagIsRepeat 130 if ir.ch != nil { 131 ir.ch(ir.data) 132 } 133 ir.necState = lead_pulse_start 134 } else { 135 // ir.data is not in a valid state for a repeat. Reset 136 ir.resetStateMachine() 137 } 138 } 139 } 140 case bit_read_start: 141 if duration > time.Microsecond*700 || duration < time.Microsecond*400 { 142 // Invalid interval for 562.5µs pulse. Reset 143 ir.resetStateMachine() 144 } else { 145 // 562.5µs pulse detected, move to next state 146 ir.necState = bit_read_end 147 } 148 case bit_read_end: 149 if duration > time.Microsecond*1800 || duration < time.Microsecond*400 { 150 // Invalid interval for 562.5µs space OR 1687.5µs space. Reset 151 ir.resetStateMachine() 152 } else { 153 // 562.5µs OR 1687.5µs space detected 154 mask := uint32(1 << ir.bitIndex) 155 if duration > time.Microsecond*1000 { 156 // 1687.5µs space detected (logic 1) - Set bit 157 ir.data.Code |= mask 158 } else { 159 // 562.5µs space detected (logic 0) - Clear bit 160 ir.data.Code &^= mask 161 } 162 163 ir.bitIndex++ 164 if ir.bitIndex > 31 { 165 // We've read all bits for this code, move to next state 166 ir.necState = trail_pulse_end 167 } else { 168 // Read next bit 169 ir.necState = bit_read_start 170 } 171 } 172 case trail_pulse_end: 173 if duration > time.Microsecond*700 || duration < time.Microsecond*400 { 174 // Invalid interval for trailing 562.5µs pulse. Reset 175 ir.resetStateMachine() 176 } else { 177 // 562.5µs trailing pulse detected. Decode & validate data 178 err := ir.decode() 179 if err == irDecodeErrorNone { 180 // Valid data, invoke client callback 181 if ir.ch != nil { 182 ir.ch(ir.data) 183 } 184 // around we go again. Note: we don't resetStateMachine() since repeat codes are now possible 185 ir.necState = lead_pulse_start 186 } else { 187 ir.resetStateMachine() 188 } 189 } 190 } 191 } 192 193 // Error type for NEC format decoding 194 type irDecodeError int 195 196 // Valid values for irDecodeError 197 const ( 198 irDecodeErrorNone irDecodeError = iota // no error occurred 199 irDecodeErrorInverseCheckFail // validation of inverse cmd does not match cmd 200 ) 201 202 func (ir *ReceiverDevice) decode() irDecodeError { 203 // Decode cmd and inverse cmd and perform validation check 204 cmd := uint8((ir.data.Code & 0x00ff0000) >> 16) 205 invCmd := uint8((ir.data.Code & 0xff000000) >> 24) 206 if cmd != ^invCmd { 207 // Validation failure. cmd and inverse cmd do not match 208 return irDecodeErrorInverseCheckFail 209 } 210 // cmd validation pass, decode address 211 ir.data.Command = uint16(cmd) 212 addrLow := uint8(ir.data.Code & 0xff) 213 addrHigh := uint8((ir.data.Code & 0xff00) >> 8) 214 if addrHigh == ^addrLow { 215 // addrHigh is inverse of addrLow. This is not a valid 16-bit address in extended NEC coding 216 // since it is indistinguishable from 8-bit address with inverse validation. Use the 8-bit address 217 ir.data.Address = uint16(addrLow) 218 } else { 219 // 16-bit extended NEC address 220 ir.data.Address = (uint16(addrHigh) << 8) | uint16(addrLow) 221 } 222 // Clear repeat flag 223 ir.data.Flags &^= DataFlagIsRepeat 224 return irDecodeErrorNone 225 }