Compare commits

...

33 Commits

Author SHA1 Message Date
Pat Whittingslow 46bde26249 Merge branch 'dev' into sdcard-refactor 2026-07-14 16:54:29 -03:00
Patricio Whittingslow 0bc660e1bc add documentation, smoke test and fix a couple bugs 2026-07-14 16:53:11 -03:00
deadprogram dc6edbb694 ws2812: fix PIO TX FIFO overflow on rp2040/rp2350 dropping LEDs
PutRGB calls TxPut which is non-blocking and silently discards data
when the TX FIFO is full.

Wait for FIFO space before each PutRGB using runtime.Gosched() to
yield cooperatively, matching the pattern used by piolib.WriteRaw.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-07-06 20:21:35 +02:00
deadprogram 1d695a231a ws2812: add support for the ESP32-C3 processor
This adds support to the WS2812 for the ESP32-C3 processor
which is a RISC-V processor from Espressif.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-06-04 19:49:50 +02:00
deadprogram 04acd8e666 netlink: add Hostname field and some godocs comments
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-05-17 08:51:06 +01:00
Joost bb2d365868 RP002-1.0.5 states that max TX power is 16 for eu868. Later, more subtle TX power tactics will be implemented. (#866)
Co-authored-by: Joost Helberg <joost@helberg.nl>
2026-05-17 08:00:40 +02:00
deadprogram f459992f3c ws2812: add support for 160MHz cortex-m processors
This adds hardware timing support for driving WS2812 LEDs on Cortex-M microcontrollers running at 160MHz (such as the STM32U585).

- Updated `go:generate` directive to include 160MHz.
- Generated the corresponding `ws2812_writeByte160` assembly routine.
- Added a switch case in `WriteByte` to handle generic 160MHz processors.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-05-17 06:48:52 +01:00
Joel Wetzell 62663c1832 add SPI driver for semtech sx128x chips (#864)
* add SPI driver for semtech sx128x chips

Co-authored-by: Copilot <copilot@github.com>

* handle busy loop better

* switch to time based busy timeout

Co-authored-by: Copilot <copilot@github.com>

* comment functions

* start on using types for function inputs

* use types where applicable and align with datasheet more

Co-authored-by: Copilot <copilot@github.com>

* work on exporting less constants

* only export "actionable" errors

* combine identical constants

* add crude lora rx and tx examples

* change from type aliases to local types

---------

Co-authored-by: Copilot <copilot@github.com>
2026-05-04 09:42:03 -03:00
soypat 62f51445b6 fix examples/sd/main.go 2024-01-23 20:38:30 -03:00
soypat 37ae0ad5b8 remove remnants of sector size 2024-01-23 20:33:05 -03:00
soypat 8de5ab7c64 sd: backtrack on EraseSectors, stick to block nomenclature 2024-01-23 20:25:58 -03:00
soypat e14fbf6d3d add sd/README.md 2024-01-23 20:21:27 -03:00
soypat 4d0d8e9c14 delete rustref.go; add readme to sd 2024-01-23 20:16:08 -03:00
soypat 964364005d working BlockDevice 2024-01-16 00:46:50 -03:00
soypat 0d80962dc8 add blkIdxer 2024-01-15 23:23:08 -03:00
soypat cb2ca239f0 rename EraseBlocks to EraseSectors 2024-01-15 21:25:22 -03:00
soypat c5ff13ad23 implement Card interface 2024-01-15 21:11:49 -03:00
soypat e6907db19e add BlockDevice 2024-01-15 20:09:27 -03:00
soypat 45e207fe2e remove unused API 2024-01-15 01:51:22 -03:00
soypat 677f8ed297 remove prints 2024-01-15 01:33:38 -03:00
soypat b31c5ca9c9 add prints 2024-01-15 01:32:09 -03:00
soypat 1b726ef2bd fully comply 2024-01-15 01:31:03 -03:00
soypat 7db9e9d6db still working on consolidation of init 2024-01-15 01:04:39 -03:00
soypat 845bd6fe93 remove some of API 2024-01-14 22:50:42 -03:00
soypat 97ef4986ba add config baud increase docs 2024-01-14 20:16:45 -03:00
soypat d5db138d9a no crc errors in status; closing the gap? 2024-01-14 20:08:57 -03:00
soypat ace4a8924b add status string method 2024-01-14 18:59:28 -03:00
soypat 66da4422dc implement waitToken 2024-01-14 17:38:20 -03:00
soypat 222f368681 rename commands 2024-01-14 17:04:16 -03:00
soypat 3d491553dd passing tests 2024-01-14 16:10:31 -03:00
soypat 46cd56951c failing CRC7 implementation 2024-01-14 14:17:04 -03:00
soypat 2f85c8bd04 CSD logic shared between V1 and V2 2024-01-14 12:39:10 -03:00
soypat 5b6571350d begin adding sd.Card refactor 2024-01-14 02:31:41 -03:00
30 changed files with 4362 additions and 406 deletions
+111
View File
@@ -0,0 +1,111 @@
package main
import (
"fmt"
"machine"
"time"
"tinygo.org/x/drivers/sd"
)
const (
SPI_RX_PIN = machine.GP16
SPI_TX_PIN = machine.GP19
SPI_SCK_PIN = machine.GP18
SPI_CS_PIN = machine.GP15
)
var (
spibus = machine.SPI0
spicfg = machine.SPIConfig{
Frequency: 250000,
Mode: 0,
SCK: SPI_SCK_PIN,
SDO: SPI_TX_PIN,
SDI: SPI_RX_PIN,
}
)
func main() {
time.Sleep(time.Second)
SPI_CS_PIN.Configure(machine.PinConfig{Mode: machine.PinOutput})
err := spibus.Configure(spicfg)
if err != nil {
panic(err.Error())
}
sdcard := sd.NewSPICard(spibus, SPI_CS_PIN.Set)
println("start init")
err = sdcard.Init()
if err != nil {
panic("sd card init:" + err.Error())
}
// After initialization it's safe to increase SPI clock speed.
csd := sdcard.CSD()
kbps := csd.TransferSpeed().RateKilobits()
spicfg.Frequency = uint32(kbps * 1000)
err = spibus.Configure(spicfg)
cid := sdcard.CID()
fmt.Printf("name=%s\ncsd=\n%s\n", cid.ProductName(), csd.String())
bd, err := sd.NewBlockDevice(sdcard, csd.ReadBlockLen(), csd.NumberOfBlocks())
if err != nil {
panic("block device creation:" + err.Error())
}
var mc MemChecker
ok, badBlkIdx, err := mc.MemCheck(bd, 2, 100)
if err != nil {
panic("memcheck:" + err.Error())
}
if !ok {
println("bad block", badBlkIdx)
} else {
println("memcheck ok")
}
}
type MemChecker struct {
rdBuf []byte
storeBuf []byte
wrBuf []byte
}
func (mc *MemChecker) MemCheck(bd *sd.BlockDevice, blockIdx, numBlocks int64) (memOK bool, badBlockIdx int64, err error) {
size := bd.BlockSize() * numBlocks
if len(mc.rdBuf) < int(size) {
mc.rdBuf = make([]byte, size)
mc.wrBuf = make([]byte, size)
mc.storeBuf = make([]byte, size)
for i := range mc.wrBuf {
mc.wrBuf[i] = byte(i)
}
}
// Start by storing the original block contents.
_, err = bd.ReadAt(mc.storeBuf, blockIdx)
if err != nil {
return false, blockIdx, err
}
// Write the test pattern.
_, err = bd.WriteAt(mc.wrBuf, blockIdx)
if err != nil {
return false, blockIdx, err
}
// Read back the test pattern.
_, err = bd.ReadAt(mc.rdBuf, blockIdx)
if err != nil {
return false, blockIdx, err
}
for j := 0; j < len(mc.rdBuf); j++ {
// Compare the read back data with the test pattern.
if mc.rdBuf[j] != mc.wrBuf[j] {
badBlock := blockIdx + int64(j)/bd.BlockSize()
return false, badBlock, nil
}
mc.rdBuf[j] = 0
}
// Leave the card in it's previous state.
_, err = bd.WriteAt(mc.storeBuf, blockIdx)
return true, -1, nil
}
+106
View File
@@ -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")
}
+96
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
//go:build !digispark && !arduino && !arduino_uno
//go:build !digispark && !arduino && !arduino_uno && !xiao_esp32c3
package main
+11
View File
@@ -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
}
+2 -2
View File
@@ -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
View File
@@ -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)
}
+11
View File
@@ -0,0 +1,11 @@
## `sd` package
File map:
* `blockdevice.go`: Contains logic for creating an `io.WriterAt` and `io.ReaderAt` with the `sd.BlockDevice` concrete type
from the `sd.Card` interface which is intrinsically a blocked reader and writer.
* `spicard.go`: Contains the `sd.SpiCard` driver for controlling an SD card over SPI using the most commonly available circuit boards.
* `responses.go`: Contains a currently unused SD response implementations as per the latest specification.
* `definitions.go`: Contains SD Card specification definitions such as the CSD and CID types as well as encoding/decoding logic, as well as CRC logic.
+231
View File
@@ -0,0 +1,231 @@
package sd
import (
"errors"
"io"
"math/bits"
)
var (
errNegativeOffset = errors.New("sd: negative offset")
)
// Compile time guarantee of interface implementation.
var _ Card = (*SPICard)(nil)
var _ io.ReaderAt = (*BlockDevice)(nil)
var _ io.WriterAt = (*BlockDevice)(nil)
// Card is the interface implemented by SD card drivers such as [SPICard].
// It provides block-aligned I/O over the card's contents. Use [NewBlockDevice]
// to wrap a Card with byte-addressed [io.ReaderAt] and [io.WriterAt] interfaces.
type Card interface {
// WriteBlocks writes the given data to the card, starting at the given block index.
// The data must be a multiple of the block size.
WriteBlocks(data []byte, startBlockIdx int64) (int, error)
// ReadBlocks reads the given number of blocks from the card, starting at the given block index.
// The dst buffer must be a multiple of the block size.
ReadBlocks(dst []byte, startBlockIdx int64) (int, error)
// EraseBlocks erases blocks starting at startBlockIdx to startBlockIdx+numBlocks.
EraseBlocks(startBlock, numBlocks int64) error
}
// NewBlockDevice creates a new [BlockDevice] from a Card. blockSize must be a
// power of 2. For an initialized [SPICard], blockSize is typically the CSD's
// [CSD.ReadBlockLen] and numBlocks is [SPICard.NumberOfBlocks].
func NewBlockDevice(card Card, blockSize int, numBlocks int64) (*BlockDevice, error) {
if card == nil || blockSize <= 0 || numBlocks <= 0 {
return nil, errors.New("invalid argument(s)")
}
blk, err := makeBlockIndexer(blockSize)
if err != nil {
return nil, err
}
bd := &BlockDevice{
card: card,
blockbuf: make([]byte, blockSize),
blk: blk,
numblocks: int64(numBlocks),
}
return bd, nil
}
// BlockDevice implements the tinyfs.BlockDevice interface for a [Card],
// providing byte-addressed reads and writes at arbitrary offsets by buffering
// non-block-aligned accesses through an internal single-block buffer.
// BlockDevice is not safe for concurrent use.
type BlockDevice struct {
card Card
blockbuf []byte
blk blkIdxer
numblocks int64
}
// ReadAt implements the [io.ReaderAt] interface for an SD card.
// Reads need not be aligned to block boundaries.
func (bd *BlockDevice) ReadAt(p []byte, off int64) (n int, err error) {
if off < 0 {
return 0, errNegativeOffset
}
blockIdx := bd.blk.idx(off)
blockOff := bd.blk.off(off)
if blockOff != 0 {
// Non-aligned first block case.
if _, err = bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
return n, err
}
n += copy(p, bd.blockbuf[blockOff:])
p = p[n:]
blockIdx++
}
fullBlocksToRead := bd.blk.idx(int64(len(p)))
if fullBlocksToRead > 0 {
// 1 or more full blocks case.
endOffset := fullBlocksToRead * bd.blk.size()
ngot, err := bd.card.ReadBlocks(p[:endOffset], blockIdx)
if err != nil {
return n + ngot, err
}
p = p[endOffset:]
n += ngot
blockIdx += fullBlocksToRead
}
if len(p) > 0 {
// Non-aligned last block case.
if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
return n, err
}
n += copy(p, bd.blockbuf)
}
return n, nil
}
// WriteAt implements the [io.WriterAt] interface for an SD card. Writes need
// not be aligned to block boundaries: partial blocks are read, modified and
// written back.
func (bd *BlockDevice) WriteAt(p []byte, off int64) (n int, err error) {
if off < 0 {
return 0, errNegativeOffset
}
blockIdx := bd.blk.idx(off)
blockOff := bd.blk.off(off)
if blockOff != 0 {
// Non-aligned first block case.
if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
return n, err
}
nexpect := copy(bd.blockbuf[blockOff:], p)
ngot, err := bd.card.WriteBlocks(bd.blockbuf, blockIdx)
if err != nil {
return n, err
} else if ngot != len(bd.blockbuf) {
return n, io.ErrShortWrite
}
n += nexpect
p = p[nexpect:]
blockIdx++
}
fullBlocksToWrite := bd.blk.idx(int64(len(p)))
if fullBlocksToWrite > 0 {
// 1 or more full blocks case.
endOffset := fullBlocksToWrite * bd.blk.size()
ngot, err := bd.card.WriteBlocks(p[:endOffset], blockIdx)
n += ngot
if err != nil {
return n, err
} else if ngot != int(endOffset) {
return n, io.ErrShortWrite
}
p = p[ngot:]
blockIdx += fullBlocksToWrite
}
if len(p) > 0 {
// Non-aligned last block case.
if _, err := bd.card.ReadBlocks(bd.blockbuf, blockIdx); err != nil {
return n, err
}
copy(bd.blockbuf, p)
ngot, err := bd.card.WriteBlocks(bd.blockbuf, blockIdx)
if err != nil {
return n, err
} else if ngot != len(bd.blockbuf) {
return n, io.ErrShortWrite
}
n += len(p)
}
return n, nil
}
// Size returns the number of bytes in this block device.
func (bd *BlockDevice) Size() int64 {
return bd.BlockSize() * bd.numblocks
}
// BlockSize returns the size of a block in bytes.
func (bd *BlockDevice) BlockSize() int64 {
return bd.blk.size()
}
// EraseBlocks erases the given number of blocks. An implementation may
// transparently coalesce ranges of blocks into larger bundles if the chip
// supports this. The start and len parameters are in block numbers, use
// EraseBlockSize to map addresses to blocks.
func (bd *BlockDevice) EraseBlocks(startEraseBlockIdx, len int64) error {
return bd.card.EraseBlocks(startEraseBlockIdx, len)
}
// blkIdxer is a helper for calculating block indices and offsets.
type blkIdxer struct {
blockshift int64
blockmask int64
}
// makeBlockIndexer returns a blkIdxer for the given block size,
// which must be a power of 2.
func makeBlockIndexer(blockSize int) (blkIdxer, error) {
if blockSize <= 0 {
return blkIdxer{}, errNoblocks
}
tz := bits.TrailingZeros(uint(blockSize))
if blockSize>>tz != 1 {
return blkIdxer{}, errors.New("blockSize must be a power of 2")
}
blk := blkIdxer{
blockshift: int64(tz),
blockmask: (1 << tz) - 1,
}
return blk, nil
}
// size returns the size of a block in bytes.
func (blk *blkIdxer) size() int64 {
return 1 << blk.blockshift
}
// off gets the offset of the byte at byteIdx from the start of its block.
//
//go:inline
func (blk *blkIdxer) off(byteIdx int64) int64 {
return blk._moduloBlockSize(byteIdx)
}
// idx gets the block index that contains the byte at byteIdx.
//
//go:inline
func (blk *blkIdxer) idx(byteIdx int64) int64 {
return blk._divideBlockSize(byteIdx)
}
// modulo and divide are defined in terms of bit operations for speed since
// blockSize is a power of 2.
//go:inline
func (blk *blkIdxer) _moduloBlockSize(n int64) int64 { return n & blk.blockmask }
//go:inline
func (blk *blkIdxer) _divideBlockSize(n int64) int64 { return n >> blk.blockshift }
+178
View File
@@ -0,0 +1,178 @@
package sd
import (
"encoding/hex"
"testing"
)
func TestCRC16(t *testing.T) {
tests := []struct {
block string
wantcrc uint16
}{
{
block: "fa33c08ed0bc007c8bf45007501ffbfcbf0006b90001f2a5ea1d060000bebe07b304803c80740e803c00751c83c610fecb75efcd188b148b4c028bee83c610fecb741a803c0074f4be8b06ac3c00740b56bb0700b40ecd105eebf0ebfebf0500bb007cb8010257cd135f730c33c0cd134f75edbea306ebd3bec206bffe7d813d55aa75c78bf5ea007c0000496e76616c696420706172746974696f6e207461626c65004572726f72206c6f6164696e67206f7065726174696e672073797374656d004d697373696e67206f7065726174696e672073797374656d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000094c6dffd0000000401040cfec2ff000800000000f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000055aa",
wantcrc: 0x52ce,
},
}
for _, tt := range tests {
b, err := hex.DecodeString(tt.block)
if err != nil {
t.Fatal(err)
}
gotcrc := CRC16(b)
if gotcrc != tt.wantcrc {
t.Errorf("calculateCRC(%s) = %#x, want %#x", tt.block, gotcrc, tt.wantcrc)
}
}
}
func TestCRC7(t *testing.T) {
const cmdSendMask = 0x40
tests := []struct {
data []byte
wantCRC uint8
}{
{ // See CRC7 Examples from section 4.5 of the SD Card Physical Layer Simplified Specification.
data: []byte{cmdSendMask, 4: 0}, // CMD0, arg=0
wantCRC: 0b1001010,
},
{
data: []byte{cmdSendMask | 17, 4: 0}, // CMD17, arg=0
wantCRC: 0b0101010,
},
{
data: []byte{17, 3: 0b1001, 4: 0}, // Response of CMD17
wantCRC: 0b0110011,
},
{ // CSD for a 8GB card.
data: []byte{64, 14, 0, 50, 83, 89, 0, 0, 60, 1, 127, 128, 10, 64, 0},
wantCRC: 0b1110010,
},
}
for _, tt := range tests {
gotcrc := CRC7(tt.data[:])
if gotcrc != tt.wantCRC {
t.Errorf("got crc=%#b, want=%#b for %#b", gotcrc, tt.wantCRC, tt.data)
}
}
cmdTests := []struct {
cmd command
arg uint32
wantCRC uint8
}{
{
cmd: cmdGoIdleState,
arg: 0,
wantCRC: 0x95,
},
{
cmd: cmdSendIfCond,
arg: 0x1AA,
wantCRC: 0x87,
},
}
var dst [6]byte
for _, test := range cmdTests {
putCmd(dst[:], test.cmd, test.arg)
gotcrc := dst[5]
if gotcrc != test.wantCRC {
t.Errorf("got crc=%#x, want=%#x", gotcrc, test.wantCRC)
}
}
}
// TestCSDv2Capacity checks CSDv2 C_SIZE decoding and the capacity formula.
//
// Field layout and formula are from the SD Physical Layer Simplified
// Specification Version 9.10, section 5.3.3 "CSD Register (CSD Version 2.0)":
// C_SIZE occupies CSD bits [69:48] and user memory capacity is
//
// memory capacity = (C_SIZE+1) * 512KByte (512KByte = 524288 bytes)
//
// Specification download: https://www.sdcard.org/downloads/pls/
//
// Regression test for two past bugs in CSDv2:
// - csize() read byte 7 as data[7]>>2 instead of data[7]&0x3F, dropping
// C_SIZE bits [17:16] (misdecoded cards > 32GiB).
// - DeviceCapacity() computed csize*512000 instead of (csize+1)*524288.
func TestCSDv2Capacity(t *testing.T) {
tests := []struct {
name string
csd []byte
wantCSize uint32
wantCap int64
}{
{
// Same 8GB-card register as in TestCRC7 above, CRC byte appended
// (CRC7=0b1110010 per that test, stored as crc<<1|always1).
// C_SIZE = 0x003C01 = 15361 -> 15362 * 524288 = 8054112256 bytes.
name: "8GB card (in-repo vector)",
csd: []byte{64, 14, 0, 50, 83, 89, 0, 0, 60, 1, 127, 128, 10, 64, 0, 0b1110010<<1 | 1},
wantCSize: 0x003C01,
wantCap: 8054112256,
},
// {
// name: "8GB card",
// csd: csdv2Bytes(0x003C01),
// wantCSize: 0x003FFF,
// wantCap: 2 << 40,
// },
{
// Synthetic: C_SIZE = 0x01FFFF -> 131072 * 524288 = 64GiB.
// Exercises C_SIZE bits [17:16], stored in CSD byte 7 (CSD bits [65:64]).
name: "64GiB synthetic",
csd: csdv2Bytes(0x01FFFF),
wantCSize: 0x01FFFF,
wantCap: 64 << 30,
},
{
// Synthetic: maximum v2 C_SIZE = 0x3FFFFF -> 4194304 * 524288 = 2TiB,
// the SDXC upper bound per section 5.3.3.
name: "2TiB synthetic (max C_SIZE)",
csd: csdv2Bytes(0x3FFFFF),
wantCSize: 0x3FFFFF,
wantCap: 2 << 40,
},
}
for _, tt := range tests {
csd, err := DecodeCSD(tt.csd)
if err != nil {
t.Fatalf("%s: DecodeCSD: %v", tt.name, err)
}
v2 := csd.MustV2()
if got := v2.csize(); got != tt.wantCSize {
t.Errorf("%s: csize() = %#x, want %#x", tt.name, got, tt.wantCSize)
}
if got := csd.DeviceCapacity(); got != tt.wantCap {
t.Errorf("%s: DeviceCapacity() = %d, want %d", tt.name, got, tt.wantCap)
}
wantBlocks := tt.wantCap / int64(csd.ReadBlockLen())
if got := csd.NumberOfBlocks(); got != wantBlocks {
t.Errorf("%s: NumberOfBlocks() = %d, want %d", tt.name, got, wantBlocks)
}
}
}
// csdv2Bytes returns a 16-byte CSD v2 register with csize spliced into
// CSD bits [69:48] and a freshly computed CRC7+always1 last byte.
// Non-capacity fields are copied from the 8GB-card vector above.
func csdv2Bytes(csize uint32) []byte {
b := []byte{64, 14, 0, 50, 83, 89, 0, 0, 60, 1, 127, 128, 10, 64, 0}
b[7] = byte(csize >> 16 & 0x3F)
b[8] = byte(csize >> 8)
b[9] = byte(csize)
return append(b, crc7noshift(b)|1)
}
func putCmd(dst []byte, cmd command, arg uint32) {
dst[0] = byte(cmd) | (1 << 6)
dst[1] = byte(arg >> 24)
dst[2] = byte(arg >> 16)
dst[3] = byte(arg >> 8)
dst[4] = byte(arg)
dst[5] = crc7noshift(dst[:5]) | 1 // Stop bit added.
}
+585
View File
@@ -0,0 +1,585 @@
package sd
import (
"bytes"
"encoding/binary"
"io"
"strconv"
"time"
)
// For reference of CID/CSD structs see:
// See https://github.com/arduino-libraries/SD/blob/1c56f58252553c7537f7baf62798cacc625aa543/src/utility/SdInfo.h#L110
// CardKind classifies an SD card by its capacity class and specification
// version, as discovered during card initialization.
type CardKind uint8
// isTimeout reports whether err is one of the package's timeout errors.
func isTimeout(err error) bool {
return err == errReadTimeout || err == errWriteTimeout || err == errBusyTimeout
}
const (
// card types
TypeSD1 CardKind = 1 // Standard capacity V1 SD card
TypeSD2 CardKind = 2 // Standard capacity V2 SD card
TypeSDHC CardKind = 3 // High Capacity SD card
)
// CID is the Card Identification register, a 128-bit (16-byte) read-only
// register holding the card's identification information: manufacturer,
// product name, serial number and manufacturing date, among other data.
// It is programmed during card manufacture and cannot be changed.
type CID struct {
data [16]byte
}
// DecodeCID decodes a CID from the first 16 bytes of b. It returns an error
// if b is too short or if the CRC7/always-1 fields are invalid.
func DecodeCID(b []byte) (cid CID, _ error) {
if len(b) < 16 {
return CID{}, io.ErrShortBuffer
}
copy(cid.data[:], b)
if !cid.IsValid() {
return cid, errBadCSDCID
}
return cid, nil
}
// RawCopy returns a copy of the raw CID data.
func (c *CID) RawCopy() [16]byte { return c.data }
// ManufacturerID is an 8-bit binary number that identifies the card manufacturer. The MID number is controlled, defined, and allocated to a SD Memory Card manufacturer by the SD-3C, LLC.
func (c *CID) ManufacturerID() uint8 { return c.data[0] }
// OEMApplicationID A 2-character ASCII string that identifies the card OEM and/or the card contents (when used as a
// distribution media either on ROM or FLASH cards). The OID number is controlled, defined, and allocated
// to a SD Memory Card manufacturer by the SD-3C, LLC
func (c *CID) OEMApplicationID() uint16 {
return binary.BigEndian.Uint16(c.data[1:3])
}
// ProductName returns the product name, an ASCII string of up to 5 characters.
func (c *CID) ProductName() string {
return string(upToNull(c.data[3:8]))
}
// ProductRevision is composed of two Binary Coded Decimal (BCD) digits, four bits each, representing
// an "n.m" revision number. The "n" is the most significant nibble and "m" is the least significant nibble.
// As an example, the PRV binary value field for product revision "6.2" will be: 0110 0010b
func (c *CID) ProductRevision() (n, m uint8) {
rev := c.data[8]
return rev >> 4, rev & 0x0F
}
// ProductSerialNumber returns the product serial number, a 32-bit binary number.
func (c *CID) ProductSerialNumber() uint32 {
return binary.BigEndian.Uint32(c.data[9:13])
}
// ManufacturingDate returns the manufacturing date of the card,
// e.g. year=2023, month=4 for April 2023.
func (c *CID) ManufacturingDate() (year uint16, month uint8) {
date := binary.BigEndian.Uint16(c.data[13:15])
return (date >> 4) + 2000, uint8(date & 0x0F)
}
// CRC7 returns the CRC7 checksum for this CID. May be invalid. Use [IsValid] to check validity of CRC7+Always1 fields.
func (c *CID) CRC7() uint8 { return c.data[15] >> 1 }
// Always1 checks the presence of the Always 1 bit. Should return true for valid CIDs.
func (c *CID) Always1() bool { return c.data[15]&1 != 0 }
// IsValid checks if the CRC and always1 fields are expected values.
func (c *CID) IsValid() bool { return c.Always1() && CRC7(c.data[:15]) == c.CRC7() }
// CSD is the Card Specific Data register, a 128-bit (16-byte) register that defines how
// the SD card standard communicates with the memory field or register. This type is
// shared among V1 and V2 type devices.
type CSD struct {
data [16]byte
}
// CSDv1 is the Card Specific Data register for V1 devices. See [CSD] for more info.
type CSDv1 struct {
CSD
}
// CSDv2 is the Card Specific Data register for V2 devices. See [CSD] for more info.
type CSDv2 struct {
CSD
}
// DecodeCSD decodes the CSD from a 16-byte slice.
func DecodeCSD(b []byte) (CSD, error) {
if len(b) < 16 {
return CSD{}, io.ErrShortBuffer
}
csd := CSD{}
copy(csd.data[:], b)
if !csd.IsValid() {
return csd, errBadCSDCID
}
return csd, nil
}
// csdStructure returns the version of the CSD structure.
func (c *CSD) csdStructure() uint8 { return c.data[0] >> 6 }
// Version returns the version of the CSD structure. Effectively returns 1+CSDStructure.
func (c *CSD) Version() uint8 { return 1 + c.csdStructure() }
// MustV1 returns the CSD as a CSDv1. Panics if the CSD is not version 1.0.
func (c CSD) MustV1() CSDv1 {
if c.csdStructure() != 0 {
panic("CSD is not version 1.0")
}
return CSDv1{CSD: c}
}
// MustV2 returns the CSD as a CSDv2. Panics if the CSD is not version 2.0.
func (c CSD) MustV2() CSDv2 {
if c.csdStructure() != 1 {
panic("CSD is not version 2.0")
}
return CSDv2{CSD: c}
}
// RawCopy returns a copy of the raw CSD data.
func (c *CSD) RawCopy() [16]byte { return c.data }
// TAAC returns the Time Access Attribute Class (data read access-time-1).
func (c *CSD) TAAC() TAAC { return TAAC(c.data[1]) }
// NSAC returns the Data Read Access-time 2 in CLK cycles (NSAC*100).
func (c *CSD) NSAC() NSAC { return NSAC(c.data[2]) }
// TransferSpeed returns the Max Data Transfer Rate. Either 0x32 or 0x5A.
func (c *CSD) TransferSpeed() TransferSpeed { return TransferSpeed(c.data[3]) }
// CommandClasses returns the supported Card Command Classes as a bitfield;
// bit position i set means command class i is supported by the card.
func (c *CSD) CommandClasses() CommandClasses {
return CommandClasses(uint16(c.data[4])<<4 | uint16(c.data[5]&0xf0)>>4)
}
// ReadBlockLen returns the Max Read Data Block Length in bytes.
func (c *CSD) ReadBlockLen() int { return 1 << c.ReadBlockLenShift() }
// ReadBlockLenShift returns the base-2 logarithm of [CSD.ReadBlockLen] (READ_BL_LEN field).
func (c *CSD) ReadBlockLenShift() uint8 { return c.data[5] & 0x0F }
// AllowsReadBlockPartial indicates that partial block reads (down to a
// single byte) are allowed. Always true for SD cards.
func (c *CSD) AllowsReadBlockPartial() bool { return c.data[6]&(1<<7) != 0 }
// AllowsWriteBlockMisalignment defines if the data block to be written by one command
// can be spread over more than one physical block of the memory device.
func (c *CSD) AllowsWriteBlockMisalignment() bool { return c.data[6]&(1<<6) != 0 }
// AllowsReadBlockMisalignment defines if the data block to be read by one command
// can be spread over more than one physical block of the memory device.
func (c *CSD) AllowsReadBlockMisalignment() bool { return c.data[6]&(1<<5) != 0 }
// CRC7 returns the CRC read for this CSD. May be invalid. Use [IsValid] to check validity of CRC7+Always1 fields.
func (c *CSD) CRC7() uint8 { return c.data[15] >> 1 }
// Always1 checks the Always 1 bit. Should always evaluate to true for valid CSDs.
func (c *CSD) Always1() bool { return c.data[15]&1 != 0 }
// IsValid checks if the CRC and always1 fields are expected values.
func (c *CSD) IsValid() bool {
// Compare last byte with CRC and also the always1 bit.
return c.Always1() && CRC7(c.data[:15]) == c.CRC7()
}
// ImplementsDSR defines if the configurable driver stage is integrated on the card.
func (c *CSD) ImplementsDSR() bool { return c.data[6]&(1<<4) != 0 }
// EraseSectorSizeInBytes returns how much memory is erased by a single
// erase command, in bytes (SectorSize multiplied by the write block length).
func (c *CSDv1) EraseSectorSizeInBytes() int64 {
blklen := c.WriteBlockLen()
numblocks := c.SectorSize()
return int64(numblocks) * blklen
}
// SectorSize returns the size of an erasable sector in units of write blocks
// (SECTOR_SIZE field, range 1..128). Its meaning varies with the CSD version:
// for V1 it is the erase unit when [CSD.EraseBlockEnabled] is false; for V2
// it is fixed to 64KiB and does not reflect the real erase unit.
func (c *CSD) SectorSize() uint8 {
return 1 + ((c.data[10]&0b11_1111)<<1 | (c.data[11] >> 7))
}
// EraseBlockEnabled defines granularity of unit size of data to be erased.
// If enabled the erase operation can erase either one or multiple units of 512 bytes.
func (c *CSD) EraseBlockEnabled() bool { return (c.data[10]>>6)&1 != 0 }
// ReadToWriteFactor returns the typical write time as a power-of-2 multiple of
// the read access time (R2W_FACTOR field), i.e. writeTime = readTime << factor.
func (c *CSD) ReadToWriteFactor() uint8 { return (c.data[12] >> 2) & 0b111 }
// WriteProtectGroupSizeInSectors indicates the size of a write protected
// group in multiple of erasable sectors.
func (c *CSD) WriteProtectGroupSizeInSectors() uint8 {
return 1 + (c.data[11] & 0b111_1111)
}
// WriteBlockLen represents maximum write data block length in bytes.
func (c *CSD) WriteBlockLen() int64 {
return 1 << ((c.data[12]&0b11)<<2 | (c.data[13] >> 6))
}
// WriteGroupEnabled indicates if write group protection is available.
func (c *CSD) WriteGroupEnabled() bool { return c.data[12]&(1<<7) != 0 }
// AllowsWritePartial Defines whether partial block sizes can be used in write block sizes.
func (c *CSD) AllowsWritePartial() bool { return c.data[13]&(1<<5) != 0 }
// FileFormat returns the file format on the card. This field is read-only for ROM.
func (c *CSD) FileFormat() FileFormat { return FileFormat(c.data[14]>>2) & 0b11 }
// TmpWriteProtected indicates temporary protection over the entire card content from being overwritten or erased.
func (c *CSD) TmpWriteProtected() bool { return c.data[14]&(1<<4) != 0 }
// PermWriteProtected indicates permanent protecttion of entire card content against overwriting or erasing (write+erase permanently disabled).
func (c *CSD) PermWriteProtected() bool { return c.data[14]&(1<<5) != 0 }
// IsCopy whether contents are original or have been copied.
func (c *CSD) IsCopy() bool { return c.data[14]&(1<<6) != 0 }
// FileFormatGroup returns the file format group bit, which selects between
// the two [FileFormat] tables. Interpret together with [CSD.FileFormat].
func (c *CSD) FileFormatGroup() bool { return c.data[14]&(1<<7) != 0 }
// DeviceCapacity returns the total device capacity in bytes, dispatching on
// the CSD version. Returns 0 for unknown CSD versions.
func (c *CSD) DeviceCapacity() (size int64) {
switch c.csdStructure() {
case 0:
v1 := c.MustV1()
size = int64(v1.DeviceCapacity())
case 1:
v2 := c.MustV2()
size = v2.DeviceCapacity()
}
return size
}
// NumberOfBlocks returns amount of readable blocks in the device given by Capacity/ReadBlockLength.
func (c *CSD) NumberOfBlocks() (numBlocks int64) {
rblocks := c.ReadBlockLen()
if rblocks == 0 {
return 0
}
return c.DeviceCapacity() / int64(rblocks)
}
// After byte 5 CSDv1 and CSDv2 differ in structure at some fields.
// DeviceCapacity returns the device capacity in bytes:
// (C_SIZE+1) * 512KiB, as per section 5.3.3 of the SD Simplified Specification.
func (c *CSDv2) DeviceCapacity() int64 {
csize := c.csize()
return (int64(csize) + 1) * (512 * 1024)
}
// csize returns the 22-bit C_SIZE field (CSD bits 69:48).
func (c *CSDv2) csize() uint32 {
return uint32(c.data[7]&0x3F)<<16 | uint32(c.data[8])<<8 | uint32(c.data[9])
}
// DeviceCapacity returns the total memory capacity of the SDCard in bytes. Max is 2GB for V1.
func (c *CSDv1) DeviceCapacity() uint32 {
mult := c.mult()
csize := c.csize()
blklen := c.ReadBlockLen()
blockNR := uint32(csize+1) * uint32(mult)
return blockNR * uint32(blklen)
}
func (c *CSDv1) csize() uint16 {
// Jesus, why did SD make this so complicated?
return uint16(c.data[8]>>6) | uint16(c.data[7])<<2 | uint16(c.data[6]&0b11)<<10
}
// mult is a factor for computing total device size with csize and csizemult.
func (c *CSDv1) mult() uint16 { return 1 << (2 + c.csizemult()) }
func (c *CSDv1) csizemult() uint8 {
return (c.data[9]&0b11)<<1 | (c.data[10] >> 7)
}
// VddReadCurrent indicates min and max values for read power supply currents.
// - values min: 0=0.5mA; 1=1mA; 2=5mA; 3=10mA; 4=25mA; 5=35mA; 6=60mA; 7=100mA
// - values max: 0=1mA; 1=5mA; 2=10mA; 3=25mA; 4=35mA; 5=45mA; 6=80mA; 7=200mA
func (c *CSDv1) VddReadCurrent() (min, max uint8) {
return (c.data[8] >> 3) & 0b111, c.data[8] & 0b111
}
// VddWriteCurrent indicates min and max values for write power supply currents.
// - values min: 0=0.5mA; 1=1mA; 2=5mA; 3=10mA; 4=25mA; 5=35mA; 6=60mA; 7=100mA
// - values max: 0=1mA; 1=5mA; 2=10mA; 3=25mA; 4=35mA; 5=45mA; 6=80mA; 7=200mA
func (c *CSDv1) VddWriteCurrent() (min, max uint8) {
return c.data[9] >> 5, (c.data[9] >> 3) & 0b111
}
// String returns a human-readable multi-line summary of the CSD fields.
func (c *CSD) String() string {
version := c.csdStructure() + 1
if version > 2 {
return "<unsupported CSD version>"
}
const delim = '\n'
buf := make([]byte, 0, 64)
buf = c.appendf(buf, delim)
return string(buf)
}
func (c *CSDv1) String() string { return c.CSD.String() }
func (c *CSDv2) String() string { return c.CSD.String() }
func (c *CSD) appendf(b []byte, delim byte) []byte {
b = appendnum(b, "Version", uint64(c.Version()), delim)
b = appendnum(b, "Capacity(bytes)", uint64(c.DeviceCapacity()), delim)
b = appendnum(b, "TimeAccess_ns", uint64(c.TAAC().AccessTime()), delim)
b = appendnum(b, "NSAC", uint64(c.NSAC()), delim)
b = appendnum(b, "Tx_kb/s", uint64(c.TransferSpeed().RateKilobits()), delim)
b = appendnum(b, "CCC", uint64(c.CommandClasses()), delim)
b = appendnum(b, "ReadBlockLen", uint64(c.ReadBlockLen()), delim)
b = appendbit(b, "ReadBlockPartial", c.AllowsReadBlockPartial(), delim)
b = appendbit(b, "AllowWriteBlockMisalignment", c.AllowsWriteBlockMisalignment(), delim)
b = appendbit(b, "AllowReadBlockMisalignment", c.AllowsReadBlockMisalignment(), delim)
b = appendbit(b, "ImplementsDSR", c.ImplementsDSR(), delim)
b = appendnum(b, "WProtectNumSectors", uint64(c.WriteProtectGroupSizeInSectors()), delim)
b = appendnum(b, "WriteBlockLen", uint64(c.WriteBlockLen()), delim)
b = appendbit(b, "WGrpEnable", c.WriteGroupEnabled(), delim)
b = appendbit(b, "WPartialAllow", c.AllowsWritePartial(), delim)
b = append(b, "FileFmt:"...)
b = append(b, c.FileFormat().String()...)
b = append(b, delim)
b = appendbit(b, "TmpWriteProtect", c.TmpWriteProtected(), delim)
b = appendbit(b, "PermWriteProtect", c.PermWriteProtected(), delim)
b = appendbit(b, "IsCopy", c.IsCopy(), delim)
b = appendbit(b, "FileFormatGrp", c.FileFormatGroup(), delim)
return b
}
func appendnum(b []byte, label string, n uint64, delim byte) []byte {
b = append(b, label...)
b = append(b, ':')
b = strconv.AppendUint(b, n, 10)
b = append(b, delim)
return b
}
func appendbit(b []byte, label string, n bool, delim byte) []byte {
b = append(b, label...)
b = append(b, ':')
b = append(b, '0'+b2u8(n))
b = append(b, delim)
return b
}
func upToNull(buf []byte) []byte {
nullIdx := bytes.IndexByte(buf, 0)
if nullIdx < 0 {
return buf
}
return buf[:nullIdx]
}
type (
command byte
appcommand byte
)
// SD commands and application commands.
const (
cmdGoIdleState command = 0
cmdSendOpCnd command = 1
cmdAllSendCID command = 2
cmdSendRelativeAddr command = 3
cmdSetDSR command = 4
cmdSwitchFunc command = 6
cmdSelectDeselectCard command = 7
cmdSendIfCond command = 8
cmdSendCSD command = 9
cmdSendCID command = 10
cmdStopTransmission command = 12
cmdSendStatus command = 13
cmdGoInactiveState command = 15
cmdSetBlocklen command = 16
cmdReadSingleBlock command = 17
cmdReadMultipleBlock command = 18
cmdWriteBlock command = 24
cmdWriteMultipleBlock command = 25
cmdProgramCSD command = 27
cmdSetWriteProt command = 28
cmdClrWriteProt command = 29
cmdSendWriteProt command = 30
cmdEraseWrBlkStartAddr command = 32
cmdEraseWrBlkEndAddr command = 33
cmdErase command = 38
cmdLockUnlock command = 42
cmdAppCmd command = 55
cmdGenCmd command = 56
cmdReadOCR command = 58
cmdCRCOnOff command = 59
acmdSET_BUS_WIDTH appcommand = 6
acmdSD_STATUS appcommand = 13
acmdSEND_NUM_WR_BLOCKS appcommand = 22
acmdSET_WR_BLK_ERASE_COUNT appcommand = 23
acmdSD_APP_OP_COND appcommand = 41
acmdSET_CLR_CARD_DETECT appcommand = 42
acmdSEND_SCR appcommand = 51
acmdSECURE_READ_MULTI_BLOCK appcommand = 18
acmdSECURE_WRITE_MULTI_BLOCK appcommand = 25
acmdSECURE_WRITE_MKB appcommand = 26
acmdSECURE_ERASE appcommand = 38
acmdGET_MKB appcommand = 43
acmdGET_MID appcommand = 44
acmdSET_CER_RN1 appcommand = 45
acmdSET_CER_RN2 appcommand = 46
acmdSET_CER_RES2 appcommand = 47
acmdSET_CER_RES1 appcommand = 48
acmdCHANGE_SECURE_AREA appcommand = 49
)
// CSD field types.
type (
// TransferSpeed is the TRAN_SPEED CSD field: the maximum data transfer
// rate encoded as a rate unit (lower 3 bits) and time value multiplier.
TransferSpeed uint8
// TAAC is the data read access time CSD field, encoded as a time unit
// (lower 3 bits) and time value multiplier.
TAAC uint8
// FileFormat is the format of the data stored on the card. See the
// FileFmt* constants for possible values.
FileFormat uint8
// CommandClasses is the CCC CSD field, a bitfield where bit position i
// set means command class i is supported.
CommandClasses uint16
// NSAC is the data read access time 2 CSD field, given in units of
// 100 clock cycles.
NSAC uint8
)
const (
FileFmtPartition FileFormat = iota // Hard disk like file system with partition table.
FileFmtDOSFAT // DOS FAT (floppy like)
FileFmtUFF // Universal File Format
FileFmtUnknown
)
// String returns a human-readable name for the file format.
func (ff FileFormat) String() (s string) {
switch ff {
case FileFmtPartition:
s = "partition"
case FileFmtDOSFAT:
s = "DOS/FAT"
case FileFmtUFF:
s = "UFF"
case FileFmtUnknown:
s = "unknown"
default:
s = "<invalid format>"
}
return s
}
var log10table = [...]int64{
1,
10,
100,
1000,
10000,
100000,
1000000,
}
// RateKilobits returns the transfer rate in kilobits per second.
func (t TransferSpeed) RateKilobits() int64 {
return 100 * log10table[t&0b111]
}
// AccessTime returns the asynchronous part of the data access time.
func (t TAAC) AccessTime() (d time.Duration) {
return time.Duration(log10table[t&0b111]) * time.Nanosecond
}
func b2u8(b bool) uint8 {
if b {
return 1
}
return 0
}
// CRC16 computes the CRC16 checksum for a given payload using the CRC-16-CCITT polynomial.
func CRC16(buf []byte) (crc uint16) {
const poly uint16 = 0x1021 // Generator polynomial G(x) = x^16 + x^12 + x^5 + 1
for _, b := range buf {
crc ^= (uint16(b) << 8) // Shift byte into MSB of crc
for i := 0; i < 8; i++ { // Process each bit
if crc&0x8000 != 0 {
crc = (crc << 1) ^ poly
} else {
crc <<= 1
}
}
}
return crc
}
// CRC7 computes the CRC7 checksum for a given payload using the polynomial x^7 + x^3 + 1.
func CRC7(data []byte) (crc uint8) {
return crc7noshift(data) >> 1
}
func crc7noshift(data []byte) (crc uint8) {
for _, b := range data {
crc = crc7_table[crc^b]
}
return crc
}
var crc7_table = [256]byte{
0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e,
0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee,
0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c,
0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc,
0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a,
0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a,
0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28,
0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8,
0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6,
0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26,
0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84,
0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14,
0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2,
0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42,
0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0,
0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70,
0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc,
0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c,
0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce,
0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e,
0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98,
0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08,
0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa,
0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a,
0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34,
0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4,
0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06,
0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96,
0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50,
0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0,
0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62,
0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2,
}
+8
View File
@@ -0,0 +1,8 @@
// Package sd implements SD card drivers and the SD card specification:
// CID and CSD register decoding, command definitions and CRC7/CRC16 checksums.
//
// The [SPICard] type drives an SD card over a SPI bus and implements the
// [Card] interface, which exposes block-aligned I/O. [BlockDevice] wraps any
// [Card] with byte-addressed [io.ReaderAt]/[io.WriterAt] implementations
// suitable for filesystem libraries such as tinyfs.
package sd
+325
View File
@@ -0,0 +1,325 @@
package sd
import (
"encoding/binary"
)
const (
_CMD_TIMEOUT = 100
_R1_IDLE_STATE = 1 << 0
_R1_ERASE_RESET = 1 << 1
_R1_ILLEGAL_COMMAND = 1 << 2
_R1_COM_CRC_ERROR = 1 << 3
_R1_ERASE_SEQUENCE_ERROR = 1 << 4
_R1_ADDRESS_ERROR = 1 << 5
_R1_PARAMETER_ERROR = 1 << 6
_DATA_RES_MASK = 0x1F
_DATA_RES_ACCEPTED = 0x05
)
// response1 is the R1 response token returned by the card in SPI mode
// after every command; a bitfield of error and idle-state flags.
type response1 uint8
func (r response1) IsIdle() bool { return r&_R1_IDLE_STATE != 0 }
func (r response1) IllegalCmdError() bool { return r&_R1_ILLEGAL_COMMAND != 0 }
func (r response1) CRCError() bool { return r&_R1_COM_CRC_ERROR != 0 }
func (r response1) EraseReset() bool { return r&_R1_ERASE_RESET != 0 }
func (r response1) EraseSeqError() bool { return r&_R1_ERASE_SEQUENCE_ERROR != 0 }
func (r response1) AddressError() bool { return r&_R1_ADDRESS_ERROR != 0 }
func (r response1) ParamError() bool { return r&_R1_PARAMETER_ERROR != 0 }
// response1Err wraps a non-zero response1 status as an error.
type response1Err struct {
context string
status response1
}
func (e response1Err) Error() string {
if e.context != "" {
return "sd:" + e.context + " " + e.status.Response()
}
return e.status.Response()
}
func (e response1) Response() string {
b := make([]byte, 0, 8)
return string(e.appendf(b))
}
func (r response1) appendf(b []byte) []byte {
b = append(b, '[')
if r.IsIdle() {
b = append(b, "idle,"...)
}
if r.EraseReset() {
b = append(b, "erase-rst,"...)
}
if r.EraseSeqError() {
b = append(b, "erase-seq,"...)
}
if r.CRCError() {
b = append(b, "crc-err,"...)
}
if r.AddressError() {
b = append(b, "addr-err,"...)
}
if r.ParamError() {
b = append(b, "param-err,"...)
}
if r.IllegalCmdError() {
b = append(b, "illegal-cmd,"...)
}
if len(b) > 1 {
b = b[:len(b)-1]
}
b = append(b, ']')
return b
}
func makeResponseError(status response1) error {
return response1Err{
status: status,
}
}
// Commands used to help generate this file:
// - stringer -type=state -trimprefix=state -output=state_string.go
// - stringer -type=status -trimprefix=status -output=status_string.go
// Tokens that are sent by card during polling.
// https://github.com/arduino-libraries/SD/blob/master/src/utility/SdInfo.h
const (
tokSTART_BLOCK = 0xfe
tokSTOP_TRAN = 0xfd
tokWRITE_MULT = 0xfc
)
// state is the card state machine state as encoded in the
// CURRENT_STATE bits of the Card Status register (section 4.10.1).
type state uint8
const (
stateIdle state = iota
stateReady
stateIdent
stateStby
stateTran
stateData
stateRcv
statePrg
stateDis
)
// status represents the Card Status Register (R1), as per section 4.10.1.
type status uint32
func (s status) state() state {
return state(s >> 9 & 0xf)
}
// First status bits.
const (
statusRsvd0 status = iota
statusRsvd1
statusRsvd2
statusAuthSeqError
statusRsvdSDIO
statusAppCmd
statusFXEvent
statusRsvd7
statusReadyForData
)
// Upper bound status bits.
const (
statusEraseReset status = iota + 13
statusECCDisabled
statusWPEraseSkip
statusCSDOverwrite
_
_
statusGenericError
statusControllerError // internal card controller error
statusECCFailed
statusIllegalCommand
statusComCRCError // CRC check of previous command failed
statusLockUnlockFailed
statusCardIsLocked // Signals that the card is locked by the host.
statusWPViolation // Write protected violation
statusEraseParamError // invalid write block selection for erase
statusEraseSeqError // error in erase sequence
statusBlockLenError // tx block length not allowed
statusAddrError // misaligned address
statusAddrOutOfRange // address out of range
)
// r1 is the normal 48-bit response to a command in SD-bus mode,
// as per section 4.9.1. It carries the 32-bit Card Status register.
type r1 struct {
data [48 / 8]byte // 48 bits of response.
}
func (r *r1) RawCopy() [6]byte { return r.data }
func (r *r1) startbit() bool {
return r.data[0]&(1<<7) != 0
}
func (r *r1) txbit() bool {
return r.data[0]&(1<<6) != 0
}
func (r *r1) cmdidx() uint8 {
return r.data[0] & 0b11_1111
}
func (r *r1) cardstatus() status {
return status(binary.BigEndian.Uint32(r.data[1:5]))
}
func (r *r1) CRC7() uint8 { return r.data[5] >> 1 }
func (r *r1) endbit() bool { return r.data[5]&1 != 0 }
func (r *r1) IsValid() bool {
return r.endbit() && CRC7(r.data[:5]) == r.CRC7()
}
// r6 is the 48-bit Published RCA response, as per section 4.9.5. It carries
// the card's new Relative Card Address and a subset of the Card Status bits.
type r6 struct {
data [48 / 8]byte
}
func (r *r6) RawCopy() [6]byte { return r.data }
func (r *r6) startbit() bool {
return r.data[0]&(1<<7) != 0
}
func (r *r6) txbit() bool {
return r.data[0]&(1<<6) != 0
}
func (r *r6) cmdidx() uint8 {
return r.data[0] & 0b11_1111
}
func (r *r6) rca() uint16 {
return binary.BigEndian.Uint16(r.data[1:3])
}
func (r *r6) CardStatus() status {
moveBit := func(b status, from, to uint) status {
return (b & (1 << from)) >> from << to
}
// See 4.9.5 R6 (Published RCA response) of the SD Simplified Specification.
s := status(binary.BigEndian.Uint16(r.data[1:5]))
s = moveBit(s, 13, 19)
s = moveBit(s, 14, 22)
s = moveBit(s, 15, 23)
return s
}
func (r *r6) CRC7() uint8 { return r.data[5] >> 1 }
func (r *r6) endbit() bool { return r.data[5]&1 != 0 }
func (r *r6) IsValid() bool {
return r.endbit() && CRC7(r.data[:5]) == r.CRC7()
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[statusRsvd0-0]
_ = x[statusRsvd1-1]
_ = x[statusRsvd2-2]
_ = x[statusAuthSeqError-3]
_ = x[statusRsvdSDIO-4]
_ = x[statusAppCmd-5]
_ = x[statusFXEvent-6]
_ = x[statusRsvd7-7]
_ = x[statusReadyForData-8]
_ = x[statusEraseReset-13]
_ = x[statusECCDisabled-14]
_ = x[statusWPEraseSkip-15]
_ = x[statusCSDOverwrite-16]
_ = x[statusGenericError-19]
_ = x[statusControllerError-20]
_ = x[statusECCFailed-21]
_ = x[statusIllegalCommand-22]
_ = x[statusComCRCError-23]
_ = x[statusLockUnlockFailed-24]
_ = x[statusCardIsLocked-25]
_ = x[statusWPViolation-26]
_ = x[statusEraseParamError-27]
_ = x[statusEraseSeqError-28]
_ = x[statusBlockLenError-29]
_ = x[statusAddrError-30]
_ = x[statusAddrOutOfRange-31]
}
const (
_status_name_0 = "Rsvd0Rsvd1Rsvd2AuthSeqErrorRsvdSDIOAppCmdFXEventRsvd7ReadyForData"
_status_name_1 = "EraseResetECCDisabledWPEraseSkipCSDOverwrite"
_status_name_2 = "GenericErrorControllerErrorECCFailedIllegalCommandComCRCErrorLockUnlockFailedCardIsLockedWPViolationEraseParamErrorEraseSeqErrorBlockLenErrorAddrErrorAddrOutOfRange"
)
var (
_status_index_0 = [...]uint8{0, 5, 10, 15, 27, 35, 41, 48, 53, 65}
_status_index_1 = [...]uint8{0, 10, 21, 32, 44}
_status_index_2 = [...]uint8{0, 12, 27, 36, 50, 61, 77, 89, 100, 115, 128, 141, 150, 164}
)
func (i status) string() string {
switch {
case i <= 8:
return _status_name_0[_status_index_0[i]:_status_index_0[i+1]]
case 13 <= i && i <= 16:
i -= 13
return _status_name_1[_status_index_1[i]:_status_index_1[i+1]]
case 19 <= i && i <= 31:
i -= 19
return _status_name_2[_status_index_2[i]:_status_index_2[i+1]]
default:
return ""
}
}
func (s status) String() string {
return string(s.appendf(nil, ','))
}
func (s status) appendf(b []byte, delim byte) []byte {
b = append(b, s.state().String()...)
b = append(b, '[')
if s == 0 {
return append(b, ']')
}
for bit := 0; bit < 32; bit++ {
if s&(1<<bit) != 0 {
b = append(b, status(bit).string()...)
b = append(b, delim)
}
}
b = append(b[:len(b)-1], ']')
return b
}
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[stateIdle-0]
_ = x[stateReady-1]
_ = x[stateIdent-2]
_ = x[stateStby-3]
_ = x[stateTran-4]
_ = x[stateData-5]
_ = x[stateRcv-6]
_ = x[statePrg-7]
_ = x[stateDis-8]
}
const _state_name = "IdleReadyIdentStbyTranDataRcvPrgDis"
var _state_index = [...]uint8{0, 4, 9, 14, 18, 22, 26, 29, 32, 35}
func (i state) String() string {
if i >= state(len(_state_index)-1) {
return "<reserved state>"
}
return _state_name[_state_index[i]:_state_index[i+1]]
}
+558
View File
@@ -0,0 +1,558 @@
package sd
import (
"encoding/binary"
"errors"
"io"
"math"
"time"
"tinygo.org/x/drivers"
)
var (
errBadCSDCID = errors.New("sd:bad CSD/CID in CRC or always1")
errNoSDCard = errors.New("sd:no card")
errCardNotSupported = errors.New("sd:card not supported")
errWaitStartBlock = errors.New("sd:did not find start block token")
errNeedBlockLenMultiple = errors.New("sd:need blocksize multiple for I/O")
errWrite = errors.New("sd:write")
errWriteTimeout = errors.New("sd:write timeout")
errReadTimeout = errors.New("sd:read timeout")
errBusyTimeout = errors.New("sd:busy card timeout")
errOOB = errors.New("sd:oob block access")
errNoblocks = errors.New("sd:no readable blocks")
)
// digitalPinout sets the logic level of an output pin; true for high, false for low.
type digitalPinout = func(b bool)
// SPICard is a SPI-mode SD card driver. It implements the [Card] interface
// and is initialized with [NewSPICard] followed by a call to [SPICard.Init].
// SPICard is not safe for concurrent use.
type SPICard struct {
bus drivers.SPI
cs digitalPinout
timers [2]timer
timeout time.Duration
wait time.Duration
// Card Identification Register.
cid CID
// Card Specific Register.
csd CSD
bufcmd [6]byte
kind CardKind
// block indexing helper based on block size.
blk blkIdxer
lastCRC uint16
}
// NewSPICard returns a new [SPICard] that communicates over spi using cs as
// the chip select pin. The returned card must be initialized with [SPICard.Init]
// before use.
func NewSPICard(spi drivers.SPI, cs digitalPinout) *SPICard {
const defaultTimeout = 300 * time.Millisecond
s := &SPICard{
bus: spi,
cs: cs,
}
s.setTimeout(defaultTimeout)
return s
}
// setTimeout sets the timeout for all operations and the wait time between each yield during busy spins.
func (c *SPICard) setTimeout(timeout time.Duration) {
if timeout <= 0 {
panic("timeout must be positive")
}
c.timeout = timeout
c.wait = timeout / 512
}
// LastReadCRC returns the CRC for the last ReadBlock operation.
func (c *SPICard) LastReadCRC() uint16 { return c.lastCRC }
// Init initializes the SD card. This routine should be performed with a SPI clock
// speed of around 100..400kHz. One may increase the clock speed after initialization.
func (d *SPICard) Init() error {
return d.initRs()
}
// NumberOfBlocks returns the number of readable and writable blocks on the card,
// as calculated from the CSD read during [SPICard.Init].
func (d *SPICard) NumberOfBlocks() int64 {
return d.csd.NumberOfBlocks()
}
// CID returns a copy of the Card Identification Register value last read.
func (d *SPICard) CID() CID { return d.cid }
// CSD returns a copy of the Card Specific Data Register value last read.
func (d *SPICard) CSD() CSD { return d.csd }
func (d *SPICard) yield() { time.Sleep(d.wait) }
type timer struct {
deadline time.Time
}
func (t *timer) setTimeout(timeout time.Duration) *timer {
t.deadline = time.Now().Add(timeout)
return t
}
func (t timer) expired() bool {
return time.Since(t.deadline) >= 0
}
// Reference for this implementation:
// https://github.com/embassy-rs/embedded-sdmmc-rs/blob/master/src/sdmmc.rs
// Not used currently. We'd want to switch over to one way of doing things, Rust way.
func (d *SPICard) initRs() error {
// Supply minimum of 74 clock cycles with CS high.
d.csEnable(true)
for i := 0; i < 10; i++ {
d.send(0xff)
}
d.csEnable(false)
for i := 0; i < 512; i++ {
d.receive()
}
d.csEnable(true)
defer d.csEnable(false)
// Enter SPI mode
const maxRetries = 32
retries := maxRetries
tm := d.timers[0].setTimeout(2 * time.Second)
for retries > 0 {
stat, err := d.card_command(cmdGoIdleState, 0) // CMD0.
if err != nil {
if isTimeout(err) {
retries--
continue // Try again!
}
return err
}
if stat == _R1_IDLE_STATE {
break
} else if tm.expired() {
retries = 0
break
}
retries--
}
if retries <= 0 {
return errNoSDCard
}
const enableCRC = true
if enableCRC {
stat, err := d.card_command(cmdCRCOnOff, 1) // CMD59.
if err != nil {
return err
} else if stat != _R1_IDLE_STATE {
return errors.New("sd:cant enable CRC")
}
}
tm.setTimeout(time.Second)
for {
stat, err := d.card_command(cmdSendIfCond, 0x1AA) // CMD8.
if err != nil {
return err
} else if stat == (_R1_ILLEGAL_COMMAND | _R1_IDLE_STATE) {
d.kind = TypeSD1
break
}
d.receive()
d.receive()
d.receive()
status, err := d.receive()
if err != nil {
return err
}
if status == 0xaa {
d.kind = TypeSD2
break
}
d.yield()
}
var arg uint32
if d.kind != TypeSD1 {
arg = 0x4000_0000
}
tm.setTimeout(time.Second)
for !tm.expired() {
stat, err := d.card_acmd(acmdSD_APP_OP_COND, arg)
if err != nil {
return err
} else if stat == 0 { // READY state.
break
}
d.yield()
}
err := d.updateCSDCID()
if err != nil {
return err
}
if d.kind != TypeSD2 {
return nil // Done if not SD2.
}
// Discover if card is high capacity.
stat, err := d.card_command(cmdReadOCR, 0)
if err != nil {
return err
} else if stat != 0 {
return makeResponseError(response1(stat))
}
ocr, err := d.receive()
if err != nil {
return err
} else if ocr&0xc0 == 0xc0 {
d.kind = TypeSDHC
}
// Discard next 3 bytes.
d.receive()
d.receive()
d.receive()
return nil
}
func (d *SPICard) updateCSDCID() (err error) {
// read CID
d.cid, err = d.read_cid()
if err != nil {
return err
}
d.csd, err = d.read_csd()
if err != nil {
return err
}
blklen := d.csd.ReadBlockLen()
d.blk, err = makeBlockIndexer(int(blklen))
if err != nil {
return err
}
return nil
}
// ReadBlocks reads card data into dst beginning at the block index startBlockIdx.
// len(dst) must be a multiple of the card's block size (see [CSD.ReadBlockLen]).
// It returns the number of bytes read into dst and any error encountered.
func (d *SPICard) ReadBlocks(dst []byte, startBlockIdx int64) (int, error) {
numblocks, err := d.checkBounds(startBlockIdx, len(dst))
if err != nil {
return 0, err
}
if d.kind != TypeSDHC {
startBlockIdx <<= 9 // Multiply by 512 for non high capacity SD cards.
}
d.csEnable(true)
defer d.csEnable(false)
if numblocks == 1 {
return d.read_block_single(dst, startBlockIdx)
} else if numblocks > 1 {
// TODO: implement multi block transaction reading.
// Rust code is failing here.
blocksize := int(d.blk.size())
for i := 0; i < numblocks; i++ {
dataoff := i * blocksize
d.csEnable(true)
_, err := d.read_block_single(dst[dataoff:dataoff+blocksize], int64(i)+startBlockIdx)
if err != nil {
return dataoff, err
}
d.csEnable(false)
}
return len(dst), nil
}
panic("unreachable numblocks<=0")
}
// EraseBlocks erases numberOfBlocks blocks beginning at startBlock.
// It always returns an error since erase is not yet implemented for SPICard.
func (d *SPICard) EraseBlocks(startBlock, numberOfBlocks int64) error {
return errors.New("sd:erase not implemented")
}
// WriteBlocks writes data to the card beginning at the block index startBlockIdx.
// len(data) must be a multiple of the card's block size (see [CSD.WriteBlockLen]).
// It returns the number of bytes written and any error encountered.
func (d *SPICard) WriteBlocks(data []byte, startBlockIdx int64) (int, error) {
numblocks, err := d.checkBounds(startBlockIdx, len(data))
if err != nil {
return 0, err
}
if d.kind != TypeSDHC {
startBlockIdx <<= 9 // Multiply by 512 for non high capacity SD cards.
}
d.csEnable(true)
defer d.csEnable(false)
writeTimeout := 2 * d.timeout
if numblocks == 1 {
return d.write_block_single(data, startBlockIdx)
} else if numblocks > 1 {
// Start multi block write.
blocksize := int(d.blk.size())
_, err = d.card_command(cmdWriteMultipleBlock, uint32(startBlockIdx))
if err != nil {
return 0, err
}
for i := 0; i < numblocks; i++ {
offset := i * blocksize
err = d.wait_not_busy(writeTimeout)
if err != nil {
return 0, err
}
err = d.write_data(tokWRITE_MULT, data[offset:offset+blocksize])
if err != nil {
return 0, err
}
}
// Stop the multi write operation.
err = d.wait_not_busy(writeTimeout)
if err != nil {
return 0, err
}
err = d.send(tokSTOP_TRAN)
if err != nil {
return 0, err
}
_, err = d.card_command(cmdStopTransmission, 0)
if err != nil {
return 0, err
}
return len(data), nil
}
panic("unreachable numblocks<=0")
}
func (d *SPICard) read_block_single(dst []byte, startBlockIdx int64) (int, error) {
_, err := d.card_command(cmdReadSingleBlock, uint32(startBlockIdx))
if err != nil {
return 0, err
}
err = d.read_data(dst)
if err != nil {
return 0, err
}
return len(dst), nil
}
func (d *SPICard) write_block_single(data []byte, startBlockIdx int64) (_ int, err error) {
_, err = d.card_command(cmdWriteBlock, uint32(startBlockIdx))
if err != nil {
return 0, err
}
err = d.write_data(tokSTART_BLOCK, data)
if err != nil {
return 0, err
}
err = d.wait_not_busy(2 * d.timeout)
if err != nil {
return 0, err
}
status, err := d.card_command(cmdSendStatus, 0)
if err != nil {
return 0, err
} else if status != 0 {
return 0, makeResponseError(response1(status))
}
status, err = d.receive()
if err != nil {
return 0, err
} else if status != 0 {
return 0, errWrite
}
return len(data), nil
}
func (d *SPICard) checkBounds(startBlockIdx int64, datalen int) (numblocks int, err error) {
if startBlockIdx >= d.NumberOfBlocks() {
return 0, errOOB
} else if startBlockIdx > math.MaxUint32 {
return 0, errCardNotSupported
}
if d.blk.off(int64(datalen)) > 0 {
return 0, errNeedBlockLenMultiple
}
numblocks = int(d.blk.idx(int64(datalen)))
if numblocks == 0 {
return 0, io.ErrShortBuffer
}
return numblocks, nil
}
func (d *SPICard) read_cid() (cid CID, err error) {
err = d.cmd_read(cmdSendCID, 0, d.cid.data[:16]) // CMD10.
if err != nil {
return cid, err
}
if !d.cid.IsValid() {
return cid, errBadCSDCID
}
return d.cid, nil
}
func (d *SPICard) read_csd() (csd CSD, err error) {
err = d.cmd_read(cmdSendCSD, 0, d.csd.data[:16]) // CMD9.
if err != nil {
return csd, err
}
if !d.csd.IsValid() {
return csd, errBadCSDCID
}
return d.csd, nil
}
func (d *SPICard) cmd_read(cmd command, args uint32, buf []byte) error {
status, err := d.card_command(cmd, args)
if err != nil {
return err
} else if status != 0 {
return makeResponseError(response1(status))
}
return d.read_data(buf)
}
func (d *SPICard) card_acmd(acmd appcommand, args uint32) (uint8, error) {
_, err := d.card_command(cmdAppCmd, 0)
if err != nil {
return 0, err
}
return d.card_command(command(acmd), args)
}
func (d *SPICard) card_command(cmd command, args uint32) (uint8, error) {
const transmitterBit = 1 << 6
err := d.wait_not_busy(d.timeout)
if err != nil {
return 0, err
}
buf := d.bufcmd[:6]
// Start bit is always zero; transmitter bit is one since we are Host.
buf[0] = transmitterBit | byte(cmd)
binary.BigEndian.PutUint32(buf[1:5], args)
buf[5] = crc7noshift(buf[:5]) | 1 // CRC and end bit which is always 1.
err = d.bus.Tx(buf, nil)
if err != nil {
return 0, err
}
if cmd == cmdStopTransmission {
d.receive() // skip stuff byte for stop read.
}
for i := 0; i < 512; i++ {
result, err := d.receive()
if err != nil {
return 0, err
}
if result&0x80 == 0 {
return result, nil
}
}
return 0, errReadTimeout
}
func (d *SPICard) read_data(data []byte) (err error) {
var status uint8
tm := d.timers[1].setTimeout(d.timeout)
for !tm.expired() {
status, err = d.receive()
if err != nil {
return err
} else if status != 0xff {
break
} else if tm.expired() {
return errReadTimeout
}
d.yield()
}
if status != tokSTART_BLOCK {
return errWaitStartBlock
}
err = d.bus.Tx(nil, data)
if err != nil {
return err
}
// CRC16 is always sent on a data block.
crchi, _ := d.receive()
crclo, _ := d.receive()
d.lastCRC = uint16(crclo) | uint16(crchi)<<8
return nil
}
func (s *SPICard) wait_not_busy(timeout time.Duration) error {
tm := s.timers[1].setTimeout(timeout)
for {
tok, err := s.receive()
if err != nil {
return err
} else if tok == 0xff {
break
} else if tm.expired() {
return errBusyTimeout
}
s.yield()
}
return nil
}
func (s *SPICard) write_data(tok byte, data []byte) error {
if len(data) > 512 {
return errors.New("data too long for write_data")
}
crc := CRC16(data)
err := s.send(tok)
if err != nil {
return err
}
err = s.bus.Tx(data, nil)
if err != nil {
return err
}
err = s.send(byte(crc >> 8))
if err != nil {
return err
}
err = s.send(byte(crc))
if err != nil {
return err
}
status, err := s.receive()
if err != nil {
return err
}
if status&_DATA_RES_MASK != _DATA_RES_ACCEPTED {
return makeResponseError(response1(status))
}
return nil
}
func (s *SPICard) receive() (byte, error) {
return s.bus.Transfer(0xFF)
}
func (s *SPICard) send(b byte) error {
_, err := s.bus.Transfer(b)
return err
}
func (c *SPICard) csEnable(b bool) {
// SD Card initialization issues with misbehaving SD cards requires clocking the card.
// https://electronics.stackexchange.com/questions/303745/sd-card-initialization-problem-cmd8-wrong-response
c.bus.Transfer(0xff)
c.cs(!b)
c.bus.Transfer(0xff)
}
+2
View File
@@ -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
@@ -150,6 +151,7 @@ tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples
tinygo build -size short -o ./build/test.hex -target=pico ./examples/ens160/main.go
tinygo build -size short -o ./build/test.hex -target=pico ./examples/si5351/main.go
tinygo build -size short -o ./build/test.hex -target=pico ./examples/w5500/main.go
tinygo build -size short -o ./build/test.hex -target=pico ./examples/sd
# network examples (espat)
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
# network examples (wifinina)
+7
View File
@@ -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)
+52
View File
@@ -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)
)
+356
View File
@@ -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)
)
+19
View File
@@ -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")
)
+69
View File
@@ -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)
)
+768
View File
@@ -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 0s and 1s 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
View File
@@ -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)
+381
View File
@@ -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()
+396
View File
@@ -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 -383
View File
@@ -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
View File
@@ -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"
+3
View File
@@ -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
+16
View File
@@ -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
}
}
+4
View File
@@ -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
View File
@@ -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