mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc6edbb694 | |||
| 1d695a231a | |||
| 04acd8e666 | |||
| bb2d365868 | |||
| f459992f3c | |||
| 62663c1832 |
@@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/sx128x"
|
||||
)
|
||||
|
||||
var (
|
||||
// pin mapping specific to the lilygo t3s3, change as needed for your board
|
||||
sdoPin = machine.GPIO6
|
||||
sdiPin = machine.GPIO3
|
||||
sckPin = machine.GPIO5
|
||||
nssPin = machine.GPIO7
|
||||
busyPin = machine.GPIO36
|
||||
resetPin = machine.GPIO8
|
||||
dio1Pin = machine.GPIO9
|
||||
)
|
||||
|
||||
func setupPins() {
|
||||
nssPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
nssPin.Set(true)
|
||||
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Set(true)
|
||||
|
||||
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
}
|
||||
|
||||
func main() {
|
||||
setupPins()
|
||||
|
||||
spi := machine.SPI0
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Mode: 0,
|
||||
Frequency: 8 * 1e6,
|
||||
SDO: sdoPin,
|
||||
SDI: sdiPin,
|
||||
SCK: sckPin,
|
||||
})
|
||||
|
||||
radio := sx128x.New(
|
||||
spi,
|
||||
nssPin,
|
||||
resetPin,
|
||||
busyPin,
|
||||
)
|
||||
|
||||
radio.WaitWhileBusy(time.Second)
|
||||
SetupLora(radio)
|
||||
|
||||
for {
|
||||
data, err := Rx(radio)
|
||||
if err != nil {
|
||||
println("failed to receive:", err)
|
||||
} else {
|
||||
println("received:", string(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SetupLora(radio *sx128x.Device) {
|
||||
radio.SetStandby(sx128x.STANDBY_RC)
|
||||
radio.SetPacketType(sx128x.PACKET_TYPE_LORA)
|
||||
radio.SetRegulatorMode(sx128x.REGULATOR_DC_DC)
|
||||
|
||||
radio.SetRfFrequency(2400000000) // 2.4Ghz
|
||||
radio.SetModulationParamsLoRa(sx128x.LORA_SF_9, sx128x.LORA_BW_1600, sx128x.LORA_CR_4_7)
|
||||
|
||||
// section 14.4.1 shows required register setting for setting up LoRa operations. These depend on the chosen spreading factor.
|
||||
radio.WriteRegister(0x925, []byte{0x32})
|
||||
radio.WriteRegister(0x93C, []byte{0x01})
|
||||
|
||||
radio.SetTxParams(13, sx128x.RADIO_RAMP_02_US)
|
||||
radio.SetPacketParamsLoRa(12, sx128x.LORA_HEADER_EXPLICIT, 0xFF, sx128x.LORA_CRC_DISABLE, sx128x.LORA_IQ_STD)
|
||||
radio.WriteRegister(sx128x.REG_LORA_SYNC_WORD_MSB, []byte{0x14, 0x24}) // full sync word is 0x1424
|
||||
|
||||
}
|
||||
|
||||
func Rx(radio *sx128x.Device) ([]byte, error) {
|
||||
radio.SetStandby(sx128x.STANDBY_RC)
|
||||
radio.SetDioIrqParams(sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_RX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00)
|
||||
radio.SetBufferBaseAddress(0, 0)
|
||||
radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK)
|
||||
radio.SetRx(sx128x.PERIOD_BASE_4_MS, 250) // 4ms * 250 = 1s
|
||||
// busy wait for IRQ indication
|
||||
for dio1Pin.Get() == false {
|
||||
runtime.Gosched()
|
||||
}
|
||||
irqStatus, _ := radio.GetIrqStatus()
|
||||
if irqStatus&sx128x.IRQ_RX_DONE_MASK != 0 {
|
||||
payloadLength, bufferOffset, err := radio.GetRxBufferStatus()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := radio.ReadBuffer(bufferOffset, payloadLength)
|
||||
return data, nil
|
||||
} else if irqStatus&sx128x.IRQ_RX_TX_TIMEOUT_MASK != 0 {
|
||||
return nil, errors.New("rx timeout")
|
||||
}
|
||||
return nil, errors.New("unexpected IRQ status")
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/sx128x"
|
||||
)
|
||||
|
||||
var (
|
||||
// pin mapping specific to the lilygo t3s3, change as needed for your board
|
||||
sdoPin = machine.GPIO6
|
||||
sdiPin = machine.GPIO3
|
||||
sckPin = machine.GPIO5
|
||||
nssPin = machine.GPIO7
|
||||
busyPin = machine.GPIO36
|
||||
resetPin = machine.GPIO8
|
||||
dio1Pin = machine.GPIO9
|
||||
)
|
||||
|
||||
func setupPins() {
|
||||
nssPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
nssPin.Set(true)
|
||||
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Set(true)
|
||||
|
||||
busyPin.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
}
|
||||
|
||||
func main() {
|
||||
setupPins()
|
||||
|
||||
spi := machine.SPI0
|
||||
spi.Configure(machine.SPIConfig{
|
||||
Mode: 0,
|
||||
Frequency: 8 * 1e6,
|
||||
SDO: sdoPin,
|
||||
SDI: sdiPin,
|
||||
SCK: sckPin,
|
||||
})
|
||||
|
||||
radio := sx128x.New(
|
||||
spi,
|
||||
nssPin,
|
||||
resetPin,
|
||||
busyPin,
|
||||
)
|
||||
|
||||
radio.WaitWhileBusy(time.Second)
|
||||
SetupLora(radio)
|
||||
|
||||
for {
|
||||
Tx(radio, []byte("Hello, world!"))
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func SetupLora(radio *sx128x.Device) {
|
||||
radio.SetStandby(sx128x.STANDBY_RC)
|
||||
radio.SetPacketType(sx128x.PACKET_TYPE_LORA)
|
||||
radio.SetRegulatorMode(sx128x.REGULATOR_DC_DC)
|
||||
|
||||
radio.SetRfFrequency(2400000000) // 2.4Ghz
|
||||
radio.SetModulationParamsLoRa(sx128x.LORA_SF_9, sx128x.LORA_BW_1600, sx128x.LORA_CR_4_7)
|
||||
|
||||
// section 14.4.1 shows required register setting for setting up LoRa operations. These depend on the chosen spreading factor.
|
||||
radio.WriteRegister(0x925, []byte{0x32})
|
||||
radio.WriteRegister(0x93C, []byte{0x01})
|
||||
|
||||
radio.SetTxParams(13, sx128x.RADIO_RAMP_02_US)
|
||||
radio.SetPacketParamsLoRa(12, sx128x.LORA_HEADER_EXPLICIT, 0xFF, sx128x.LORA_CRC_DISABLE, sx128x.LORA_IQ_STD)
|
||||
radio.WriteRegister(sx128x.REG_LORA_SYNC_WORD_MSB, []byte{0x14, 0x24}) // full sync word is 0x1424
|
||||
|
||||
}
|
||||
|
||||
func Tx(radio *sx128x.Device, data []byte) error {
|
||||
if len(data) > 255 {
|
||||
return errors.New("data length exceeds maximum of 255 bytes")
|
||||
}
|
||||
radio.SetStandby(sx128x.STANDBY_RC)
|
||||
radio.SetPacketParamsLoRa(12, sx128x.LORA_HEADER_EXPLICIT, uint8(len(data)&0xFF), sx128x.LORA_CRC_DISABLE, sx128x.LORA_IQ_STD)
|
||||
radio.SetBufferBaseAddress(0, 0)
|
||||
radio.WriteBuffer(0, data)
|
||||
radio.SetDioIrqParams(sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, sx128x.IRQ_TX_DONE_MASK|sx128x.IRQ_RX_TX_TIMEOUT_MASK, 0x00, 0x00)
|
||||
radio.ClearIrqStatus(sx128x.IRQ_ALL_MASK)
|
||||
radio.SetTx(sx128x.PERIOD_BASE_4_MS, 250) // 4ms * 250 = 1s
|
||||
// busy wait for IRQ indication
|
||||
for dio1Pin.Get() == false {
|
||||
runtime.Gosched()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !digispark && !arduino && !arduino_uno
|
||||
//go:build !digispark && !arduino && !arduino_uno && !xiao_esp32c3
|
||||
|
||||
package main
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build xiao_esp32c3
|
||||
|
||||
package main
|
||||
|
||||
import "machine"
|
||||
|
||||
func init() {
|
||||
// Replace neo in the code below to match the pin
|
||||
// that you are using if different.
|
||||
neo = machine.D6
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package region
|
||||
import "tinygo.org/x/drivers/lora"
|
||||
|
||||
const (
|
||||
EU868_DEFAULT_PREAMBLE_LEN = 8
|
||||
EU868_DEFAULT_TX_POWER_DBM = 20
|
||||
EU868_DEFAULT_PREAMBLE_LEN = 8 // page 103 RP002-1.0.5
|
||||
EU868_DEFAULT_TX_POWER_DBM = 16 // page 36 RP002-1.0.5, 16 is the max
|
||||
)
|
||||
|
||||
type ChannelEU struct {
|
||||
|
||||
+12
-9
@@ -1,5 +1,4 @@
|
||||
// L2 data link layer
|
||||
|
||||
// package netlink provides an interface for L2 data link layer operations.
|
||||
package netlink
|
||||
|
||||
import (
|
||||
@@ -20,6 +19,7 @@ var (
|
||||
ErrNotSupported = errors.New("Not supported")
|
||||
)
|
||||
|
||||
// Event is a network event type passed to the callback registered with NetNotify.
|
||||
type Event int
|
||||
|
||||
// Network events
|
||||
@@ -38,6 +38,7 @@ const (
|
||||
ConnectModeAP // Connect as Wifi Access Point
|
||||
)
|
||||
|
||||
// AuthType is the type of WiFi authorization to use when connecting to an access point.
|
||||
type AuthType int
|
||||
|
||||
// Wifi authorization types. Used when setting up an access point, or
|
||||
@@ -49,10 +50,11 @@ const (
|
||||
AuthTypeWPA2Mixed // WPA2/WPA mixed authorization
|
||||
)
|
||||
|
||||
// DefaultConnectTimeout is the default timeout for connection attempts. This is used when ConnectParams.ConnectTimeout is zero.
|
||||
const DefaultConnectTimeout = 10 * time.Second
|
||||
|
||||
// ConnectParams is the set of parameters used to connect a Netlinker device to a network.
|
||||
type ConnectParams struct {
|
||||
|
||||
// Connect mode
|
||||
ConnectMode
|
||||
|
||||
@@ -81,22 +83,23 @@ type ConnectParams struct {
|
||||
// downed connection or hardware fault and try to recover the
|
||||
// connection. Set to zero to disable watchodog.
|
||||
WatchdogTimeout time.Duration
|
||||
|
||||
// Hostname to use for this device.
|
||||
Hostname string
|
||||
}
|
||||
|
||||
// Netlinker is TinyGo's OSI L2 data link layer interface. Network device
|
||||
// drivers implement Netlinker to expose the device's L2 functionality.
|
||||
|
||||
type Netlinker interface {
|
||||
|
||||
// Connect device to network
|
||||
// NetConnect connects the device to a network
|
||||
NetConnect(params *ConnectParams) error
|
||||
|
||||
// Disconnect device from network
|
||||
// NetDisconnect disconnects the device from the network
|
||||
NetDisconnect()
|
||||
|
||||
// Notify to register callback for network events
|
||||
// NetNotify registers a callback for network events
|
||||
NetNotify(cb func(Event))
|
||||
|
||||
// GetHardwareAddr returns device MAC address
|
||||
// GetHardwareAddr returns the device's MAC address
|
||||
GetHardwareAddr() (net.HardwareAddr, error)
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ tinygo build -size short -o ./build/test.bin -target=m5stamp-c3 ./examp
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
|
||||
tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
|
||||
tinygo build -size short -o ./build/test.bin -target=xiao-esp32c3 ./examples/ws2812
|
||||
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bme280/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/microphone/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/buzzer/main.go
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# SX128x Radio
|
||||
Radio from Semtech in the 2.4 GHz band. This driver uses SPI to communicate with the radio instead of the alternative UART interface.
|
||||
|
||||
## Supported Chips
|
||||
- [SX1280](https://www.semtech.com/products/wireless-rf/lora-connect/sx1280)
|
||||
- [SX1281](https://www.semtech.com/products/wireless-rf/lora-connect/sx1281)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package sx128x
|
||||
|
||||
const (
|
||||
// SX128X SPI commands
|
||||
cmdGetStatus = uint8(0xC0)
|
||||
|
||||
// Register Access Operations
|
||||
cmdWriteRegister = uint8(0x18)
|
||||
cmdReadRegister = uint8(0x19)
|
||||
|
||||
// Data Buffer Operations
|
||||
cmdWriteBuffer = uint8(0x1A)
|
||||
cmdReadBuffer = uint8(0x1B)
|
||||
|
||||
// Radio Operation Modes
|
||||
cmdSetSleep = uint8(0x84)
|
||||
cmdSetStandby = uint8(0x80)
|
||||
cmdSetFS = uint8(0xC1)
|
||||
cmdSetTx = uint8(0x83)
|
||||
cmdSetRx = uint8(0x82)
|
||||
cmdSetRxDutyCycle = uint8(0x94)
|
||||
cmdSetLongPreamble = uint8(0x9B)
|
||||
cmdSetCAD = uint8(0xC5)
|
||||
cmdSetTxContinuousWave = uint8(0xD1)
|
||||
cmdSetContinuousPreamble = uint8(0xD2)
|
||||
cmdSetAutoTx = uint8(0x98)
|
||||
cmdSetAutoFS = uint8(0x9E)
|
||||
|
||||
// Radio Configuration
|
||||
cmdSetPacketType = uint8(0x8A)
|
||||
cmdGetPacketType = uint8(0x03)
|
||||
cmdSetRFFrequency = uint8(0x86)
|
||||
cmdSetTxParams = uint8(0x8E)
|
||||
cmdSetCADParams = uint8(0x88)
|
||||
cmdSetBufferBaseAddress = uint8(0x8F)
|
||||
cmdSetModulationParams = uint8(0x8B)
|
||||
cmdSetPacketParams = uint8(0x8C)
|
||||
|
||||
// Communication Status Information
|
||||
cmdGetRxBufferStatus = uint8(0x17)
|
||||
cmdGetPacketStatus = uint8(0x1D)
|
||||
cmdGetRSSIInst = uint8(0x1F)
|
||||
|
||||
// IRQ Handling
|
||||
cmdSetDIOIRQParams = uint8(0x8D)
|
||||
cmdGetIRQStatus = uint8(0x15)
|
||||
cmdClearIRQStatus = uint8(0x97)
|
||||
|
||||
// Miscellaneous
|
||||
cmdSetRegulatorMode = uint8(0x96)
|
||||
cmdSetSaveContext = uint8(0xD5)
|
||||
)
|
||||
@@ -0,0 +1,356 @@
|
||||
package sx128x
|
||||
|
||||
type SleepConfig uint8
|
||||
type StandbyConfig uint8
|
||||
type PeriodBase uint8
|
||||
type PacketType uint8
|
||||
type RadioRampTime uint8
|
||||
type CadSymbolNum uint8
|
||||
|
||||
// GFSK Modulation Params
|
||||
type GFSKBLEBitrateBandwidth uint8
|
||||
type ModulationIndex uint8
|
||||
type ModulationShaping uint8
|
||||
|
||||
// GFSK Packet Params
|
||||
type GFSKPreambleLength uint8
|
||||
type GFSKSyncWordLength uint8
|
||||
type GFSKSyncWordMatch uint8
|
||||
type GFSKHeaderType uint8
|
||||
type GFSKCrcType uint8
|
||||
|
||||
// BLE Packet Params
|
||||
type BLEConnectionState uint8
|
||||
type BLECrcType uint8
|
||||
type BLETestPayload uint8
|
||||
|
||||
// FLRC Modulation Params
|
||||
type FLRCBitrateBandwidth uint8
|
||||
type FLRCCodingRate uint8
|
||||
|
||||
// FLRC Packet Params
|
||||
type FLRCPreambleLength uint8
|
||||
type FLRCSyncWordLength uint8
|
||||
type FLRCSyncWordMatch uint8
|
||||
type FLRCHeaderType uint8
|
||||
type FLRCCrcType uint8
|
||||
|
||||
// LoRa Modulation Params
|
||||
type LoRaSpreadingFactor uint8
|
||||
type LoRaBandwidth uint8
|
||||
type LoRaCodingRate uint8
|
||||
|
||||
// LoRa Packet Params
|
||||
type LoRaHeaderType uint8
|
||||
type LoRaCrcType uint8
|
||||
type LoRaIqType uint8
|
||||
|
||||
// Misc
|
||||
type RegulatorMode uint8
|
||||
type IRQMask = uint16
|
||||
type CircuitMode uint8
|
||||
type CommandStatus uint8
|
||||
|
||||
// Packet Status
|
||||
type GFSKPacketInfo uint8
|
||||
type BLEPacketInfo uint8
|
||||
type FLRCPacketInfo uint8
|
||||
|
||||
const (
|
||||
whiteningDisable = 0x00
|
||||
whiteningEnable = 0x08
|
||||
|
||||
// Circuit Mode
|
||||
circuitModeMask = uint8(0b11100000)
|
||||
CIRCUIT_MODE_STDBY_RC = CircuitMode(0x2)
|
||||
CIRCUIT_MODE_STDBY_XOSC = CircuitMode(0x3)
|
||||
CIRCUIT_MODE_FS = CircuitMode(0x4)
|
||||
CIRCUIT_MODE_RX = CircuitMode(0x5)
|
||||
CIRCUIT_MODE_TX = CircuitMode(0x6)
|
||||
|
||||
// Command Status
|
||||
commandStatusMask = uint8(0b00011100)
|
||||
COMMAND_STATUS_SUCCESS = CommandStatus(0x1)
|
||||
COMMAND_STATUS_DATA_AVAILABLE = CommandStatus(0x2)
|
||||
COMMAND_STATUS_TIMEOUT = CommandStatus(0x3)
|
||||
COMMAND_STATUS_PROCESSING_ERROR = CommandStatus(0x4)
|
||||
COMMAND_STATUS_EXECUTION_ERROR = CommandStatus(0x5)
|
||||
COMMAND_STATUS_TX_DONE = CommandStatus(0x6)
|
||||
|
||||
// SleepConfig
|
||||
SLEEP_DATA_BUFFER_RETAIN = SleepConfig(2)
|
||||
SLEEP_DATA_RAM_RETAIN = SleepConfig(1)
|
||||
|
||||
// StandbyConfig
|
||||
STANDBY_RC = StandbyConfig(0)
|
||||
STANDBY_XOSC = StandbyConfig(1)
|
||||
|
||||
// PeriodBase
|
||||
PERIOD_BASE_15_625_US = PeriodBase(0)
|
||||
PERIOD_BASE_62_5_US = PeriodBase(1)
|
||||
PERIOD_BASE_1_MS = PeriodBase(2)
|
||||
PERIOD_BASE_4_MS = PeriodBase(3)
|
||||
|
||||
// PacketType
|
||||
PACKET_TYPE_GFSK = PacketType(0x00) // default
|
||||
PACKET_TYPE_LORA = PacketType(0x01)
|
||||
PACKET_TYPE_RANGING = PacketType(0x02)
|
||||
PACKET_TYPE_FLRC = PacketType(0x03)
|
||||
PACKET_TYPE_BLE = PacketType(0x04)
|
||||
|
||||
// RampTime
|
||||
RADIO_RAMP_02_US = RadioRampTime(0x00)
|
||||
RADIO_RAMP_04_US = RadioRampTime(0x20)
|
||||
RADIO_RAMP_06_US = RadioRampTime(0x40)
|
||||
RADIO_RAMP_08_US = RadioRampTime(0x60)
|
||||
RADIO_RAMP_10_US = RadioRampTime(0x80)
|
||||
RADIO_RAMP_12_US = RadioRampTime(0xA0)
|
||||
RADIO_RAMP_16_US = RadioRampTime(0xC0)
|
||||
RADIO_RAMP_20_US = RadioRampTime(0xE0)
|
||||
|
||||
// CadSymbolNum
|
||||
LORA_CAD_01_SYMBOL = CadSymbolNum(0x00)
|
||||
LORA_CAD_02_SYMBOLS = CadSymbolNum(0x20)
|
||||
LORA_CAD_04_SYMBOLS = CadSymbolNum(0x40)
|
||||
LORA_CAD_08_SYMBOLS = CadSymbolNum(0x60)
|
||||
LORA_CAD_16_SYMBOLS = CadSymbolNum(0x80)
|
||||
|
||||
// GFSK Modulation Params
|
||||
// Bitrate + Bandwidth - same for BLE
|
||||
GFSK_BLE_BR_2_000_BW_2_4 = GFSKBLEBitrateBandwidth(0x04)
|
||||
GFSK_BLE_BR_1_600_BW_2_4 = GFSKBLEBitrateBandwidth(0x28)
|
||||
GFSK_BLE_BR_1_000_BW_2_4 = GFSKBLEBitrateBandwidth(0x4C)
|
||||
GFSK_BLE_BR_1_000_BW_1_2 = GFSKBLEBitrateBandwidth(0x45)
|
||||
GFSK_BLE_BR_0_800_BW_2_4 = GFSKBLEBitrateBandwidth(0x70)
|
||||
GFSK_BLE_BR_0_800_BW_1_2 = GFSKBLEBitrateBandwidth(0x69)
|
||||
GFSK_BLE_BR_0_500_BW_1_2 = GFSKBLEBitrateBandwidth(0x8D)
|
||||
GFSK_BLE_BR_0_500_BW_0_6 = GFSKBLEBitrateBandwidth(0x86)
|
||||
GFSK_BLE_BR_0_400_BW_1_2 = GFSKBLEBitrateBandwidth(0xB1)
|
||||
GFSK_BLE_BR_0_400_BW_0_6 = GFSKBLEBitrateBandwidth(0xAA)
|
||||
GFSK_BLE_BR_0_250_BW_0_6 = GFSKBLEBitrateBandwidth(0xCE)
|
||||
GFSK_BLE_BR_0_250_BW_0_3 = GFSKBLEBitrateBandwidth(0xC7)
|
||||
GFSK_BLE_BR_0_125_BW_0_3 = GFSKBLEBitrateBandwidth(0xEF)
|
||||
|
||||
// Modulation Index - same for BLE
|
||||
MOD_IND_0_35 = ModulationIndex(0x00)
|
||||
MOD_IND_0_5 = ModulationIndex(0x01)
|
||||
MOD_IND_0_75 = ModulationIndex(0x02)
|
||||
MOD_IND_1_00 = ModulationIndex(0x03)
|
||||
MOD_IND_1_25 = ModulationIndex(0x04)
|
||||
MOD_IND_1_50 = ModulationIndex(0x05)
|
||||
MOD_IND_1_75 = ModulationIndex(0x06)
|
||||
MOD_IND_2_00 = ModulationIndex(0x07)
|
||||
MOD_IND_2_25 = ModulationIndex(0x08)
|
||||
MOD_IND_2_50 = ModulationIndex(0x09)
|
||||
MOD_IND_2_75 = ModulationIndex(0x0A)
|
||||
MOD_IND_3_00 = ModulationIndex(0x0B)
|
||||
MOD_IND_3_25 = ModulationIndex(0x0C)
|
||||
MOD_IND_3_50 = ModulationIndex(0x0D)
|
||||
MOD_IND_3_75 = ModulationIndex(0x0E)
|
||||
MOD_IND_4_00 = ModulationIndex(0x0F)
|
||||
|
||||
// Modulation Shaping - same for BLE and FLRC
|
||||
MOD_SHAPING_OFF = ModulationShaping(0x00)
|
||||
MOD_SHAPING_1_0 = ModulationShaping(0x10)
|
||||
MOD_SHAPING_0_5 = ModulationShaping(0x20)
|
||||
|
||||
// GFSK Packet Params
|
||||
// Preamble Length
|
||||
GFSK_PREAMBLE_LENGTH_04_BITS = GFSKPreambleLength(0x00)
|
||||
GFSK_PREAMBLE_LENGTH_08_BITS = GFSKPreambleLength(0x10)
|
||||
GFSK_PREAMBLE_LENGTH_12_BITS = GFSKPreambleLength(0x20)
|
||||
GFSK_PREAMBLE_LENGTH_16_BITS = GFSKPreambleLength(0x30)
|
||||
GFSK_PREAMBLE_LENGTH_20_BITS = GFSKPreambleLength(0x40)
|
||||
GFSK_PREAMBLE_LENGTH_24_BITS = GFSKPreambleLength(0x50)
|
||||
GFSK_PREAMBLE_LENGTH_28_BITS = GFSKPreambleLength(0x60)
|
||||
GFSK_PREAMBLE_LENGTH_32_BITS = GFSKPreambleLength(0x70)
|
||||
|
||||
// Sync Word Length
|
||||
GFSK_SYNC_WORD_LEN_1_B = GFSKSyncWordLength(0x00)
|
||||
GFSK_SYNC_WORD_LEN_2_B = GFSKSyncWordLength(0x02)
|
||||
GFSK_SYNC_WORD_LEN_3_B = GFSKSyncWordLength(0x04)
|
||||
GFSK_SYNC_WORD_LEN_4_B = GFSKSyncWordLength(0x06)
|
||||
GFSK_SYNC_WORD_LEN_5_B = GFSKSyncWordLength(0x08)
|
||||
|
||||
// Sync Word Match
|
||||
GFSK_SYNCWORD_MATCH_OFF = GFSKSyncWordMatch(0x00)
|
||||
GFSK_SYNCWORD_MATCH_1 = GFSKSyncWordMatch(0x10)
|
||||
GFSK_SYNCWORD_MATCH_2 = GFSKSyncWordMatch(0x20)
|
||||
GFSK_SYNCWORD_MATCH_1_2 = GFSKSyncWordMatch(0x30)
|
||||
GFSK_SYNCWORD_MATCH_3 = GFSKSyncWordMatch(0x40)
|
||||
GFSK_SYNCWORD_MATCH_1_3 = GFSKSyncWordMatch(0x50)
|
||||
GFSK_SYNCWORD_MATCH_2_3 = GFSKSyncWordMatch(0x60)
|
||||
GFSK_SYNCWORD_MATCH_1_2_3 = GFSKSyncWordMatch(0x70)
|
||||
|
||||
// GFSK Header Type
|
||||
GFSK_HEADER_FIXED_LENGTH = GFSKHeaderType(0x00)
|
||||
GFSK_HEADER_VARIABLE_LENGTH = GFSKHeaderType(0x20)
|
||||
|
||||
// GFSK CRC Type
|
||||
GFSK_CRC_OFF = GFSKCrcType(0x00)
|
||||
GFSK_CRC_1_BYTE = GFSKCrcType(0x10)
|
||||
GFSK_CRC_2_BYTES = GFSKCrcType(0x20)
|
||||
|
||||
// BLE Packet Params
|
||||
// Connection State
|
||||
BLE_MASTER_SLAVE = BLEConnectionState(0x00)
|
||||
BLE_ADVERTISER = BLEConnectionState(0x02)
|
||||
BLE_TX_TEST_MODE = BLEConnectionState(0x04)
|
||||
BLE_RX_TEST_MODE = BLEConnectionState(0x06)
|
||||
BLE_RXTX_TEST_MODE = BLEConnectionState(0x08)
|
||||
|
||||
// CRC Type
|
||||
BLE_CRC_OFF = BLECrcType(0x00)
|
||||
BLE_CRC_3_BYTES = BLECrcType(0x10)
|
||||
|
||||
// BLE Test Payload
|
||||
BLE_PAYLOAD_PRBS_9 = BLETestPayload(0x00)
|
||||
BLE_PAYLOAD_EYELONG_1_0 = BLETestPayload(0x04)
|
||||
BLE_PAYLOAD_EYESHORT_1_0 = BLETestPayload(0x08)
|
||||
BLE_PAYLOAD_PRBS_15 = BLETestPayload(0x0C)
|
||||
BLE_PAYLOAD_ALL_1 = BLETestPayload(0x10)
|
||||
BLE_PAYLOAD_ALL_0 = BLETestPayload(0x14)
|
||||
BLE_PAYLOAD_EYELONG_0_1 = BLETestPayload(0x18)
|
||||
BLE_PAYLOAD_EYESHORT_0_1 = BLETestPayload(0x1C)
|
||||
|
||||
// FLRC Modulation Params
|
||||
// Bitrate + Bandwidth
|
||||
FLRC_BR_1_300_BW_1_2 = FLRCBitrateBandwidth(0x45)
|
||||
FLRC_BR_1_000_BW_1_2 = FLRCBitrateBandwidth(0x69)
|
||||
FLRC_BR_0_650_BW_0_6 = FLRCBitrateBandwidth(0x86)
|
||||
FLRC_BR_0_520_BW_0_6 = FLRCBitrateBandwidth(0xAA)
|
||||
FLRC_BR_0_325_BW_0_3 = FLRCBitrateBandwidth(0xC7)
|
||||
FLRC_BR_0_260_BW_0_3 = FLRCBitrateBandwidth(0xEB)
|
||||
|
||||
// Coding Rate
|
||||
FLRC_CR_1_2 = FLRCCodingRate(0x00) // 1/2
|
||||
FLRC_CR_3_4 = FLRCCodingRate(0x02) // 3/4
|
||||
FLRC_CR_1_0 = FLRCCodingRate(0x04) // 1
|
||||
|
||||
// FLRC Packet Params
|
||||
// Preamble Length
|
||||
FLRC_PREAMBLE_LENGTH_4_BITS = FLRCPreambleLength(0x00)
|
||||
FLRC_PREAMBLE_LENGTH_8_BITS = FLRCPreambleLength(0x10)
|
||||
FLRC_PREAMBLE_LENGTH_12_BITS = FLRCPreambleLength(0x20)
|
||||
FLRC_PREAMBLE_LENGTH_16_BITS = FLRCPreambleLength(0x30)
|
||||
FLRC_PREAMBLE_LENGTH_20_BITS = FLRCPreambleLength(0x40)
|
||||
FLRC_PREAMBLE_LENGTH_24_BITS = FLRCPreambleLength(0x50)
|
||||
FLRC_PREAMBLE_LENGTH_28_BITS = FLRCPreambleLength(0x60)
|
||||
FLRC_PREAMBLE_LENGTH_32_BITS = FLRCPreambleLength(0x70)
|
||||
|
||||
// Sync Word Length
|
||||
FLRC_SYNC_WORD_LEN_0 = FLRCSyncWordLength(0x00)
|
||||
FLRC_SYNC_WORD_LEN_32_BITS = FLRCSyncWordLength(0x04)
|
||||
|
||||
// Sync Word Match
|
||||
FLRC_SYNC_WORD_MATCH_DISABLE = FLRCSyncWordMatch(0x00) // Disable Sync Word
|
||||
FLRC_SYNC_WORD_MATCH_1 = FLRCSyncWordMatch(0x10) // Sync Word 1
|
||||
FLRC_SYNC_WORD_MATCH_2 = FLRCSyncWordMatch(0x20) // Sync Word 2
|
||||
FLRC_SYNC_WORD_MATCH_1_2 = FLRCSyncWordMatch(0x30) // Sync Word 1 or Sync Word 2
|
||||
FLRC_SYNC_WORD_MATCH_3 = FLRCSyncWordMatch(0x40) // Sync Word 3
|
||||
FLRC_SYNC_WORD_MATCH_1_3 = FLRCSyncWordMatch(0x50) // Sync Word 1 or Sync Word 3
|
||||
FLRC_SYNC_WORD_MATCH_2_3 = FLRCSyncWordMatch(0x60) // Sync Word 2 or Sync Word 3
|
||||
FLRC_SYNC_WORD_MATCH_1_2_3 = FLRCSyncWordMatch(0x70) // Sync Word 1 or Sync Word 2 or Sync Word 3
|
||||
|
||||
// Header Type
|
||||
FLRC_HEADER_FIXED_LENGTH = FLRCHeaderType(0x00)
|
||||
FLRC_HEADER_VARIABLE_LENGTH = FLRCHeaderType(0x20)
|
||||
|
||||
// CRC Type
|
||||
FLRC_CRC_OFF = FLRCCrcType(0x00)
|
||||
FLRC_CRC_1_BYTE = FLRCCrcType(0x10)
|
||||
FLRC_CRC_2_BYTES = FLRCCrcType(0x20)
|
||||
FLRC_CRC_3_BYTES = FLRCCrcType(0x30)
|
||||
|
||||
// LoRa Modulation Params
|
||||
|
||||
// SpreadingFactor
|
||||
LORA_SF_5 = LoRaSpreadingFactor(0x50)
|
||||
LORA_SF_6 = LoRaSpreadingFactor(0x60)
|
||||
LORA_SF_7 = LoRaSpreadingFactor(0x70)
|
||||
LORA_SF_8 = LoRaSpreadingFactor(0x80)
|
||||
LORA_SF_9 = LoRaSpreadingFactor(0x90)
|
||||
LORA_SF_10 = LoRaSpreadingFactor(0xA0)
|
||||
LORA_SF_11 = LoRaSpreadingFactor(0xB0)
|
||||
LORA_SF_12 = LoRaSpreadingFactor(0xC0)
|
||||
|
||||
// Bandwidth
|
||||
LORA_BW_1600 = LoRaBandwidth(0x0A)
|
||||
LORA_BW_800 = LoRaBandwidth(0x18)
|
||||
LORA_BW_400 = LoRaBandwidth(0x26)
|
||||
LORA_BW_200 = LoRaBandwidth(0x34)
|
||||
|
||||
// CodingRate
|
||||
LORA_CR_4_5 = LoRaCodingRate(0x01)
|
||||
LORA_CR_4_6 = LoRaCodingRate(0x02)
|
||||
LORA_CR_4_7 = LoRaCodingRate(0x03)
|
||||
LORA_CR_4_8 = LoRaCodingRate(0x04)
|
||||
LORA_CR_LI_4_5 = LoRaCodingRate(0x05)
|
||||
LORA_CR_LI_4_6 = LoRaCodingRate(0x06)
|
||||
LORA_CR_LI_4_8 = LoRaCodingRate(0x07)
|
||||
|
||||
// LoraPacketParams
|
||||
// HeaderType
|
||||
LORA_HEADER_EXPLICIT = LoRaHeaderType(0x00)
|
||||
LORA_HEADER_IMPLICIT = LoRaHeaderType(0x80)
|
||||
|
||||
// CRC Type
|
||||
LORA_CRC_ENABLE = LoRaCrcType(0x20)
|
||||
LORA_CRC_DISABLE = LoRaCrcType(0x00)
|
||||
|
||||
// IQ Type
|
||||
LORA_IQ_INVERTED = LoRaIqType(0x00)
|
||||
LORA_IQ_STD = LoRaIqType(0x40)
|
||||
|
||||
// RegulatorMode
|
||||
REGULATOR_LDO = RegulatorMode(0)
|
||||
REGULATOR_DC_DC = RegulatorMode(1)
|
||||
|
||||
// IRQ masks
|
||||
IRQ_ALL_MASK = IRQMask(0xFFFF)
|
||||
IRQ_NONE_MASK = IRQMask(0x0000)
|
||||
IRQ_TX_DONE_MASK = IRQMask(0b0000000000000001)
|
||||
IRQ_RX_DONE_MASK = IRQMask(0b0000000000000010)
|
||||
IRQ_SYNC_WORD_VALID_MASK = IRQMask(0b0000000000000100)
|
||||
IRQ_SYNC_WORD_ERROR_MASK = IRQMask(0b0000000000001000)
|
||||
IRQ_HEADER_VALID_MASK = IRQMask(0b0000000000010000)
|
||||
IRQ_HEADER_ERROR_MASK = IRQMask(0b0000000000100000)
|
||||
IRQ_CRC_ERROR_MASK = IRQMask(0b0000000001000000)
|
||||
IRQ_RANGING_SLAVE_RESPONSE_DONE_MASK = IRQMask(0b0000000010000000)
|
||||
IRQ_RANGING_SLAVE_RESPONSE_DISCARD_MASK = IRQMask(0b0000000100000000)
|
||||
IRQ_RANGING_MASTER_RESULT_VALID_MASK = IRQMask(0b0000001000000000)
|
||||
IRQ_RANGING_MASTER_TIMEOUT_MASK = IRQMask(0b0000010000000000)
|
||||
IRQ_RANGING_SLAVE_REQUEST_VALID_MASK = IRQMask(0b0000100000000000)
|
||||
IRQ_CAD_DONE_MASK = IRQMask(0b0001000000000000)
|
||||
IRQ_CAD_DETECTED_MASK = IRQMask(0b0010000000000000)
|
||||
IRQ_RX_TX_TIMEOUT_MASK = IRQMask(0b0100000000000000)
|
||||
IRQ_PREAMBLE_DETECTED_MASK = IRQMask(0b1000000000000000)
|
||||
IRQ_ADVANCED_RANGING_DONE_MASK = IRQMask(0b1000000000000000)
|
||||
|
||||
// GFSK Packet Info
|
||||
GFSK_SYNC_ERROR = GFSKPacketInfo(0b1000000)
|
||||
GFSK_LENGTH_ERROR = GFSKPacketInfo(0b0100000)
|
||||
GFSK_CRC_ERROR = GFSKPacketInfo(0b0010000)
|
||||
GFSK_ABORT_ERROR = GFSKPacketInfo(0b0001000)
|
||||
GFSK_HEADER_RECEIVED = GFSKPacketInfo(0b0000100)
|
||||
GFSK_PACKET_RECEIVED = GFSKPacketInfo(0b0000010)
|
||||
GFSK_PACKET_CRTL_BUSY = GFSKPacketInfo(0b0000001)
|
||||
|
||||
// BLE Packet Info
|
||||
BLE_SYNC_ERROR = BLEPacketInfo(0b1000000)
|
||||
BLE_LENGTH_ERROR = BLEPacketInfo(0b0100000)
|
||||
BLE_CRC_ERROR = BLEPacketInfo(0b0010000)
|
||||
BLE_ABORT_ERROR = BLEPacketInfo(0b0001000)
|
||||
BLE_HEADER_RECEIVED = BLEPacketInfo(0b0000100)
|
||||
BLE_PACKET_RECEIVED = BLEPacketInfo(0b0000010)
|
||||
BLE_PACKET_CRTL_BUSY = BLEPacketInfo(0b0000001)
|
||||
|
||||
// FLRC Packet Info
|
||||
FLRC_SYNC_ERROR = FLRCPacketInfo(0b1000000)
|
||||
FLRC_LENGTH_ERROR = FLRCPacketInfo(0b0100000)
|
||||
FLRC_CRC_ERROR = FLRCPacketInfo(0b0010000)
|
||||
FLRC_ABORT_ERROR = FLRCPacketInfo(0b0001000)
|
||||
FLRC_HEADER_RECEIVED = FLRCPacketInfo(0b0000100)
|
||||
FLRC_PACKET_RECEIVED = FLRCPacketInfo(0b0000010)
|
||||
FLRC_PACKET_CRTL_BUSY = FLRCPacketInfo(0b0000001)
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
package sx128x
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrBusyPinTimeout = errors.New("busy pin timeout")
|
||||
errDataTooLong = errors.New("data over 256 bytes")
|
||||
errInvalidSleepConfig = errors.New("invalid sleep config")
|
||||
errInvalidStandbyConfig = errors.New("invalid standby config")
|
||||
errFrequencyTooLow = errors.New("frequency below 2.4Ghz")
|
||||
errFrequencyTooHigh = errors.New("frequency above 2.5Ghz")
|
||||
errPowerTooLow = errors.New("power level below -18dBm")
|
||||
errPowerTooHigh = errors.New("power level above 13dBm")
|
||||
errInvalidPeriodBase = errors.New("invalid period base")
|
||||
errInvalidPacketType = errors.New("invalid packet type")
|
||||
errInvalidRegulatorMode = errors.New("invalid regulator mode")
|
||||
errPayloadLengthTooShort = errors.New("payload length too short")
|
||||
errPayloadLengthTooLong = errors.New("payload length too long")
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
package sx128x
|
||||
|
||||
const (
|
||||
|
||||
// SX128X register map
|
||||
REG_FIRMWARE_VERSIONS = uint16(0x153)
|
||||
REG_RX_GAIN = uint16(0x891)
|
||||
REG_MANUAL_GAIN_SETTING = uint16(0x895)
|
||||
REG_LNA_GAIN_VALUE = uint16(0x89E)
|
||||
REG_LNA_GAIN_CONTROL = uint16(0x89F)
|
||||
REG_SYNCH_PEAK_ATTENUATION = uint16(0x8C2)
|
||||
REG_PAYLOAD_LENGTH = uint16(0x901)
|
||||
REG_LORA_HEADER_MODE = uint16(0x903)
|
||||
REG_RANGING_REQUEST_ADDRESS_BYTE_3 = uint16(0x912)
|
||||
REG_RANGING_REQUEST_ADDRESS_BYTE_2 = uint16(0x913)
|
||||
REG_RANGING_REQUEST_ADDRESS_BYTE_1 = uint16(0x914)
|
||||
REG_RANGING_REQUEST_ADDRESS_BYTE_0 = uint16(0x915)
|
||||
REG_RANGING_DEVICE_ADDRESS_BYTE_3 = uint16(0x916)
|
||||
REG_RANGING_DEVICE_ADDRESS_BYTE_2 = uint16(0x917)
|
||||
REG_RANGING_DEVICE_ADDRESS_BYTE_1 = uint16(0x918)
|
||||
REG_RANGING_DEVICE_ADDRESS_BYTE_0 = uint16(0x919)
|
||||
REG_RANGING_FILTER_WINDOW_SIZE = uint16(0x91E)
|
||||
REG_RESET_RANGING_FILTER = uint16(0x923)
|
||||
REG_RANGING_RESULT_MUX = uint16(0x924)
|
||||
REG_SF_ADDITIONAL_CONFIGURATION = uint16(0x925)
|
||||
REG_RANGING_CALIBRATION_BYTE_2 = uint16(0x92B)
|
||||
REG_RANGING_CALIBRATION_BYTE_1 = uint16(0x92C)
|
||||
REG_RANGING_CALIBRATION_BYTE_0 = uint16(0x92D)
|
||||
REG_RANGING_ID_CHECK_LENGTH = uint16(0x931)
|
||||
REG_FREQUENCY_ERROR_CORRECTION = uint16(0x93C)
|
||||
REG_CAD_DETECT_PEAK = uint16(0x942)
|
||||
REG_LORA_SYNC_WORD_MSB = uint16(0x944)
|
||||
REG_LORA_SYNC_WORD_LSB = uint16(0x945)
|
||||
REG_HEADER_CRC = uint16(0x954)
|
||||
REG_CODING_RATE = uint16(0x950)
|
||||
REG_FEI_BYTE_2 = uint16(0x954)
|
||||
REG_FEI_BYTE_1 = uint16(0x955)
|
||||
REG_FEI_BYTE_0 = uint16(0x956)
|
||||
REG_RANGING_RESULT_BYTE_2 = uint16(0x961)
|
||||
REG_RANGING_RESULT_BYTE_1 = uint16(0x962)
|
||||
REG_RANGING_RESULT_BYTE_0 = uint16(0x963)
|
||||
REG_RANGING_RSSI = uint16(0x964)
|
||||
REG_FREEZE_RANGING_RESULT = uint16(0x97F)
|
||||
REG_PACKET_PREAMBLE_SETTINGS = uint16(0x9C1)
|
||||
REG_WHITENING_INITIAL_VALUE = uint16(0x9C5)
|
||||
REG_CRC_POLYNOMIAL_DEFINITION_MSB = uint16(0x9C6)
|
||||
REG_CRC_POLYNOMIAL_DEFINITION_LSB = uint16(0x9C7)
|
||||
REG_CRC_POLYNOMIAL_SEED_BYTE_2 = uint16(0x9C7)
|
||||
REG_CRC_POLYNOMIAL_SEED_BYTE_1 = uint16(0x9C8)
|
||||
REG_CRC_POLYNOMIAL_SEED_BYTE_0 = uint16(0x9C9)
|
||||
REG_CRC_MSB_INITIAL_VALUE = uint16(0x9C8)
|
||||
REG_CRC_LSB_INITIAL_VALUE = uint16(0x9C9)
|
||||
REG_SYNC_ADDRESS_CONTROL = uint16(0x9CD)
|
||||
REG_SYNC_ADDRESS_1_BYTE_4 = uint16(0x9CE)
|
||||
REG_SYNC_ADDRESS_1_BYTE_3 = uint16(0x9CF)
|
||||
REG_SYNC_ADDRESS_1_BYTE_2 = uint16(0x9D0)
|
||||
REG_SYNC_ADDRESS_1_BYTE_1 = uint16(0x9D1)
|
||||
REG_SYNC_ADDRESS_1_BYTE_0 = uint16(0x9D2)
|
||||
REG_SYNC_ADDRESS_2_BYTE_4 = uint16(0x9D3)
|
||||
REG_SYNC_ADDRESS_2_BYTE_3 = uint16(0x9D4)
|
||||
REG_SYNC_ADDRESS_2_BYTE_2 = uint16(0x9D5)
|
||||
REG_SYNC_ADDRESS_2_BYTE_1 = uint16(0x9D6)
|
||||
REG_SYNC_ADDRESS_2_BYTE_0 = uint16(0x9D7)
|
||||
REG_SYNC_ADDRESS_3_BYTE_4 = uint16(0x9D8)
|
||||
REG_SYNC_ADDRESS_3_BYTE_3 = uint16(0x9D9)
|
||||
REG_SYNC_ADDRESS_3_BYTE_2 = uint16(0x9DA)
|
||||
REG_SYNC_ADDRESS_3_BYTE_1 = uint16(0x9DB)
|
||||
REG_SYNC_ADDRESS_3_BYTE_0 = uint16(0x9DC)
|
||||
)
|
||||
@@ -0,0 +1,768 @@
|
||||
package sx128x
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
type Device struct {
|
||||
spi drivers.SPI
|
||||
nssPin pin.Output
|
||||
resetPin pin.Output
|
||||
busyPin pin.Input
|
||||
spiTxBuf []byte
|
||||
spiRxBuf []byte
|
||||
}
|
||||
|
||||
func New(spi drivers.SPI, nssPin pin.Output, resetPin pin.Output, busyPin pin.Input) *Device {
|
||||
return &Device{
|
||||
spi: spi,
|
||||
nssPin: nssPin,
|
||||
resetPin: resetPin,
|
||||
busyPin: busyPin,
|
||||
spiTxBuf: make([]byte, 256), // TODO: optimize buffer size
|
||||
spiRxBuf: make([]byte, 256),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) Reset() {
|
||||
d.resetPin.Set(false)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
d.resetPin.Set(true)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
func (d *Device) WaitWhileBusy(timeout time.Duration) error {
|
||||
// largest busy period is on boot with around ~400ish this should be more than enough
|
||||
now := time.Now()
|
||||
for d.busyPin.Get() {
|
||||
if time.Since(now) > timeout {
|
||||
return ErrBusyPinTimeout
|
||||
}
|
||||
runtime.Gosched()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get tranceiver status, returns circuit mode and command status
|
||||
func (d *Device) GetStatus() (CircuitMode, CommandStatus, error) {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
status, err := d.spi.Transfer(cmdGetStatus)
|
||||
d.nssPin.Set(true)
|
||||
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
circuitMode := (status & circuitModeMask) >> 5
|
||||
commandStatus := (status & commandStatusMask) >> 2
|
||||
return CircuitMode(circuitMode), CommandStatus(commandStatus), nil
|
||||
}
|
||||
|
||||
func (d *Device) WriteRegister(addr uint16, data []byte) error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdWriteRegister, uint8((addr>>8)&0xFF), uint8(addr&0xFF))
|
||||
d.spiTxBuf = append(d.spiTxBuf, data...)
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Device) ReadRegister(addr uint16) (uint8, error) {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdReadRegister, uint8((addr&0xFF00)>>8), uint8(addr&0x00FF), 0x00, 0x00)
|
||||
d.spiRxBuf = d.spiRxBuf[:5]
|
||||
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
|
||||
d.nssPin.Set(true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d.spiRxBuf[4], nil
|
||||
}
|
||||
|
||||
func (d *Device) WriteBuffer(offset uint8, data []byte) error {
|
||||
if len(data) > 256 {
|
||||
return errDataTooLong
|
||||
}
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdWriteBuffer, offset)
|
||||
d.spiTxBuf = append(d.spiTxBuf, data...)
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Read data from the payload buffer starting at the given offset with the given length
|
||||
func (d *Device) ReadBuffer(offset uint8, length uint8) ([]byte, error) {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdReadBuffer, offset, 0x00)
|
||||
for i := uint8(0); i < length; i++ {
|
||||
d.spiTxBuf = append(d.spiTxBuf, 0x00)
|
||||
}
|
||||
d.spiRxBuf = d.spiRxBuf[:len(d.spiTxBuf)]
|
||||
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
|
||||
d.nssPin.Set(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.spiRxBuf[3:], nil
|
||||
}
|
||||
|
||||
// Set the device into sleep mode with the given configuration: 0 (no retention), 1 (ram retentation), 2 (buffer retention) or 3 (ram and buffer retention)
|
||||
func (d *Device) SetSleep(sleepConfig SleepConfig) error {
|
||||
if sleepConfig > (SLEEP_DATA_BUFFER_RETAIN | SLEEP_DATA_RAM_RETAIN) {
|
||||
return errInvalidSleepConfig
|
||||
}
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetSleep, uint8(sleepConfig))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Put device into standby mode, 0 (RC) or 1 (XOSC)
|
||||
func (d *Device) SetStandby(standbyConfig StandbyConfig) error {
|
||||
if standbyConfig > STANDBY_XOSC { // XOSC is the highest standby config anything higher is invalid
|
||||
return errInvalidStandbyConfig
|
||||
}
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetStandby, uint8(standbyConfig))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the device into Frequency Synthesizer mode
|
||||
func (d *Device) SetFs() error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetFS)
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
func checkPeriodBase(periodBase PeriodBase) error {
|
||||
if periodBase > PERIOD_BASE_4_MS { // 4ms is the highest period base anything higher is invalid
|
||||
return errInvalidPeriodBase
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sets the device in transmit mode, the IRQ status should be cleared before using this command
|
||||
// timout is determined by periodBase * periodBaseCount
|
||||
func (d *Device) SetTx(periodBase PeriodBase, periodBaseCount uint16) error {
|
||||
err := checkPeriodBase(periodBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetTx, uint8(periodBase), uint8((periodBaseCount>>8)&0xFF), uint8(periodBaseCount&0xFF))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Sets the device in receive mode, the IRQ status should be cleared before using this command
|
||||
// timeout is determined by periodBase * periodBaseCount
|
||||
func (d *Device) SetRx(periodBase PeriodBase, periodBaseCount uint16) error {
|
||||
err := checkPeriodBase(periodBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetRx, uint8(periodBase), uint8((periodBaseCount>>8)&0xFF), uint8(periodBaseCount&0xFF))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Sets the device in a continuous receive mode, it enters receive mode with a timeout of periodBase * rxPeriodBaseCount.
|
||||
// If no packet is received it will enter sleep mode for periodBase * sleepPeriodBaseCount before re-entering receive mode.
|
||||
// The loop is exited when a packet is received or the device is put into standby mode.
|
||||
func (d *Device) SetRxDutyCycle(periodBase PeriodBase, rxPeriodBaseCount uint16, sleepPeriodBaseCount uint16) error {
|
||||
err := checkPeriodBase(periodBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetRxDutyCycle, uint8(periodBase), uint8((rxPeriodBaseCount&0xFF00)>>8), uint8(rxPeriodBaseCount&0x00FF))
|
||||
d.spiTxBuf = append(d.spiTxBuf, uint8((sleepPeriodBaseCount&0xFF00)>>8), uint8(sleepPeriodBaseCount&0x00FF))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Sets the transceiver into Long Preamble mode, and can only be used with either the LoRa mode and GFSK mode
|
||||
func (d *Device) SetLongPreamble(enable bool) error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetLongPreamble)
|
||||
if enable {
|
||||
d.spiTxBuf = append(d.spiTxBuf, 1)
|
||||
} else {
|
||||
d.spiTxBuf = append(d.spiTxBuf, 0)
|
||||
}
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Channel activity detection (CAD) is a LoRa specific mode of operation where the device searches for a LoRa signal.
|
||||
// After search has completed, the device returns to STDBY_RC mode. The length of the search is configured via the SetCadParams() command.
|
||||
func (d *Device) SetCAD() error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetCAD)
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Test command to generate a Continuous Wave (RF tone) at a selected frequency and output power
|
||||
// The device remains in Tx Continuous Wave until the host sends a mode configuration command.
|
||||
func (d *Device) SetTxContinuousWave() error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetTxContinuousWave)
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Test command to generate an infinite sequence of alternating ‘0’s and ‘1’s in
|
||||
// GFSK modulation and symbol 0 in LoRa. The device remains in transmit until the host sends a mode configuration command.
|
||||
func (d *Device) SetTxContinuousPreamble() error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetContinuousPreamble)
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// This command allows the transceiver to send a packet at a user programmable time after the end of a packet reception.
|
||||
// This is useful for Bluetooth Low Energy (BLE) compatibility which requires the transceiver to be able to send back a response 150µs after a packet reception.
|
||||
func (d *Device) SetAutoTx(timeUs uint16) error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetAutoTx, uint8((timeUs&0xFF00)>>8), uint8(timeUs&0x00FF))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Modifies the chip behavior so that the state following a Rx or Tx operation is FS and not standby.
|
||||
// This allows for faster transitions between Rx and/or Tx.
|
||||
func (d *Device) SetAutoFs(enable bool) error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetAutoFS)
|
||||
if enable {
|
||||
d.spiTxBuf = append(d.spiTxBuf, 1)
|
||||
} else {
|
||||
d.spiTxBuf = append(d.spiTxBuf, 0)
|
||||
}
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Choose between GFSK, LoRa, Ranging, FLRC or BLE packet types, this will affect the available configuration parameters and the structure of the packet
|
||||
func (d *Device) SetPacketType(packetType PacketType) error {
|
||||
if packetType > PACKET_TYPE_BLE { // BLE is the highest packet type anything higher is invalid.
|
||||
return errInvalidPacketType
|
||||
}
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetPacketType, uint8(packetType))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the currently configured packet type, this will be 0 (GFSK), 1 (LoRa), 2 (Ranging), 3 (FLRC) or 4 (BLE)
|
||||
func (d *Device) GetPacketType() (PacketType, error) {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdGetPacketType, 0x00, 0x00)
|
||||
d.spiRxBuf = d.spiRxBuf[:3]
|
||||
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
|
||||
d.nssPin.Set(true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return PacketType(d.spiRxBuf[2]), nil
|
||||
}
|
||||
|
||||
// Set the RF frequency in Hz, must be between 2.4 GHz and 2.5 GHz
|
||||
func (d *Device) SetRfFrequency(frequencyHz uint32) error {
|
||||
if frequencyHz < 2400000000 {
|
||||
return errFrequencyTooLow
|
||||
}
|
||||
if frequencyHz > 2500000000 {
|
||||
return errFrequencyTooHigh
|
||||
}
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
rfFrequency := uint32((uint64(frequencyHz) << 18) / 52000000)
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetRFFrequency, uint8((rfFrequency>>16)&0xFF), uint8((rfFrequency>>8)&0xFF), uint8(rfFrequency&0xFF))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the output power in dBm, must be between -18 and 13 dBm, and the ramp time
|
||||
func (d *Device) SetTxParams(powerdBm int8, rampTime RadioRampTime) error {
|
||||
if powerdBm < -18 {
|
||||
return errPowerTooLow
|
||||
}
|
||||
if powerdBm > 13 {
|
||||
return errPowerTooHigh
|
||||
}
|
||||
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
adjustedPower := uint8(powerdBm + 18)
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetTxParams, adjustedPower, uint8(rampTime))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the number of symbols used for channel activity detection which determines the sensitivity of the detection.
|
||||
// This is only applicable in LoRa mode.
|
||||
func (d *Device) SetCadParams(cadSymbolNum uint8) error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetCADParams, cadSymbolNum)
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set the base address for the internal buffer for Tx and Rx operations.
|
||||
// When transmitting or receiving data is read from or written to the buffer starting at the given offset.
|
||||
func (d *Device) SetBufferBaseAddress(txBase uint8, rxBase uint8) error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetBufferBaseAddress, txBase, rxBase)
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// The arguments to this function depend on the packet type. It is recommended to use the mode specific functions for a better experience.
|
||||
// BLE & GFSK: BitrateBandwidth, ModulationIndex, ModulationShaping
|
||||
// FLRC: BitrateBandwidth, CodingRate, ModulationShaping
|
||||
// LoRa & Ranging: SpreadingFactor, Bandwidth, CodingRate
|
||||
func (d *Device) SetModulationParams(modParam1, modParam2, modParam3 uint8) error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetModulationParams, modParam1, modParam2, modParam3)
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Device) SetModulationParamsBLE(bitrateBandwidth GFSKBLEBitrateBandwidth, modulationIndex ModulationIndex, modulationShaping ModulationShaping) error {
|
||||
return d.SetModulationParams(uint8(bitrateBandwidth), uint8(modulationIndex), uint8(modulationShaping))
|
||||
}
|
||||
|
||||
func (d *Device) SetModulationParamsGFSK(bitrateBandwidth GFSKBLEBitrateBandwidth, modulationIndex ModulationIndex, modulationShaping ModulationShaping) error {
|
||||
return d.SetModulationParams(uint8(bitrateBandwidth), uint8(modulationIndex), uint8(modulationShaping))
|
||||
}
|
||||
|
||||
func (d *Device) SetModulationParamsFLRC(bitrateBandwidth FLRCBitrateBandwidth, codingRate FLRCCodingRate, modulationShaping ModulationShaping) error {
|
||||
return d.SetModulationParams(uint8(bitrateBandwidth), uint8(codingRate), uint8(modulationShaping))
|
||||
}
|
||||
|
||||
func (d *Device) SetModulationParamsLoRa(spreadingFactor LoRaSpreadingFactor, bandwidth LoRaBandwidth, codingRate LoRaCodingRate) error {
|
||||
return d.SetModulationParams(uint8(spreadingFactor), uint8(bandwidth), uint8(codingRate))
|
||||
}
|
||||
|
||||
// The arguments to this function depend on the packet type. It is recommended to use the mode specific functions for a better experience.
|
||||
// GFSK & FLRC: PreambleLength, SyncWordLength, SyncWordMatch, HeaderType, PayloadLength, CrcLength, Whitening
|
||||
// BLE: ConnectionState, CrcLength, BleTestPayload, Whitening
|
||||
// LoRa & Ranging: PreambleLength, HeaderType, PayloadLength, CRC, InvertIQ/chirp invert
|
||||
func (d *Device) SetPacketParams(param1, param2, param3, param4, param5, param6, param7 uint8) error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetPacketParams, param1, param2, param3, param4, param5, param6, param7)
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set GFSK related packet parameters, this assumes the packet type is already set to GFSK.
|
||||
// - payloadLength: range of 0-255
|
||||
func (d *Device) SetPacketParamsGFSK(preambleLength GFSKPreambleLength, syncWordLength GFSKSyncWordLength, syncWordMatch GFSKSyncWordMatch, headerType GFSKHeaderType, payloadLength uint8, crcLength GFSKCrcType, whitening bool) error {
|
||||
var whiteningVal uint8
|
||||
if whitening {
|
||||
whiteningVal = whiteningEnable
|
||||
} else {
|
||||
whiteningVal = whiteningDisable
|
||||
}
|
||||
return d.SetPacketParams(uint8(preambleLength), uint8(syncWordLength), uint8(syncWordMatch), uint8(headerType), payloadLength, uint8(crcLength), whiteningVal)
|
||||
}
|
||||
|
||||
// Set FLRC related packet parameters, this assumes the packet type is already set to FLRC.
|
||||
// - payloadLength: range of 6-127
|
||||
func (d *Device) SetPacketParamsFLRC(preambleLength FLRCPreambleLength, syncWordLength FLRCSyncWordLength, syncWordMatch FLRCSyncWordMatch, headerType FLRCHeaderType, payloadLength uint8, crcLength FLRCCrcType) error {
|
||||
if payloadLength < 6 {
|
||||
return errPayloadLengthTooShort
|
||||
}
|
||||
if payloadLength > 127 {
|
||||
return errPayloadLengthTooLong
|
||||
}
|
||||
return d.SetPacketParams(uint8(preambleLength), uint8(syncWordLength), uint8(syncWordMatch), uint8(headerType), payloadLength, uint8(crcLength), whiteningDisable)
|
||||
}
|
||||
|
||||
// Set BLE related packet parameters, this assumes the packet type is already set to BLE.
|
||||
func (d *Device) SetPacketParamsBLE(connectionState BLEConnectionState, crcLength BLECrcType, bleTestPayload BLETestPayload, whitening bool) error {
|
||||
var whiteningVal uint8
|
||||
if whitening {
|
||||
whiteningVal = whiteningEnable
|
||||
} else {
|
||||
whiteningVal = whiteningDisable
|
||||
}
|
||||
return d.SetPacketParams(uint8(connectionState), uint8(crcLength), uint8(bleTestPayload), whiteningVal, 0, 0, 0)
|
||||
}
|
||||
|
||||
// Set LoRa related packet parameters, this assumes the packet type is already set to LoRa.
|
||||
// - payloadLength: range of 1-255
|
||||
func (d *Device) SetPacketParamsLoRa(preambleLength uint32, headerType LoRaHeaderType, payloadLength uint8, crcType LoRaCrcType, iqType LoRaIqType) error {
|
||||
if payloadLength == 0 {
|
||||
return errPayloadLengthTooShort
|
||||
}
|
||||
exponent, mantissa := getExponentAndMantissa(preambleLength)
|
||||
return d.SetPacketParams(uint8(exponent<<4)|mantissa, uint8(headerType), payloadLength, uint8(crcType), uint8(iqType), 0, 0)
|
||||
}
|
||||
|
||||
func getExponentAndMantissa(value uint32) (uint8, uint8) {
|
||||
// pulled from RadioLib https://github.com/jgromes/RadioLib/blob/master/src/modules/SX128x/SX128x.cpp
|
||||
e := uint8(1)
|
||||
m := uint8(1)
|
||||
len := uint32(0)
|
||||
for e = uint8(1); e <= 15; e++ {
|
||||
for m = uint8(1); m <= 15; m++ {
|
||||
len = uint32(m) * (uint32(1 << e))
|
||||
if len >= value {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len >= value {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return e, m
|
||||
}
|
||||
|
||||
// Get information about the most recent packet received.
|
||||
// Return the payload length, the offset in the buffer where the payload starts.
|
||||
func (d *Device) GetRxBufferStatus() (uint8, uint8, error) {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdGetRxBufferStatus, 0x00, 0x00, 0x00)
|
||||
d.spiRxBuf = d.spiRxBuf[:4]
|
||||
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
|
||||
d.nssPin.Set(true)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return d.spiRxBuf[2], d.spiRxBuf[3], nil
|
||||
}
|
||||
|
||||
// The return type of this function depends on the packet type. Use mode specific function for typed returns.
|
||||
// BLE, GFSK & FLRC: unused, rssiSync, errors, status, sync
|
||||
// LoRa & Ranging: rssiSync, SNR
|
||||
func (d *Device) GetPacketStatus() (uint8, uint8, uint8, uint8, uint8, error) {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdGetPacketStatus, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
|
||||
d.spiRxBuf = d.spiRxBuf[:7]
|
||||
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
|
||||
d.nssPin.Set(true)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, 0, err
|
||||
}
|
||||
return d.spiRxBuf[2], d.spiRxBuf[3], d.spiRxBuf[4], d.spiRxBuf[5], d.spiRxBuf[6], nil
|
||||
}
|
||||
|
||||
// Get information about the most recent GFSK packet received or transmitted:
|
||||
// - RSSI of last received packet
|
||||
// - packet information (each bit represents a different error or status flag)
|
||||
// - whether the last packet transmission has ended
|
||||
// - the sync word that was used for the last packet reception (0-3)
|
||||
func (d *Device) GetPacketStatusGFSK() (float32, GFSKPacketInfo, bool, uint8, error) {
|
||||
_, rssiSync, packetInfo, status, sync, err := d.GetPacketStatus()
|
||||
if err != nil {
|
||||
return 0, 0, false, 0, err
|
||||
}
|
||||
return float32(int8(rssiSync)) / 2 * -1, GFSKPacketInfo(packetInfo), status != 0, sync, nil
|
||||
}
|
||||
|
||||
// Get information about the most recent BLE packet received or transmitted:
|
||||
// - RSSI of last received packet
|
||||
// - packet information (each bit represents a different error or status flag)
|
||||
// - whether the last packet transmission has ended
|
||||
// - the sync word that was used for the last packet reception (0-1)
|
||||
func (d *Device) GetPacketStatusBLE() (float32, BLEPacketInfo, bool, uint8, error) {
|
||||
_, rssiSync, packetInfo, status, sync, err := d.GetPacketStatus()
|
||||
if err != nil {
|
||||
return 0, 0, false, 0, err
|
||||
}
|
||||
return float32(int8(rssiSync)) / 2 * -1, BLEPacketInfo(packetInfo), status != 0, sync, nil
|
||||
}
|
||||
|
||||
// Get information about the most recent BLE packet received or transmitted:
|
||||
// - RSSI of last received packet
|
||||
// - packet information (each bit represents a different error or status flag)
|
||||
// - PID field of the received packet
|
||||
// - NO_ACK field of the received packet
|
||||
// - PID check status of the current packet
|
||||
// - whether the last packet transmission has ended
|
||||
// - the sync word that was used for the last packet reception (0-1)
|
||||
func (d *Device) GetPacketStatusFLRC() (float32, FLRCPacketInfo, uint8, bool, bool, bool, uint8, error) {
|
||||
_, rawRSSI, packetInfo, rxTxInfo, sync, err := d.GetPacketStatus()
|
||||
|
||||
rxPid := (rxTxInfo & 0b11000000) >> 6
|
||||
noAck := (rxTxInfo & 0b00100000) != 0
|
||||
pidCheck := (rxTxInfo & 0b00010000) != 0
|
||||
txDone := (rxTxInfo & 0b00000001) != 0
|
||||
|
||||
if err != nil {
|
||||
return 0, 0, 0, false, false, false, 0, err
|
||||
}
|
||||
return float32(int8(rawRSSI)) / 2 * -1, FLRCPacketInfo(packetInfo), rxPid, noAck, pidCheck, txDone, sync, nil
|
||||
}
|
||||
|
||||
// Get information about the most recent LoRa packet received:
|
||||
// - RSSI of last received packet
|
||||
// - signal-to-noise ratio (SNR) of last received packet
|
||||
func (d *Device) GetPacketStatusLoRa() (float32, float32, error) {
|
||||
rawRSSI, rawSnr, _, _, _, err := d.GetPacketStatus()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return float32(int8(rawRSSI)) / 2 * -1, float32(int8(rawSnr)) / 4, nil
|
||||
}
|
||||
|
||||
// Get the instantaneous RSSI value during reception of the packet
|
||||
func (d *Device) GetRssiInst() (float32, error) {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdGetRSSIInst, 0x00, 0x00)
|
||||
d.spiRxBuf = d.spiRxBuf[:3]
|
||||
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
|
||||
d.nssPin.Set(true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return float32(int8(d.spiRxBuf[2])) / 2 * -1, nil
|
||||
}
|
||||
|
||||
// Configure the overall IRQ mask and the mapping of individual IRQs to the DIO1, DIO2 and DIO3 pins
|
||||
func (d *Device) SetDioIrqParams(irqMask IRQMask, dio1Mask IRQMask, dio2Mask IRQMask, dio3Mask IRQMask) error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetDIOIRQParams, uint8((irqMask&0xFF00)>>8), uint8(irqMask&0x00FF))
|
||||
d.spiTxBuf = append(d.spiTxBuf, uint8((dio1Mask&0xFF00)>>8), uint8(dio1Mask&0x00FF))
|
||||
d.spiTxBuf = append(d.spiTxBuf, uint8((dio2Mask&0xFF00)>>8), uint8(dio2Mask&0x00FF))
|
||||
d.spiTxBuf = append(d.spiTxBuf, uint8((dio3Mask&0xFF00)>>8), uint8(dio3Mask&0x00FF))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the current IRQ status.
|
||||
func (d *Device) GetIrqStatus() (IRQMask, error) {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdGetIRQStatus, 0x00, 0x00, 0x00)
|
||||
d.spiRxBuf = d.spiRxBuf[:4]
|
||||
err = d.spi.Tx(d.spiTxBuf, d.spiRxBuf)
|
||||
d.nssPin.Set(true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint16(d.spiRxBuf[2])<<8 | uint16(d.spiRxBuf[3]), err
|
||||
}
|
||||
|
||||
// Clear the IRQ bits specified in the irqMask.
|
||||
func (d *Device) ClearIrqStatus(irqMask IRQMask) error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdClearIRQStatus, uint8((irqMask&0xFF00)>>8), uint8(irqMask&0x00FF))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Switch between the low-dropout regulator (LDO) and the DC-DC converter for internal power regulation.
|
||||
func (d *Device) SetRegulatorMode(mode RegulatorMode) error {
|
||||
if mode > REGULATOR_DC_DC { // DC-DC is the highest regulator mode anything higher is invalid
|
||||
return errInvalidRegulatorMode
|
||||
}
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetRegulatorMode, uint8(mode))
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
|
||||
// Stores the present context of the radio register values to the Data RAM which will be restored when the device wakes up from sleep mode.
|
||||
func (d *Device) SetSaveContext() error {
|
||||
err := d.WaitWhileBusy(time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.nssPin.Set(false)
|
||||
d.spiTxBuf = d.spiTxBuf[:0]
|
||||
d.spiTxBuf = append(d.spiTxBuf, cmdSetSaveContext)
|
||||
err = d.spi.Tx(d.spiTxBuf, nil)
|
||||
d.nssPin.Set(true)
|
||||
return err
|
||||
}
|
||||
+49
-5
@@ -17,7 +17,8 @@ import (
|
||||
// the new assembly implementation - no fiddly timings to calculate and no nops
|
||||
// to count!
|
||||
//
|
||||
// Right now this is specific to Cortex-M chips and assume the following things:
|
||||
// Right now this is specific to specific chips:
|
||||
// On Cortex-M chips it assume the following things:
|
||||
// - Arithmetic operations (shift, add, sub) take up 1 clock cycle.
|
||||
// - The nop instruction also takes up 1 clock cycle.
|
||||
// - Store instructions (to the GPIO pins) take up 2 clock cycles.
|
||||
@@ -25,8 +26,15 @@ import (
|
||||
// depends on whether the branch is taken or not. On the M4, the documentation
|
||||
// is less clear but it appears the instruction is still 1 to 3 cycles
|
||||
// (possibly including some branch prediction).
|
||||
// It is certainly possible to extend this to other architectures, such as AVR
|
||||
// and RISC-V if needed.
|
||||
// On RISC-V chips it assumes the following things:
|
||||
// - Arithmetic operations (shift, add, sub) take up 1 clock cycle.
|
||||
// - The nop instruction also takes up 1 clock cycle.
|
||||
// - Store instructions (to the GPIO pins) take up 1 clock cycle.
|
||||
// - Branch instructions can take up 1 or 3 clock cycles, depending on branch
|
||||
// prediction. This is based on the SiFive FE310 CPU, but hopefully it
|
||||
// generalizes to other RISC-V chips as well.
|
||||
|
||||
// It is certainly possible to extend this to other architectures, such as AVR as needed.
|
||||
//
|
||||
// Here are two important resources. For the timings:
|
||||
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
|
||||
@@ -45,6 +53,7 @@ type architectureImpl struct {
|
||||
maxBaseCyclesT1H int
|
||||
minBaseCyclesTLD int
|
||||
valueTemplate string // template for how to pass the 'c' byte to assembly
|
||||
funcAttr string // C function attribute (default: always_inline)
|
||||
template string // assembly template
|
||||
}
|
||||
|
||||
@@ -83,7 +92,7 @@ var architectures = map[string]architectureImpl{
|
||||
// - branches are 1 or 3 cycles, depending on branch prediction
|
||||
// - ALU operations are 1 cycle (as on most CPUs)
|
||||
// Hopefully this generalizes to other chips.
|
||||
buildTag: "tinygo.riscv32",
|
||||
buildTag: "tinygo.riscv32 && !esp32c3",
|
||||
minBaseCyclesT0H: 1 + 1 + 1, // shift + branch (not taken) + store
|
||||
maxBaseCyclesT0H: 1 + 3 + 1, // shift + branch (not taken) + store
|
||||
minBaseCyclesT1H: 1 + 1 + 1, // shift + branch (taken) + store
|
||||
@@ -103,6 +112,37 @@ var architectures = map[string]architectureImpl{
|
||||
@DELAY3
|
||||
addi %[i], %[i], -1 // [1]
|
||||
bnez %[i], 1b // [1/3] send_bit
|
||||
`,
|
||||
},
|
||||
"esp32c3": {
|
||||
// ESP32-C3 RISC-V core:
|
||||
// - stores are 1 cycle
|
||||
// - branches are 1 or 3 cycles
|
||||
// - ALU operations are 1 cycle
|
||||
// Uses the same instruction timing as the SiFive FE310, but the
|
||||
// function is placed in IRAM instead of flash to avoid instruction
|
||||
// cache miss stalls that would destroy WS2812 timing.
|
||||
buildTag: "esp32c3",
|
||||
minBaseCyclesT0H: 1 + 1 + 1, // shift + branch (not taken) + store
|
||||
maxBaseCyclesT0H: 1 + 3 + 1, // shift + branch (not taken) + store
|
||||
minBaseCyclesT1H: 1 + 1 + 1, // shift + branch (taken) + store
|
||||
maxBaseCyclesT1H: 1 + 3 + 1, // shift + branch (taken) + store
|
||||
minBaseCyclesTLD: 1 + 1 + 1, // subtraction + branch + store (in next cycle)
|
||||
valueTemplate: "(uint32_t)c << 23",
|
||||
funcAttr: `__attribute__((section(".iram1"), noinline))`,
|
||||
template: `
|
||||
1: // send_bit
|
||||
sw %[maskSet], %[portSet] // [1] T0H and T0L start here
|
||||
@DELAY1
|
||||
slli %[value], %[value], 1 // [1] shift value left by 1
|
||||
bltz %[value], 2f // [1/3] skip_store
|
||||
sw %[maskClear], %[portClear] // [1] T0H -> T0L transition
|
||||
2: // skip_store
|
||||
@DELAY2
|
||||
sw %[maskClear], %[portClear] // [1] T1H -> T1L transition
|
||||
@DELAY3
|
||||
addi %[i], %[i], -1 // [1]
|
||||
bnez %[i], 1b // [1/3] send_bit
|
||||
`,
|
||||
},
|
||||
}
|
||||
@@ -208,7 +248,11 @@ func writeCAssembly(f *os.File, arch string, megahertz int) error {
|
||||
// ignore I/O errors.
|
||||
buf := &bytes.Buffer{}
|
||||
fmt.Fprintf(buf, "\n")
|
||||
fmt.Fprintf(buf, "__attribute__((always_inline))\nvoid ws2812_writeByte%d(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {\n", megahertz)
|
||||
funcAttr := archImpl.funcAttr
|
||||
if funcAttr == "" {
|
||||
funcAttr = "__attribute__((always_inline))"
|
||||
}
|
||||
fmt.Fprintf(buf, "%s\nvoid ws2812_writeByte%d(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {\n", funcAttr, megahertz)
|
||||
fmt.Fprintf(buf, " // Timings:\n")
|
||||
fmt.Fprintf(buf, " // T0H: %2d - %2d cycles or %.1fns - %.1fns\n", actualMinCyclesT0H, actualMaxCyclesT0H, actualMinNanosecondsT0H, actualMaxNanosecondsT0H)
|
||||
fmt.Fprintf(buf, " // T1H: %2d - %2d cycles or %.1fns - %.1fns\n", actualMinCyclesT1H, actualMaxCyclesT1H, actualMinNanosecondsT1H, actualMaxNanosecondsT1H)
|
||||
|
||||
@@ -1281,6 +1281,377 @@ void ws2812_writeByte150(char c, uint32_t *portSet, uint32_t *portClear, uint32_
|
||||
[portClear]"m"(*portClear));
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
void ws2812_writeByte160(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
|
||||
// Timings:
|
||||
// T0H: 56 - 58 cycles or 350.0ns - 362.5ns
|
||||
// T1H: 168 - 170 cycles or 1050.0ns - 1062.5ns
|
||||
// TLD: 184 - cycles or 1150.0ns -
|
||||
uint32_t value = (uint32_t)c << 24;
|
||||
char i = 8;
|
||||
__asm__ __volatile__(
|
||||
"1: @ send_bit\n"
|
||||
"\t str %[maskSet], %[portSet] @ [2] T0H and T0L start here\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t lsls %[value], #1 @ [1]\n"
|
||||
"\t bcs.n 2f @ [1/3] skip_store\n"
|
||||
"\t str %[maskClear], %[portClear] @ [2] T0H -> T0L transition\n"
|
||||
"\t2: @ skip_store\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t str %[maskClear], %[portClear] @ [2] T1H -> T1L transition\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t subs %[i], #1 @ [1]\n"
|
||||
"\t beq.n 3f @ [1/3] end\n"
|
||||
"\t b 1b @ [1/3] send_bit\n"
|
||||
"\t3: @ end\n"
|
||||
: [value]"+r"(value),
|
||||
[i]"+r"(i)
|
||||
: [maskSet]"r"(maskSet),
|
||||
[portSet]"m"(*portSet),
|
||||
[maskClear]"r"(maskClear),
|
||||
[portClear]"m"(*portClear));
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
void ws2812_writeByte168(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
|
||||
// Timings:
|
||||
@@ -2192,6 +2563,16 @@ func (d Device) writeByte150(c byte) {
|
||||
interrupt.Restore(mask)
|
||||
}
|
||||
|
||||
func (d Device) writeByte160(c byte) {
|
||||
portSet, maskSet := d.Pin.PortMaskSet()
|
||||
portClear, maskClear := d.Pin.PortMaskClear()
|
||||
|
||||
mask := interrupt.Disable()
|
||||
C.ws2812_writeByte160(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||
|
||||
interrupt.Restore(mask)
|
||||
}
|
||||
|
||||
func (d Device) writeByte168(c byte) {
|
||||
portSet, maskSet := d.Pin.PortMaskSet()
|
||||
portClear, maskClear := d.Pin.PortMaskClear()
|
||||
|
||||
@@ -0,0 +1,396 @@
|
||||
//go:build esp32c3
|
||||
|
||||
package ws2812
|
||||
|
||||
// Warning: autogenerated file. Instead of modifying this file, change
|
||||
// gen-ws2812.go and run "go generate".
|
||||
|
||||
import "runtime/interrupt"
|
||||
import "unsafe"
|
||||
|
||||
/*
|
||||
#include <stdint.h>
|
||||
|
||||
__attribute__((section(".iram1"), noinline))
|
||||
void ws2812_writeByte160(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
|
||||
// Timings:
|
||||
// T0H: 56 - 58 cycles or 350.0ns - 362.5ns
|
||||
// T1H: 168 - 170 cycles or 1050.0ns - 1062.5ns
|
||||
// TLD: 184 - cycles or 1150.0ns -
|
||||
uint32_t value = (uint32_t)c << 23;
|
||||
char i = 8;
|
||||
__asm__ __volatile__(
|
||||
"1: // send_bit\n"
|
||||
"\t sw %[maskSet], %[portSet] // [1] T0H and T0L start here\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t slli %[value], %[value], 1 // [1] shift value left by 1\n"
|
||||
"\t bltz %[value], 2f // [1/3] skip_store\n"
|
||||
"\t sw %[maskClear], %[portClear] // [1] T0H -> T0L transition\n"
|
||||
"\t2: // skip_store\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t sw %[maskClear], %[portClear] // [1] T1H -> T1L transition\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t addi %[i], %[i], -1 // [1]\n"
|
||||
"\t bnez %[i], 1b // [1/3] send_bit\n"
|
||||
: [value]"+r"(value),
|
||||
[i]"+r"(i)
|
||||
: [maskSet]"r"(maskSet),
|
||||
[portSet]"m"(*portSet),
|
||||
[maskClear]"r"(maskClear),
|
||||
[portClear]"m"(*portClear));
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func (d Device) writeByte160(c byte) {
|
||||
portSet, maskSet := d.Pin.PortMaskSet()
|
||||
portClear, maskClear := d.Pin.PortMaskClear()
|
||||
|
||||
mask := interrupt.Disable()
|
||||
C.ws2812_writeByte160(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||
|
||||
interrupt.Restore(mask)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build tinygo.riscv32
|
||||
//go:build tinygo.riscv32 && !esp32c3
|
||||
|
||||
package ws2812
|
||||
|
||||
@@ -11,378 +11,6 @@ import "unsafe"
|
||||
/*
|
||||
#include <stdint.h>
|
||||
|
||||
__attribute__((always_inline))
|
||||
void ws2812_writeByte160(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
|
||||
// Timings:
|
||||
// T0H: 56 - 58 cycles or 350.0ns - 362.5ns
|
||||
// T1H: 168 - 170 cycles or 1050.0ns - 1062.5ns
|
||||
// TLD: 184 - cycles or 1150.0ns -
|
||||
uint32_t value = (uint32_t)c << 23;
|
||||
char i = 8;
|
||||
__asm__ __volatile__(
|
||||
"1: // send_bit\n"
|
||||
"\t sw %[maskSet], %[portSet] // [1] T0H and T0L start here\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t slli %[value], %[value], 1 // [1] shift value left by 1\n"
|
||||
"\t bltz %[value], 2f // [1/3] skip_store\n"
|
||||
"\t sw %[maskClear], %[portClear] // [1] T0H -> T0L transition\n"
|
||||
"\t2: // skip_store\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t sw %[maskClear], %[portClear] // [1] T1H -> T1L transition\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t nop\n"
|
||||
"\t addi %[i], %[i], -1 // [1]\n"
|
||||
"\t bnez %[i], 1b // [1/3] send_bit\n"
|
||||
: [value]"+r"(value),
|
||||
[i]"+r"(i)
|
||||
: [maskSet]"r"(maskSet),
|
||||
[portSet]"m"(*portSet),
|
||||
[maskClear]"r"(maskClear),
|
||||
[portClear]"m"(*portClear));
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
void ws2812_writeByte320(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {
|
||||
// Timings:
|
||||
@@ -1109,16 +737,6 @@ void ws2812_writeByte320(char c, uint32_t *portSet, uint32_t *portClear, uint32_
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func (d Device) writeByte160(c byte) {
|
||||
portSet, maskSet := d.Pin.PortMaskSet()
|
||||
portClear, maskClear := d.Pin.PortMaskClear()
|
||||
|
||||
mask := interrupt.Disable()
|
||||
C.ws2812_writeByte160(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear))
|
||||
|
||||
interrupt.Restore(mask)
|
||||
}
|
||||
|
||||
func (d Device) writeByte320(c byte) {
|
||||
portSet, maskSet := d.Pin.PortMaskSet()
|
||||
portClear, maskClear := d.Pin.PortMaskClear()
|
||||
|
||||
+3
-2
@@ -4,8 +4,9 @@
|
||||
// On RP2040/RP2350 it uses PIO for hardware-timed control.
|
||||
package ws2812 // import "tinygo.org/x/drivers/ws2812"
|
||||
|
||||
//go:generate go run gen-ws2812.go -arch=cortexm 16 48 64 120 125 150 168 200
|
||||
//go:generate go run gen-ws2812.go -arch=tinygoriscv 160 320
|
||||
//go:generate go run gen-ws2812.go -arch=cortexm 16 48 64 120 125 150 160 168 200
|
||||
//go:generate go run gen-ws2812.go -arch=tinygoriscv 320
|
||||
//go:generate go run gen-ws2812.go -arch=esp32c3 160
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
@@ -34,6 +34,9 @@ func (d Device) WriteByte(c byte) error {
|
||||
case 150_000_000: // 150MHz, e.g. rp2350
|
||||
d.writeByte150(c)
|
||||
return nil
|
||||
case 160_000_000: // 160MHz, e.g. stm32u585
|
||||
d.writeByte160(c)
|
||||
return nil
|
||||
case 168_000_000: // 168MHz, e.g. stm32f405
|
||||
d.writeByte168(c)
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//go:build esp32c3
|
||||
|
||||
package ws2812
|
||||
|
||||
import "machine"
|
||||
|
||||
// Send a single byte using the WS2812 protocol.
|
||||
func (d Device) WriteByte(c byte) error {
|
||||
switch machine.CPUFrequency() {
|
||||
case 160_000_000: // 160MHz
|
||||
d.writeByte160(c)
|
||||
return nil
|
||||
default:
|
||||
return errUnknownClockSpeed
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ package ws2812
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"runtime"
|
||||
|
||||
pio "github.com/tinygo-org/pio/rp2-pio"
|
||||
"github.com/tinygo-org/pio/rp2-pio/piolib"
|
||||
@@ -27,6 +28,9 @@ func newWS2812Device(pin machine.Pin) Device {
|
||||
writeColorFunc: func(_ Device, buf []color.RGBA, brightness uint8) error {
|
||||
for _, c := range buf {
|
||||
r, g, b := applyBrightness(c, brightness)
|
||||
for ws.IsQueueFull() {
|
||||
runtime.Gosched()
|
||||
}
|
||||
ws.PutRGB(r, g, b)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build tinygo.riscv32
|
||||
//go:build tinygo.riscv32 && !esp32c3
|
||||
|
||||
package ws2812
|
||||
|
||||
@@ -7,9 +7,6 @@ import "machine"
|
||||
// Send a single byte using the WS2812 protocol.
|
||||
func (d Device) WriteByte(c byte) error {
|
||||
switch machine.CPUFrequency() {
|
||||
case 160_000_000: // 160MHz, e.g. esp32c3
|
||||
d.writeByte160(c)
|
||||
return nil
|
||||
case 320_000_000: // 320MHz, e.g. fe310
|
||||
d.writeByte320(c)
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user