Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions examples/irremote/sender/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"machine"
"time"

"tinygo.org/x/drivers/irremote"
)

var (
pinIROut = machine.GPIO17
pwmIROut = machine.PWM0
ir irremote.SenderDevice
)

const (
irAddress = 0x00
irCmdPwr = 0x45
)

func main() {
ir = irremote.NewSender(irremote.SenderConfig{
Pin: pinIROut,
PWM: pwmIROut,
DutyCycle: 33,
})
if err := ir.Configure(); err != nil {
println(err.Error())
return
}
for {
// Send one frame, then hold the button down for half a second.
ir.SendNEC(irAddress, irCmdPwr, true)
time.Sleep(time.Millisecond * 500)
ir.StopNECRepeats()
time.Sleep(time.Second * 2)
}
}
61 changes: 61 additions & 0 deletions irremote/irprotocol/proto_nec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package irprotocol // import "tinygo.org/x/drivers/irremote/irprotocol"

import "time"

// NEC protocol reference
// https://www.sbprojects.net/knowledge/ir/nec.php

// NEC timings.
const (
NECCarrierHz = 38000

NECUnit = time.Nanosecond * 562_500
NECLeadMark = NECUnit * 16
NECLeadSpace = NECUnit * 8
NECRepeatSpace = NECUnit * 4
NECBitMark = NECUnit
NECZeroSpace = NECUnit
NECOneSpace = NECUnit * 3
NECTrailMark = NECUnit

// NECRepeatPeriod is the time from the start of one frame to the start of
// the next repeat frame while a button stays down.
NECRepeatPeriod = NECUnit * 192
)

// SplitRawNECData breaks a raw NEC code into its parts and reports whether the
// command and its inverse agree.
func SplitRawNECData(data uint32) (valid bool, address uint16, command byte) {
addrLow := byte(data)
addrHigh := byte(data >> 8)
command = byte(data >> 16)
invCmd := byte(data >> 24)
address = MakeNECAddress(addrLow, addrHigh)
valid = command == ^invCmd
return
}

// MakeRawNECData assembles a raw NEC code from an address and a command.
func MakeRawNECData(address uint16, command byte) uint32 {
addrLow, addrHigh := SplitNECAddress(address)
return uint32(^command)<<24 | uint32(command)<<16 | uint32(addrHigh)<<8 | uint32(addrLow)
}

// SplitNECAddress splits an NEC address into low and high bytes.
func SplitNECAddress(address uint16) (addrLow, addrHigh byte) {
addrLow = byte(address)
addrHigh = byte(address >> 8)
if addrHigh == 0 {
addrHigh = ^addrLow
}
return addrLow, addrHigh
}

// MakeNECAddress assembles an NEC address from low and high bytes. A high byte
// that is the inverse of the low byte is the 8 bit form, not an extended address.
func MakeNECAddress(addrLow, addrHigh byte) uint16 {
if addrHigh == ^addrLow {
return uint16(addrLow)
}
return uint16(addrHigh)<<8 | uint16(addrLow)
}
99 changes: 99 additions & 0 deletions irremote/irprotocol/proto_nec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package irprotocol

import (
"fmt"
"testing"

qt "github.com/frankban/quicktest"
)

type necTestData struct {
Code uint32
Address uint16
Command byte
}

func decodeTests(t *testing.T, tests []necTestData, expectedValid bool) {
c := qt.New(t)

for _, data := range tests {
name := fmt.Sprintf("Decode:Code:%08x Addr:%04x Cmd:%02x",
data.Code, data.Address, data.Command)
c.Run(name, func(c *qt.C) {
valid, addr, cmd := SplitRawNECData(data.Code)
c.Assert(valid, qt.Equals, expectedValid)
if valid {
c.Assert(addr, qt.Equals, data.Address)
c.Assert(cmd, qt.Equals, data.Command)
}
})
}
}

func encodeTests(t *testing.T, tests []necTestData) {
c := qt.New(t)

for _, data := range tests {
name := fmt.Sprintf("Encode:Code:%08x Addr:%04x Cmd:%02x",
data.Code, data.Address, data.Command)
c.Run(name, func(c *qt.C) {
code := MakeRawNECData(data.Address, data.Command)
c.Assert(code, qt.Equals, data.Code)
})
}
}

// TestRawNECDataNonExtendedAddr encodes and decodes codes with 8 bit addresses.
func TestRawNECDataNonExtendedAddr(t *testing.T) {
tests := []necTestData{
{Code: 0xFF00FF00, Address: 0x0000, Command: 0x00},
{Code: 0x00FFFF00, Address: 0x0000, Command: 0xFF},
{Code: 0xFF0000FF, Address: 0x00FF, Command: 0x00},
{Code: 0x00FF00FF, Address: 0x00FF, Command: 0xFF},
{Code: 0xFF00DF20, Address: 0x0020, Command: 0x00},
{Code: 0xFF0020DF, Address: 0x00DF, Command: 0x00},
{Code: 0xDF20FF00, Address: 0x0000, Command: 0x20},
{Code: 0x20DFFF00, Address: 0x0000, Command: 0xDF},
}
decodeTests(t, tests, true)
encodeTests(t, tests)
}

// TestRawNECDataExtendedAddr encodes and decodes codes with 16 bit extended
// addresses.
func TestRawNECDataExtendedAddr(t *testing.T) {
tests := []necTestData{
{Code: 0xFF000100, Address: 0x0100, Command: 0x00},
{Code: 0xFF00FE00, Address: 0xFE00, Command: 0x00},
{Code: 0xFF00F00D, Address: 0xF00D, Command: 0x00},
}
decodeTests(t, tests, true)
encodeTests(t, tests)
}

// TestSplitRawNECDataInvalidCommand checks that a command that does not match
// its inverse fails validation.
func TestSplitRawNECDataInvalidCommand(t *testing.T) {
decodeTests(t,
[]necTestData{
// One wrong bit in each position of the inverse command.
{Code: 0x01FFFF00, Address: 0x0000, Command: 0xFF},
{Code: 0x02FFFF00, Address: 0x0000, Command: 0xFF},
{Code: 0x04FFFF00, Address: 0x0000, Command: 0xFF},
{Code: 0x08FFFF00, Address: 0x0000, Command: 0xFF},
{Code: 0x10FFFF00, Address: 0x0000, Command: 0xFF},
{Code: 0x20FFFF00, Address: 0x0000, Command: 0xFF},
{Code: 0x40FFFF00, Address: 0x0000, Command: 0xFF},
{Code: 0x80FFFF00, Address: 0x0000, Command: 0xFF},
// One wrong bit in each position of the command.
{Code: 0xFF01FF00, Address: 0x0000, Command: 0xFF},
{Code: 0xFF02FF00, Address: 0x0000, Command: 0xFF},
{Code: 0xFF04FF00, Address: 0x0000, Command: 0xFF},
{Code: 0xFF08FF00, Address: 0x0000, Command: 0xFF},
{Code: 0xFF10FF00, Address: 0x0000, Command: 0xFF},
{Code: 0xFF20FF00, Address: 0x0000, Command: 0xFF},
{Code: 0xFF40FF00, Address: 0x0000, Command: 0xFF},
{Code: 0xFF80FF00, Address: 0x0000, Command: 0xFF},
},
false)
}
21 changes: 5 additions & 16 deletions irremote/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package irremote // import "tinygo.org/x/drivers/irremote"
import (
"machine"
"time"

"tinygo.org/x/drivers/irremote/irprotocol"
)

// NEC protocol references
Expand Down Expand Up @@ -200,25 +202,12 @@ const (
)

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
valid, addr, cmd := irprotocol.SplitRawNECData(ir.data.Code)
if !valid {
return irDecodeErrorInverseCheckFail
}
// cmd validation pass, decode address
ir.data.Address = addr
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
Expand Down
Loading
Loading