mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-01 13:37:49 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b6b5668945 | |||
| 24b6f0f296 | |||
| 38ba7772e2 | |||
| 6df6bf4880 | |||
| 8cfe5ebc4a | |||
| f098853465 | |||
| dc953b09b6 | |||
| 59ff8a4585 | |||
| 125d8b6530 | |||
| 1832e40f4d | |||
| 9af4640788 | |||
| 65677a3836 | |||
| f67130b52d | |||
| d94003bd3a | |||
| a421318a4d | |||
| deb22c4a57 | |||
| 67d2af5d45 | |||
| 877342d0ff |
@@ -120,6 +120,7 @@ The following 90 devices are supported.
|
||||
| [Servo](https://learn.sparkfun.com/tutorials/hobby-servo-tutorial/all) | PWM |
|
||||
| [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO |
|
||||
| [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO |
|
||||
| [SH1106 OLED display](https://www.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf) | I2C / SPI |
|
||||
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
|
||||
| [SHTC3 Digital Humidity Sensor (RH/T)](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHTC3_Datasheet.pdf) | I2C |
|
||||
| [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI |
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# AT-CMD implementation of at-lora command set
|
||||
|
||||
```
|
||||
$ tinygo monitor
|
||||
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
|
||||
+AT: OK
|
||||
+VER: 0.0.1 (sx127x v18)
|
||||
```
|
||||
|
||||
# Building
|
||||
|
||||
## Simulator
|
||||
|
||||
```
|
||||
tinygo flash -target pico ./examples/lora/atcmd/
|
||||
```
|
||||
|
||||
## PyBadge with LoRa Featherwing
|
||||
|
||||
```
|
||||
tinygo flash -target pybadge -tags featherwing ./examples/lora/atcmd/
|
||||
```
|
||||
|
||||
## LoRa-E5
|
||||
|
||||
```
|
||||
tinygo flash -target lorae5 ./examples/lora/atcmd/
|
||||
```
|
||||
|
||||
@@ -0,0 +1,410 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Use to test if connection to module is OK.
|
||||
func quicktest() {
|
||||
writeCommandOutput("AT", "OK")
|
||||
}
|
||||
|
||||
// Check firmware version.
|
||||
func version() {
|
||||
writeCommandOutput("VER", currentVersion()+" ("+firmwareVersion()+")")
|
||||
}
|
||||
|
||||
// Use to check the ID of the LoRaWAN module, or change the ID.
|
||||
func id(args string) error {
|
||||
cmd := "ID"
|
||||
|
||||
// look for comma in args
|
||||
param, val, hasComma := strings.Cut(args, ",")
|
||||
if hasComma {
|
||||
// set
|
||||
switch param {
|
||||
case "DevAddr":
|
||||
writeCommandOutput(cmd, "DevAddr, "+val)
|
||||
case "DevEui":
|
||||
writeCommandOutput(cmd, "DevEui, "+val)
|
||||
case "AppEui":
|
||||
writeCommandOutput(cmd, "AppEui, "+val)
|
||||
default:
|
||||
return errInvalidCommand
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// get
|
||||
switch param {
|
||||
case "DevAddr":
|
||||
writeCommandOutput(cmd, "DevAddr, xx:xx:xx:xx")
|
||||
case "DevEui":
|
||||
writeCommandOutput(cmd, "DevEui, xx:xx:xx:xx:xx:xx:xx:xx")
|
||||
case "AppEui":
|
||||
writeCommandOutput(cmd, "AppEui, xx:xx:xx:xx:xx:xx:xx:xx")
|
||||
default:
|
||||
writeCommandOutput(cmd, "DevAddr, xx:xx:xx:xx")
|
||||
writeCommandOutput(cmd, "DevEui, xx:xx:xx:xx:xx:xx:xx:xx")
|
||||
writeCommandOutput(cmd, "AppEui, xx:xx:xx:xx:xx:xx:xx:xx")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use to reset the module. If module returns error, then reset function is invalid.
|
||||
func reset() error {
|
||||
radio.Reset()
|
||||
|
||||
writeCommandOutput("RESET", "OK")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use to send string format frame which is no need to be confirmed by the server.
|
||||
func msg(data string) error {
|
||||
cmd := "MSG"
|
||||
writeCommandOutput(cmd, "Start")
|
||||
|
||||
if err := radio.Tx([]byte(data), defaultTimeout); err != nil {
|
||||
writeCommandOutput(cmd, err.Error())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
writeCommandOutput(cmd, "Done")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use to send string format frame which must be confirmed by the server
|
||||
func cmsg(data string) error {
|
||||
cmd := "CMSG"
|
||||
writeCommandOutput(cmd, "Start")
|
||||
|
||||
if err := radio.Tx([]byte(data), defaultTimeout); err != nil {
|
||||
writeCommandOutput(cmd, err.Error())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: confirmation
|
||||
|
||||
writeCommandOutput(cmd, "Done")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use to send hex format frame which is no need to be confirmed by the server
|
||||
func msghex(data string) error {
|
||||
cmd := "MSGHEX"
|
||||
writeCommandOutput(cmd, "Start")
|
||||
|
||||
writeCommandOutput(cmd, "Done")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use to send hex format frame which must be confirmed by the server.
|
||||
func cmsghex(data string) error {
|
||||
cmd := "CMSGHEX"
|
||||
writeCommandOutput(cmd, "Start")
|
||||
|
||||
writeCommandOutput(cmd, "Done")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use to send string format LoRaWAN proprietary frames
|
||||
func pmsg(data string) error {
|
||||
cmd := "PMSG"
|
||||
writeCommandOutput(cmd, "Start")
|
||||
|
||||
writeCommandOutput(cmd, "Done")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use to send hex format LoRaWAN proprietary frames.
|
||||
func pmsghex(data string) error {
|
||||
cmd := "PMSGHEX"
|
||||
writeCommandOutput(cmd, "Start")
|
||||
|
||||
writeCommandOutput(cmd, "Done")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set PORT number which will be used by MSG/CMSG/MSGHEX/CMSGHEX command to send
|
||||
// message, port number should range from 1 to 255. User should refer to LoRaWAN
|
||||
// specification to choose port.
|
||||
func port(p string) error {
|
||||
cmd := "PMSG"
|
||||
writeCommandOutput(cmd, p)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set ADR function of LoRaWAN module
|
||||
func adr(state string) error {
|
||||
cmd := "ADR"
|
||||
writeCommandOutput(cmd, state)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use LoRaWAN defined DRx to set datarate of LoRaWAN AT modem.
|
||||
func dr(rate string) error {
|
||||
cmd := "DR"
|
||||
writeCommandOutput(cmd, rate)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Channel Configuration
|
||||
func ch(channel string) error {
|
||||
cmd := "CH"
|
||||
writeCommandOutput(cmd, channel)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set and Check Power
|
||||
func power(setting string) error {
|
||||
cmd := "POWER"
|
||||
writeCommandOutput(cmd, setting)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unconfirmed message repeats times.
|
||||
func rept(setting string) error {
|
||||
cmd := "REPT"
|
||||
writeCommandOutput(cmd, setting)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Confirmed message retry times. Valid range 0~254,
|
||||
// if retry times is less than 2, only one message will
|
||||
// be sent. Random delay 3 - 10s between each retry
|
||||
// (band duty cycle limitation has the priority)
|
||||
func retry(setting string) error {
|
||||
cmd := "RETRY"
|
||||
writeCommandOutput(cmd, setting)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rxwin2(setting string) error {
|
||||
cmd := "RXWIN2"
|
||||
writeCommandOutput(cmd, setting)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rxwin1(setting string) error {
|
||||
cmd := "RXWIN1"
|
||||
writeCommandOutput(cmd, setting)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func key(setting string) error {
|
||||
cmd := "KEY"
|
||||
writeCommandOutput(cmd, setting)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fdefault(setting string) error {
|
||||
cmd := "FDEFAULT"
|
||||
writeCommandOutput(cmd, "OK")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mode(setting string) error {
|
||||
cmd := "MODE"
|
||||
writeCommandOutput(cmd, setting)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func join(setting string) error {
|
||||
cmd := "JOIN"
|
||||
writeCommandOutput(cmd, "Starting")
|
||||
|
||||
writeCommandOutput(cmd, "Done")
|
||||
return nil
|
||||
}
|
||||
|
||||
func beacon(setting string) error {
|
||||
cmd := "BEACON"
|
||||
writeCommandOutput(cmd, "Starting")
|
||||
|
||||
writeCommandOutput(cmd, "Done")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func class(setting string) error {
|
||||
cmd := "CLASS"
|
||||
writeCommandOutput(cmd, "Starting")
|
||||
|
||||
writeCommandOutput(cmd, "Done")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func delay(setting string) error {
|
||||
cmd := "DELAY"
|
||||
writeCommandOutput(cmd, setting)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lw(setting string) error {
|
||||
cmd := "LW"
|
||||
writeCommandOutput(cmd, setting)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func wdt(setting string) error {
|
||||
cmd := "WDT"
|
||||
writeCommandOutput(cmd, "Not implemented")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lowpower(setting string) error {
|
||||
cmd := "LOWPOWER"
|
||||
writeCommandOutput(cmd, "Not implemented")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func vdd(setting string) error {
|
||||
cmd := "VDD"
|
||||
writeCommandOutput(cmd, "Not implemented")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func temp(setting string) error {
|
||||
cmd := "TEMP"
|
||||
writeCommandOutput(cmd, "Not implemented")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rtc(setting string) error {
|
||||
cmd := "RTC"
|
||||
writeCommandOutput(cmd, "Not implemented")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func eeprom(setting string) error {
|
||||
cmd := "EEPROM"
|
||||
writeCommandOutput(cmd, "Not implemented")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func uartcmd(setting string) error {
|
||||
cmd := "UART"
|
||||
writeCommandOutput(cmd, "Not implemented")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func test(setting string) error {
|
||||
cmd := "TEST"
|
||||
writeCommandOutput(cmd, "Not implemented")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func log(setting string) error {
|
||||
cmd := "LOG"
|
||||
writeCommandOutput(cmd, "Not implemented")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func send(data string) error {
|
||||
cmd := "SEND"
|
||||
writeCommandOutput(cmd, "Start")
|
||||
|
||||
// remove leading/trailing quotes
|
||||
data = strings.Trim(data, "\"'")
|
||||
|
||||
if err := radio.Tx([]byte(data), defaultTimeout); err != nil {
|
||||
writeCommandOutput(cmd, err.Error())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
writeCommandOutput(cmd, "Done")
|
||||
return nil
|
||||
}
|
||||
|
||||
func sendhex(data string) error {
|
||||
cmd := "SENDHEX"
|
||||
writeCommandOutput(cmd, "Start")
|
||||
|
||||
// remove leading/trailing quotes
|
||||
data = strings.Trim(data, "\"'")
|
||||
|
||||
// convert data from hex formatted string
|
||||
data = strings.ReplaceAll(data, " ", "")
|
||||
payload, err := hex.DecodeString(data)
|
||||
if err != nil {
|
||||
writeCommandOutput(cmd, err.Error())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := radio.Tx(payload, defaultTimeout); err != nil {
|
||||
writeCommandOutput(cmd, err.Error())
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
writeCommandOutput(cmd, "Done")
|
||||
return nil
|
||||
}
|
||||
|
||||
func recv(setting string) error {
|
||||
cmd := "RECV"
|
||||
|
||||
data, err := lorarx()
|
||||
if err != nil {
|
||||
writeCommandOutput(cmd, "ERROR "+err.Error())
|
||||
return err
|
||||
}
|
||||
writeCommandOutput(cmd, string(data))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func recvhex(setting string) error {
|
||||
cmd := "RECVHEX"
|
||||
|
||||
data, err := lorarx()
|
||||
if err != nil {
|
||||
writeCommandOutput(cmd, "ERROR "+err.Error())
|
||||
return err
|
||||
}
|
||||
writeCommandOutput(cmd, string(data))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func crlf() {
|
||||
uart.Write([]byte("\r\n"))
|
||||
}
|
||||
|
||||
func writeCommandOutput(cmd, data string) {
|
||||
uart.Write([]byte("+" + cmd + ": "))
|
||||
uart.Write([]byte(data))
|
||||
crlf()
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// AT command set console running on the device UART to communicate with
|
||||
// an attached LoRa device.
|
||||
//
|
||||
// Computer <-> UART <-> MCU <-> SPI <-> SX126x/SX127x
|
||||
//
|
||||
// Connect using default baudrate for this hardware, 8-N-1 with your terminal program.
|
||||
// For details on the AT command set, see:
|
||||
// https://files.seeedstudio.com/products/317990687/res/LoRa-E5%20AT%20Command%20Specification_V1.0%20.pdf
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// change these to test a different UART or pins if available
|
||||
var (
|
||||
uart = machine.Serial
|
||||
tx = machine.UART_TX_PIN
|
||||
rx = machine.UART_RX_PIN
|
||||
input = make([]byte, 0, 64)
|
||||
|
||||
radio LoraRadio
|
||||
defaultTimeout uint32 = 1000
|
||||
)
|
||||
|
||||
func main() {
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
|
||||
var err error
|
||||
radio, err = setupLora()
|
||||
if err != nil {
|
||||
fail(err.Error())
|
||||
}
|
||||
|
||||
for {
|
||||
if uart.Buffered() > 0 {
|
||||
data, _ := uart.ReadByte()
|
||||
|
||||
switch data {
|
||||
case 13:
|
||||
// return key
|
||||
if err := parse(input); err != nil {
|
||||
uart.Write([]byte("ERROR: "))
|
||||
uart.Write([]byte(err.Error()))
|
||||
crlf()
|
||||
}
|
||||
input = input[:0]
|
||||
default:
|
||||
// just capture the character
|
||||
input = append(input, data)
|
||||
}
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func fail(msg string) {
|
||||
for {
|
||||
uart.Write([]byte(msg))
|
||||
crlf()
|
||||
|
||||
time.Sleep(time.Minute)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidCommand = errors.New("Invalid command")
|
||||
)
|
||||
|
||||
func parse(data []byte) error {
|
||||
switch {
|
||||
case len(data) < 2, string(data[0:2]) != "AT":
|
||||
return errInvalidCommand
|
||||
case len(data) == 2:
|
||||
// just the AT command by itself
|
||||
quicktest()
|
||||
case len(data) < 6 || data[2] != '+':
|
||||
return errInvalidCommand
|
||||
default:
|
||||
// parse the rest of the command
|
||||
cmd, args, _ := strings.Cut(string(data[3:]), "=")
|
||||
return parseCommand(cmd, args)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseCommand(cmd, args string) error {
|
||||
switch cmd {
|
||||
case "VER":
|
||||
version()
|
||||
case "ID":
|
||||
id(args)
|
||||
case "RESET":
|
||||
reset()
|
||||
case "MSG":
|
||||
msg(args)
|
||||
case "CMSG":
|
||||
cmsg(args)
|
||||
case "MSGHEX":
|
||||
msghex(args)
|
||||
case "CMSGHEX":
|
||||
cmsghex(args)
|
||||
case "PMSG":
|
||||
pmsg(args)
|
||||
case "PMSGHEX":
|
||||
pmsg(args)
|
||||
case "PORT":
|
||||
port(args)
|
||||
case "ADR":
|
||||
adr(args)
|
||||
case "DR":
|
||||
dr(args)
|
||||
case "CH":
|
||||
ch(args)
|
||||
case "POWER":
|
||||
power(args)
|
||||
case "REPT":
|
||||
rept(args)
|
||||
case "RETRY":
|
||||
retry(args)
|
||||
case "RXWIN2":
|
||||
rxwin2(args)
|
||||
case "RXWIN1":
|
||||
rxwin1(args)
|
||||
case "KEY":
|
||||
key(args)
|
||||
case "FDEFAULT":
|
||||
fdefault(args)
|
||||
case "MODE":
|
||||
mode(args)
|
||||
case "JOIN":
|
||||
join(args)
|
||||
case "BEACON":
|
||||
join(args)
|
||||
case "CLASS":
|
||||
class(args)
|
||||
case "DELAY":
|
||||
delay(args)
|
||||
case "LW":
|
||||
lw(args)
|
||||
case "WDT":
|
||||
wdt(args)
|
||||
case "LOWPOWER":
|
||||
lowpower(args)
|
||||
case "VDD":
|
||||
vdd(args)
|
||||
case "TEMP":
|
||||
temp(args)
|
||||
case "RTC":
|
||||
rtc(args)
|
||||
case "EEPROM":
|
||||
eeprom(args)
|
||||
case "UART":
|
||||
uartcmd(args)
|
||||
case "TEST":
|
||||
test(args)
|
||||
case "LOG":
|
||||
log(args)
|
||||
case "RECV":
|
||||
recv(args)
|
||||
case "RECVHEX":
|
||||
recvhex(args)
|
||||
case "SEND":
|
||||
send(args)
|
||||
case "SENDHEX":
|
||||
sendhex(args)
|
||||
default:
|
||||
return errInvalidCommand
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errRadioNotFound = errors.New("radio not found")
|
||||
errRxTimeout = errors.New("radio RX timeout")
|
||||
)
|
||||
|
||||
type LoraRadio interface {
|
||||
Reset()
|
||||
Tx(pkt []uint8, timeoutMs uint32) error
|
||||
Rx(timeoutMs uint32) ([]uint8, error)
|
||||
SetFrequency(freq uint32)
|
||||
SetIqMode(mode uint8)
|
||||
SetCodingRate(cr uint8)
|
||||
SetBandwidth(bw uint8)
|
||||
SetCrc(enable bool)
|
||||
SetSpreadingFactor(sf uint8)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//go:build !featherwing && !gnse && !lorae5 && !nucleowl55jc
|
||||
|
||||
package main
|
||||
|
||||
// do simulator setup here
|
||||
func setupLora() (LoraRadio, error) {
|
||||
return &SimLoraRadio{}, nil
|
||||
}
|
||||
|
||||
type SimLoraRadio struct {
|
||||
}
|
||||
|
||||
func (sr *SimLoraRadio) Reset() {
|
||||
}
|
||||
|
||||
func (sr *SimLoraRadio) Tx(pkt []uint8, timeoutMs uint32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sr *SimLoraRadio) Rx(timeoutMs uint32) ([]uint8, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (sr *SimLoraRadio) SetFrequency(freq uint32) {}
|
||||
func (sr *SimLoraRadio) SetIqMode(mode uint8) {}
|
||||
func (sr *SimLoraRadio) SetCodingRate(cr uint8) {}
|
||||
func (sr *SimLoraRadio) SetBandwidth(bw uint8) {}
|
||||
func (sr *SimLoraRadio) SetCrc(enable bool) {}
|
||||
func (sr *SimLoraRadio) SetSpreadingFactor(sf uint8) {}
|
||||
|
||||
func firmwareVersion() string {
|
||||
return "simulator " + currentVersion()
|
||||
}
|
||||
|
||||
func lorarx() ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
//go:build gnse || lorae5 || nucleowl55jc
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
|
||||
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
|
||||
|
||||
"tinygo.org/x/drivers/lora"
|
||||
"tinygo.org/x/drivers/sx126x"
|
||||
)
|
||||
|
||||
const (
|
||||
FREQ = 868100000
|
||||
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
||||
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
||||
)
|
||||
|
||||
var (
|
||||
loraRadio *sx126x.Device
|
||||
)
|
||||
|
||||
// do sx126x setup here
|
||||
func setupLora() (LoraRadio, error) {
|
||||
loraRadio = sx126x.New(machine.SPI3)
|
||||
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
||||
|
||||
// Create RF Switch
|
||||
var radioSwitch rfswitch.CustomSwitch
|
||||
loraRadio.SetRfSwitch(radioSwitch)
|
||||
|
||||
if state := loraRadio.DetectDevice(); !state {
|
||||
return nil, errRadioNotFound
|
||||
}
|
||||
|
||||
// Add interrupt handler for Radio IRQs
|
||||
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
|
||||
intr.Enable()
|
||||
|
||||
loraConf := lora.Config{
|
||||
Freq: FREQ,
|
||||
Bw: lora.Bandwidth_500_0,
|
||||
Sf: lora.SpreadingFactor9,
|
||||
Cr: lora.CodingRate4_7,
|
||||
HeaderType: lora.HeaderExplicit,
|
||||
Preamble: 12,
|
||||
Ldr: lora.LowDataRateOptimizeOff,
|
||||
Iq: lora.IQStandard,
|
||||
Crc: lora.CRCOn,
|
||||
SyncWord: lora.SyncPrivate,
|
||||
LoraTxPowerDBm: 20,
|
||||
}
|
||||
|
||||
loraRadio.LoraConfig(loraConf)
|
||||
|
||||
return loraRadio, nil
|
||||
}
|
||||
|
||||
// radioIntHandler will take care of radio interrupts
|
||||
func radioIntHandler(intr interrupt.Interrupt) {
|
||||
loraRadio.HandleInterrupt()
|
||||
}
|
||||
|
||||
func firmwareVersion() string {
|
||||
return "sx126x"
|
||||
}
|
||||
|
||||
func lorarx() ([]byte, error) {
|
||||
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//go:build featherwing
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/lora"
|
||||
"tinygo.org/x/drivers/sx127x"
|
||||
)
|
||||
|
||||
const (
|
||||
FREQ = 868100000
|
||||
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
||||
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
||||
)
|
||||
|
||||
var (
|
||||
// We assume LoRa Featherwing module is connected to PyBadge:
|
||||
rstPin = machine.D11
|
||||
csPin = machine.D10
|
||||
dio0Pin = machine.D6
|
||||
dio1Pin = machine.D9
|
||||
spi = machine.SPI0
|
||||
loraRadio *sx127x.Device
|
||||
)
|
||||
|
||||
// do sx127x setup here
|
||||
func setupLora() (LoraRadio, error) {
|
||||
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
dio0Pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
spi.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
|
||||
|
||||
loraRadio = sx127x.New(spi, csPin, rstPin)
|
||||
loraRadio.Reset()
|
||||
|
||||
if state := loraRadio.DetectDevice(); !state {
|
||||
return nil, errRadioNotFound
|
||||
}
|
||||
|
||||
// Setup DIO0 interrupt Handling
|
||||
if err := dio0Pin.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
|
||||
println("could not configure DIO0 pin interrupt:", err.Error())
|
||||
}
|
||||
|
||||
// Setup DIO1 interrupt Handling
|
||||
if err := dio1Pin.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
|
||||
println("could not configure DIO1 pin interrupt:", err.Error())
|
||||
}
|
||||
|
||||
// Prepare for Lora Operation
|
||||
loraConf := lora.Config{
|
||||
Freq: FREQ,
|
||||
Bw: lora.Bandwidth_500_0,
|
||||
Sf: lora.SpreadingFactor9,
|
||||
Cr: lora.CodingRate4_7,
|
||||
HeaderType: lora.HeaderExplicit,
|
||||
Preamble: 12,
|
||||
Iq: lora.IQStandard,
|
||||
Crc: lora.CRCOn,
|
||||
SyncWord: lora.SyncPrivate,
|
||||
LoraTxPowerDBm: 20,
|
||||
}
|
||||
|
||||
loraRadio.LoraConfig(loraConf)
|
||||
|
||||
return loraRadio, nil
|
||||
}
|
||||
|
||||
func dioIrqHandler(machine.Pin) {
|
||||
loraRadio.HandleInterrupt()
|
||||
}
|
||||
|
||||
func firmwareVersion() string {
|
||||
v := loraRadio.GetVersion()
|
||||
return "sx127x v" + strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
func lorarx() ([]byte, error) {
|
||||
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
const VERSION = "0.0.1"
|
||||
|
||||
func currentVersion() string {
|
||||
return VERSION
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//go:build nucleowl55jc
|
||||
|
||||
/*
|
||||
Nucleo WL55JC1
|
||||
RFSwitch
|
||||
|
||||
+-----------+---------+------------+------------+
|
||||
| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 |
|
||||
| | (PC4) | (PC5) | (PC3) |
|
||||
+-----------+----------+-----------+------------+
|
||||
| TX_HP | LOW | HIGH | HIGH |
|
||||
| TX_LP | HIGH | HIGH | HIGH |
|
||||
| RX | HIGH | LOW | HIGH |
|
||||
+-----------+----------+-----------+------------+
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers/sx126x"
|
||||
)
|
||||
|
||||
type CustomSwitch struct {
|
||||
}
|
||||
|
||||
func (s CustomSwitch) InitRFSwitch() {
|
||||
machine.PC4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
machine.PC5.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
machine.PC3.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
}
|
||||
|
||||
func (s CustomSwitch) SetRfSwitchMode(mode int) error {
|
||||
switch mode {
|
||||
|
||||
case sx126x.RFSWITCH_TX_HP:
|
||||
machine.PC4.Set(false)
|
||||
machine.PC5.Set(true)
|
||||
machine.PC3.Set(true)
|
||||
|
||||
case sx126x.RFSWITCH_TX_LP:
|
||||
machine.PC4.Set(true)
|
||||
machine.PC5.Set(true)
|
||||
machine.PC3.Set(true)
|
||||
|
||||
case sx126x.RFSWITCH_RX:
|
||||
machine.PC4.Set(true)
|
||||
machine.PC5.Set(false)
|
||||
machine.PC3.Set(true)
|
||||
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//go:build macropad_rp2040
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/sh1106"
|
||||
)
|
||||
|
||||
var (
|
||||
display = sh1106.NewSPI(machine.SPI1, machine.OLED_DC, machine.OLED_RST, machine.OLED_CS)
|
||||
)
|
||||
|
||||
func init() {
|
||||
machine.SPI1.Configure(machine.SPIConfig{
|
||||
Frequency: 48000000,
|
||||
})
|
||||
display.Configure(sh1106.Config{
|
||||
Width: 128,
|
||||
Height: 64,
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
display.ClearDisplay()
|
||||
|
||||
x := int16(0)
|
||||
y := int16(0)
|
||||
deltaX := int16(1)
|
||||
deltaY := int16(1)
|
||||
for {
|
||||
pixel := display.GetPixel(x, y)
|
||||
c := color.RGBA{255, 255, 255, 255}
|
||||
if pixel {
|
||||
c = color.RGBA{0, 0, 0, 255}
|
||||
}
|
||||
display.SetPixel(x, y, c)
|
||||
display.Display()
|
||||
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
|
||||
if x == 0 || x == 127 {
|
||||
deltaX = -deltaX
|
||||
}
|
||||
|
||||
if y == 0 || y == 63 {
|
||||
deltaY = -deltaY
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
|
||||
|
||||
"tinygo.org/x/drivers/lora"
|
||||
"tinygo.org/x/drivers/sx126x"
|
||||
)
|
||||
|
||||
@@ -36,17 +37,17 @@ func main() {
|
||||
}
|
||||
|
||||
// Prepare for Lora operation
|
||||
loraConf := sx126x.LoraConfig{
|
||||
loraConf := lora.Config{
|
||||
Freq: FREQ,
|
||||
Bw: sx126x.SX126X_LORA_BW_500_0,
|
||||
Sf: sx126x.SX126X_LORA_SF9,
|
||||
Cr: sx126x.SX126X_LORA_CR_4_7,
|
||||
HeaderType: sx126x.SX126X_LORA_HEADER_EXPLICIT,
|
||||
Bw: lora.Bandwidth_500_0,
|
||||
Sf: lora.SpreadingFactor9,
|
||||
Cr: lora.CodingRate4_7,
|
||||
HeaderType: lora.HeaderExplicit,
|
||||
Preamble: 12,
|
||||
Ldr: sx126x.SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF,
|
||||
Iq: sx126x.SX126X_LORA_IQ_STANDARD,
|
||||
Crc: sx126x.SX126X_LORA_CRC_ON,
|
||||
SyncWord: sx126x.SX126X_LORA_MAC_PRIVATE_SYNCWORD,
|
||||
Ldr: lora.LowDataRateOptimizeOff,
|
||||
Iq: lora.IQStandard,
|
||||
Crc: lora.CRCOn,
|
||||
SyncWord: lora.SyncPrivate,
|
||||
LoraTxPowerDBm: 14,
|
||||
}
|
||||
loraRadio.LoraConfig(loraConf)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
|
||||
|
||||
"tinygo.org/x/drivers/lora"
|
||||
"tinygo.org/x/drivers/sx126x"
|
||||
)
|
||||
|
||||
@@ -54,17 +55,17 @@ func main() {
|
||||
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
|
||||
intr.Enable()
|
||||
|
||||
loraConf := sx126x.LoraConfig{
|
||||
loraConf := lora.Config{
|
||||
Freq: FREQ,
|
||||
Bw: sx126x.SX126X_LORA_BW_500_0,
|
||||
Sf: sx126x.SX126X_LORA_SF9,
|
||||
Cr: sx126x.SX126X_LORA_CR_4_7,
|
||||
HeaderType: sx126x.SX126X_LORA_HEADER_EXPLICIT,
|
||||
Bw: lora.Bandwidth_500_0,
|
||||
Sf: lora.SpreadingFactor9,
|
||||
Cr: lora.CodingRate4_7,
|
||||
HeaderType: lora.HeaderExplicit,
|
||||
Preamble: 12,
|
||||
Ldr: sx126x.SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF,
|
||||
Iq: sx126x.SX126X_LORA_IQ_STANDARD,
|
||||
Crc: sx126x.SX126X_LORA_CRC_ON,
|
||||
SyncWord: sx126x.SX126X_LORA_MAC_PRIVATE_SYNCWORD,
|
||||
Ldr: lora.LowDataRateOptimizeOff,
|
||||
Iq: lora.IQStandard,
|
||||
Crc: lora.CRCOn,
|
||||
SyncWord: lora.SyncPrivate,
|
||||
LoraTxPowerDBm: 20,
|
||||
}
|
||||
|
||||
@@ -74,21 +75,18 @@ func main() {
|
||||
for {
|
||||
tStart := time.Now()
|
||||
|
||||
// Blocking RX for LORA_DEFAULT_RXTIMEOUT_MS
|
||||
println("Start Lora RX for 10 sec")
|
||||
println("main: Receiving Lora for 10 seconds")
|
||||
for int(time.Now().Sub(tStart).Seconds()) < 10 {
|
||||
buf, err := loraRadio.LoraRx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||
|
||||
buf, err := loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||
if err != nil {
|
||||
println("RX Error: ", err)
|
||||
} else if buf != nil {
|
||||
println("Packet Received: len=", len(buf), string(buf))
|
||||
}
|
||||
}
|
||||
println("END Lora RX")
|
||||
|
||||
println("LORA TX size=", len(txmsg))
|
||||
err := loraRadio.LoraTx(txmsg, LORA_DEFAULT_TXTIMEOUT_MS)
|
||||
println("main: End Lora RX")
|
||||
println("LORA TX size=", len(txmsg), " -> ", string(txmsg))
|
||||
err := loraRadio.Tx(txmsg, LORA_DEFAULT_TXTIMEOUT_MS)
|
||||
if err != nil {
|
||||
println("TX Error:", err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
// This example code demonstrates Lora RX/TX With SX127x driver
|
||||
// You need to connect SPI, RST, CS, DIO0 (aka IRQ) and DIO1 to use.
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/lora"
|
||||
"tinygo.org/x/drivers/sx127x"
|
||||
)
|
||||
|
||||
const FREQ = 868100000
|
||||
|
||||
const (
|
||||
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
||||
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
||||
)
|
||||
|
||||
var (
|
||||
loraRadio *sx127x.Device
|
||||
txmsg = []byte("Hello TinyGO")
|
||||
|
||||
// We assume LoRa Featherwing module is connected to PyBadge:
|
||||
SX127X_PIN_RST = machine.D11
|
||||
SX127X_PIN_CS = machine.D10
|
||||
SX127X_PIN_DIO0 = machine.D6
|
||||
SX127X_PIN_DIO1 = machine.D9
|
||||
SX127X_SPI = machine.SPI0
|
||||
)
|
||||
|
||||
func dioIrqHandler(machine.Pin) {
|
||||
loraRadio.HandleInterrupt()
|
||||
}
|
||||
|
||||
func main() {
|
||||
time.Sleep(5 * time.Second)
|
||||
println("\n# TinyGo Lora RX/TX test")
|
||||
println("# ----------------------")
|
||||
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
SX127X_PIN_RST.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
SX127X_PIN_CS.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
SX127X_PIN_DIO0.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
SX127X_PIN_DIO1.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
SX127X_SPI.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
|
||||
|
||||
println("main: create and start SX127x driver")
|
||||
loraRadio = sx127x.New(SX127X_SPI, SX127X_PIN_CS, SX127X_PIN_RST)
|
||||
loraRadio.Reset()
|
||||
state := loraRadio.DetectDevice()
|
||||
if !state {
|
||||
panic("main: sx127x NOT FOUND !!!")
|
||||
} else {
|
||||
println("main: sx127x found")
|
||||
}
|
||||
|
||||
// Setup DIO0 interrupt Handling
|
||||
if err := SX127X_PIN_DIO0.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
|
||||
println("could not configure DIO0 pin interrupt:", err.Error())
|
||||
}
|
||||
|
||||
// Setup DIO1 interrupt Handling
|
||||
if err := SX127X_PIN_DIO1.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
|
||||
println("could not configure DIO1 pin interrupt:", err.Error())
|
||||
}
|
||||
|
||||
// Prepare for Lora Operation
|
||||
loraConf := lora.Config{
|
||||
Freq: FREQ,
|
||||
Bw: lora.Bandwidth_500_0,
|
||||
Sf: lora.SpreadingFactor9,
|
||||
Cr: lora.CodingRate4_7,
|
||||
HeaderType: lora.HeaderExplicit,
|
||||
Preamble: 12,
|
||||
Iq: lora.IQStandard,
|
||||
Crc: lora.CRCOn,
|
||||
SyncWord: lora.SyncPrivate,
|
||||
LoraTxPowerDBm: 20,
|
||||
}
|
||||
|
||||
loraRadio.LoraConfig(loraConf)
|
||||
|
||||
var count uint
|
||||
for {
|
||||
tStart := time.Now()
|
||||
|
||||
println("main: Receiving Lora for 10 seconds")
|
||||
for time.Since(tStart) < 10*time.Second {
|
||||
buf, err := loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||
if err != nil {
|
||||
println("RX Error: ", err)
|
||||
} else if buf != nil {
|
||||
println("Packet Received: len=", len(buf), string(buf))
|
||||
}
|
||||
}
|
||||
println("main: End Lora RX")
|
||||
println("LORA TX size=", len(txmsg), " -> ", string(txmsg))
|
||||
err := loraRadio.Tx(txmsg, LORA_DEFAULT_TXTIMEOUT_MS)
|
||||
if err != nil {
|
||||
println("TX Error:", err)
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package lora
|
||||
|
||||
import "errors"
|
||||
|
||||
// Config holds the LoRa configuration parameters
|
||||
type Config struct {
|
||||
Freq uint32 // Frequency
|
||||
Cr uint8 // Coding Rate
|
||||
Sf uint8 // Spread Factor
|
||||
Bw uint8 // Bandwidth
|
||||
Ldr uint8 // Low Data Rate
|
||||
Preamble uint16 // PreambleLength
|
||||
SyncWord uint16 // Sync Word
|
||||
HeaderType uint8 // Header : Implicit/explicit
|
||||
Crc uint8 // CRC : Yes/No
|
||||
Iq uint8 // iq : Standard/inverted
|
||||
LoraTxPowerDBm int8 // Tx power in Dbm
|
||||
}
|
||||
|
||||
var (
|
||||
ErrUndefinedLoraConf = errors.New("Undefined Lora configuration")
|
||||
)
|
||||
|
||||
const (
|
||||
SpreadingFactor5 = 0x05
|
||||
SpreadingFactor6 = 0x06
|
||||
SpreadingFactor7 = 0x07
|
||||
SpreadingFactor8 = 0x08
|
||||
SpreadingFactor9 = 0x09
|
||||
SpreadingFactor10 = 0x0A
|
||||
SpreadingFactor11 = 0x0B
|
||||
SpreadingFactor12 = 0x0C
|
||||
)
|
||||
|
||||
const (
|
||||
CodingRate4_5 = 0x01 // 7 0 LoRa coding rate: 4/5
|
||||
CodingRate4_6 = 0x02 // 7 0 4/6
|
||||
CodingRate4_7 = 0x03 // 7 0 4/7
|
||||
CodingRate4_8 = 0x04 // 7 0 4/8
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderExplicit = 0x00 // 7 0 LoRa header mode: explicit
|
||||
HeaderImplicit = 0x01 // 7 0 implicit
|
||||
)
|
||||
|
||||
const (
|
||||
LowDataRateOptimizeOff = 0x00 // 7 0 LoRa low data rate optimization: disabled
|
||||
LowDataRateOptimizeOn = 0x01 // 7 0 enabled
|
||||
)
|
||||
|
||||
const (
|
||||
CRCOff = 0x00 // 7 0 LoRa CRC mode: disabled
|
||||
CRCOn = 0x01 // 7 0 enabled
|
||||
)
|
||||
|
||||
const (
|
||||
IQStandard = 0x00 // 7 0 LoRa IQ setup: standard
|
||||
IQInverted = 0x01 // 7 0 inverted
|
||||
)
|
||||
|
||||
const (
|
||||
Bandwidth_7_8 = iota // 7.8 kHz
|
||||
Bandwidth_10_4 // 10.4 kHz
|
||||
Bandwidth_15_6 // 15.6 kHz
|
||||
Bandwidth_20_8 // 20.8 kHz
|
||||
Bandwidth_31_25 // 31.25 kHz
|
||||
Bandwidth_41_7 // 41.7 kHz
|
||||
Bandwidth_62_5 // 62.5 kHz
|
||||
Bandwidth_125_0 // 125.0 kHz
|
||||
Bandwidth_250_0 // 250.0 kHz
|
||||
Bandwidth_500_0 // 500.0 kHz
|
||||
)
|
||||
|
||||
const (
|
||||
SyncPublic = iota
|
||||
SyncPrivate
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
package lora
|
||||
|
||||
const (
|
||||
RadioEventRxDone = iota
|
||||
RadioEventTxDone
|
||||
RadioEventTimeout
|
||||
RadioEventWatchdog
|
||||
RadioEventCrcError
|
||||
RadioEventUnhandled
|
||||
)
|
||||
|
||||
// RadioEvent are used for communicating in the radio Event Channel
|
||||
type RadioEvent struct {
|
||||
EventType int
|
||||
IRQStatus uint16
|
||||
EventData []byte
|
||||
}
|
||||
|
||||
// NewRadioEvent() returns a new RadioEvent that can be used in the RadioChannel
|
||||
func NewRadioEvent(eType int, irqStatus uint16, eData []byte) RadioEvent {
|
||||
r := RadioEvent{EventType: eType, IRQStatus: irqStatus, EventData: eData}
|
||||
return r
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package http
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -213,41 +211,3 @@ func (c *Client) Post(url, contentType string, body io.Reader) (resp *Response,
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
// PostForm issues a POST to the specified URL, with data's keys and
|
||||
// values URL-encoded as the request body.
|
||||
//
|
||||
// The Content-Type header is set to application/x-www-form-urlencoded.
|
||||
// To set other headers, use NewRequest and DefaultClient.Do.
|
||||
//
|
||||
// When err is nil, resp always contains a non-nil resp.Body.
|
||||
// Caller should close resp.Body when done reading from it.
|
||||
//
|
||||
// PostForm is a wrapper around DefaultClient.PostForm.
|
||||
//
|
||||
// See the Client.Do method documentation for details on how redirects
|
||||
// are handled.
|
||||
//
|
||||
// To make a request with a specified context.Context, use NewRequestWithContext
|
||||
// and DefaultClient.Do.
|
||||
func PostForm(url string, data url.Values) (resp *Response, err error) {
|
||||
return DefaultClient.PostForm(url, data)
|
||||
}
|
||||
|
||||
// PostForm issues a POST to the specified URL,
|
||||
// with data's keys and values URL-encoded as the request body.
|
||||
//
|
||||
// The Content-Type header is set to application/x-www-form-urlencoded.
|
||||
// To set other headers, use NewRequest and Client.Do.
|
||||
//
|
||||
// When err is nil, resp always contains a non-nil resp.Body.
|
||||
// Caller should close resp.Body when done reading from it.
|
||||
//
|
||||
// See the Client.Do method documentation for details on how redirects
|
||||
// are handled.
|
||||
//
|
||||
// To make a request with a specified context.Context, use NewRequestWithContext
|
||||
// and Client.Do.
|
||||
func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error) {
|
||||
return c.Post(url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
||||
}
|
||||
|
||||
+118
-135
@@ -5,7 +5,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -26,43 +25,17 @@ func (c *Client) Do(req *Request) (*Response, error) {
|
||||
req.AddCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
transport := c.Transport
|
||||
if transport == nil {
|
||||
transport = DefaultTransport
|
||||
}
|
||||
res, err := transport.RoundTrip(req)
|
||||
|
||||
if c.Jar != nil {
|
||||
if rc := res.Cookies(); len(rc) > 0 {
|
||||
c.Jar.SetCookies(req.URL, rc)
|
||||
}
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
type Transport struct {
|
||||
}
|
||||
|
||||
var DefaultTransport RoundTripper
|
||||
|
||||
func init() {
|
||||
DefaultTransport = &Transport{}
|
||||
}
|
||||
|
||||
func (t *Transport) RoundTrip(req *Request) (*Response, error) {
|
||||
switch req.URL.Scheme {
|
||||
case "http":
|
||||
return t.doHTTP(req)
|
||||
return c.doHTTP(req)
|
||||
case "https":
|
||||
return t.doHTTPS(req)
|
||||
return c.doHTTPS(req)
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid schemer : %s", req.URL.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) doHTTP(req *Request) (*Response, error) {
|
||||
func (c *Client) doHTTP(req *Request) (*Response, error) {
|
||||
// make TCP connection
|
||||
ip := net.ParseIP(req.URL.Hostname())
|
||||
port := 80
|
||||
@@ -133,10 +106,10 @@ func (t *Transport) doHTTP(req *Request) (*Response, error) {
|
||||
|
||||
}
|
||||
|
||||
return t.doResp(conn, req)
|
||||
return c.doResp(conn, req)
|
||||
}
|
||||
|
||||
func (t *Transport) doHTTPS(req *Request) (*Response, error) {
|
||||
func (c *Client) doHTTPS(req *Request) (*Response, error) {
|
||||
conn, err := tls.Dial("tcp", req.URL.Host, nil)
|
||||
retry := 0
|
||||
for ; err != nil; conn, err = tls.Dial("tcp", req.URL.Host, nil) {
|
||||
@@ -194,131 +167,141 @@ func (t *Transport) doHTTPS(req *Request) (*Response, error) {
|
||||
|
||||
}
|
||||
|
||||
return t.doResp(conn, req)
|
||||
return c.doResp(conn, req)
|
||||
}
|
||||
|
||||
func (t *Transport) doResp(conn net.Conn, req *Request) (*Response, error) {
|
||||
func (c *Client) doResp(conn net.Conn, req *Request) (*Response, error) {
|
||||
resp := &Response{
|
||||
Header: map[string][]string{},
|
||||
}
|
||||
|
||||
br := bufio.NewReader(conn)
|
||||
tp := textproto.NewReader(br)
|
||||
|
||||
for {
|
||||
line, err := tp.ReadLine()
|
||||
if err != nil {
|
||||
if err == io.ErrNoProgress {
|
||||
// default: no timeout
|
||||
continue
|
||||
}
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status := strings.SplitN(line, " ", 2)
|
||||
if len(status) != 2 {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("invalid status : %q", line)
|
||||
}
|
||||
resp.Proto = status[0]
|
||||
fmt.Sscanf(status[0], "HTTP/%d.%d", &resp.ProtoMajor, &resp.ProtoMinor)
|
||||
|
||||
resp.Status = status[1]
|
||||
fmt.Sscanf(status[1], "%d", &resp.StatusCode)
|
||||
break
|
||||
}
|
||||
|
||||
m, err := tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range m {
|
||||
//fmt.Printf("%s: %s\n", k, v)
|
||||
|
||||
if strings.ToLower(k) == "content-length" {
|
||||
resp.ContentLength, err = strconv.ParseInt(v[0], 10, 64)
|
||||
// Header
|
||||
var scanner *bufio.Scanner
|
||||
cont := true
|
||||
ofs := 0
|
||||
remain := int64(0)
|
||||
for cont {
|
||||
for n, err := conn.Read(buf[ofs:]); n > 0; n, err = conn.Read(buf[ofs:]) {
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if resp.Header.Get(k) == "" {
|
||||
resp.Header.Set(k, v[0])
|
||||
v = v[1:]
|
||||
}
|
||||
for _, vv := range v {
|
||||
resp.Header.Add(k, vv)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if resp.Header.Get("Transfer-Encoding") == "chunked" {
|
||||
// chunked
|
||||
cur := 0
|
||||
end := 0
|
||||
for {
|
||||
length := 0
|
||||
if len(buf) < cur+6 {
|
||||
// This is not a very accurate check, but in many cases it should be fine.
|
||||
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", cur+6)
|
||||
}
|
||||
for i := 0; ; i++ {
|
||||
buf[cur+i], err = br.ReadByte()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
println("Read error: " + err.Error())
|
||||
} else {
|
||||
// Take care of the case where "\r\n\r\n" is on the boundary of a buffer
|
||||
start := ofs
|
||||
if start > 3 {
|
||||
start -= 3
|
||||
}
|
||||
length = i + 1
|
||||
if i > 1 && buf[cur+i-1] == '\r' && buf[cur+i] == '\n' {
|
||||
break
|
||||
idx := bytes.Index(buf[start:ofs+n], []byte("\r\n\r\n"))
|
||||
if idx == -1 {
|
||||
ofs += n
|
||||
continue
|
||||
}
|
||||
}
|
||||
//fmt.Printf("cur:%d length:%d\n", cur, length)
|
||||
idx += start + 4
|
||||
|
||||
size, err := strconv.ParseInt(string(buf[cur:cur+length-2]), 16, 64)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
//cur += length
|
||||
//fmt.Printf("cur:%d length:%d size:%d\n", cur, length, size)
|
||||
scanner = bufio.NewScanner(bytes.NewReader(buf[0 : ofs+n]))
|
||||
if resp.Status == "" && scanner.Scan() {
|
||||
status := strings.SplitN(scanner.Text(), " ", 2)
|
||||
if len(status) != 2 {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("invalid status : %q", scanner.Text())
|
||||
}
|
||||
resp.Proto = status[0]
|
||||
fmt.Sscanf(status[0], "HTTP/%d.%d", &resp.ProtoMajor, &resp.ProtoMinor)
|
||||
|
||||
end = cur + int(size) + 2 // size + 2 (\r\n)
|
||||
if len(buf) < end {
|
||||
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", end)
|
||||
}
|
||||
for i := 0; i < int(size)+2; i++ {
|
||||
buf[cur+i], err = br.ReadByte()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
resp.Status = status[1]
|
||||
fmt.Sscanf(status[1], "%d", &resp.StatusCode)
|
||||
}
|
||||
}
|
||||
cur += int(size)
|
||||
|
||||
if size == 0 {
|
||||
end = end - 2
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
if text == "" {
|
||||
// end of header
|
||||
if idx < n+ofs {
|
||||
ofs = ofs + n - idx
|
||||
for i := 0; i < ofs; i++ {
|
||||
buf[i] = buf[i+idx]
|
||||
}
|
||||
} else {
|
||||
ofs = 0
|
||||
}
|
||||
break
|
||||
} else {
|
||||
header := strings.SplitN(text, ": ", 2)
|
||||
if len(header) != 2 {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("invalid header : %q", text)
|
||||
}
|
||||
if resp.Header.Get(header[0]) == "" {
|
||||
resp.Header.Set(header[0], header[1])
|
||||
} else {
|
||||
resp.Header.Add(header[0], header[1])
|
||||
}
|
||||
|
||||
if strings.ToLower(header[0]) == "content-length" {
|
||||
resp.ContentLength, err = strconv.ParseInt(header[1], 10, 64)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
remain = resp.ContentLength
|
||||
}
|
||||
}
|
||||
}
|
||||
cont = false
|
||||
break
|
||||
}
|
||||
}
|
||||
//fmt.Printf("%q\n", buf[:end])
|
||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:end]))
|
||||
} else {
|
||||
end := int(resp.ContentLength)
|
||||
if len(buf) < end {
|
||||
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", end)
|
||||
}
|
||||
|
||||
// Body
|
||||
remain -= int64(ofs)
|
||||
if remain <= 0 {
|
||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:ofs]))
|
||||
if c.Jar != nil {
|
||||
if rc := resp.Cookies(); len(rc) > 0 {
|
||||
c.Jar.SetCookies(req.URL, rc)
|
||||
}
|
||||
}
|
||||
for i := 0; i < end; i++ {
|
||||
buf[i], err = br.ReadByte()
|
||||
return resp, conn.Close()
|
||||
}
|
||||
|
||||
cont = true
|
||||
lastRequestTime := time.Now()
|
||||
for cont {
|
||||
for {
|
||||
end := ofs + 0x400
|
||||
if len(buf) < end {
|
||||
return nil, fmt.Errorf("slice out of range : use http.SetBuf() to change the allocation to %d bytes or more", end)
|
||||
}
|
||||
n, err := conn.Read(buf[ofs : ofs+0x400])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
} else {
|
||||
ofs += n
|
||||
remain -= int64(n)
|
||||
if remain <= 0 {
|
||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:ofs]))
|
||||
cont = false
|
||||
break
|
||||
}
|
||||
if time.Now().Sub(lastRequestTime).Milliseconds() >= 1000 {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("time out")
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.Body = io.NopCloser(bytes.NewReader(buf[:end]))
|
||||
}
|
||||
|
||||
if c.Jar != nil {
|
||||
if rc := resp.Cookies(); len(rc) > 0 {
|
||||
c.Jar.SetCookies(req.URL, rc)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, conn.Close()
|
||||
|
||||
@@ -71,13 +71,10 @@ func (d *Driver) ConnectTCPSocket(addr, port string) error {
|
||||
name[6] = byte(ipaddr[2])
|
||||
name[7] = byte(ipaddr[3])
|
||||
|
||||
result, err := d.Rpc_lwip_connect(socket, name, uint32(len(name)))
|
||||
_, err = d.Rpc_lwip_connect(socket, name, uint32(len(name)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result == -1 {
|
||||
return fmt.Errorf("failed to connect to %d.%d.%d.%d port %s", addr[0], addr[1], addr[2], addr[3], port)
|
||||
}
|
||||
|
||||
readset := []byte{}
|
||||
writeset := []byte{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
@@ -344,11 +341,8 @@ func (d *Driver) ReadSocket(b []byte) (n int, err error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if nn == -76 {
|
||||
// no data
|
||||
return 0, nil
|
||||
} else if nn < 0 {
|
||||
return 0, fmt.Errorf("error %d", nn)
|
||||
if nn < 0 {
|
||||
return 0, fmt.Errorf("error %d", n)
|
||||
} else if nn == 0 || nn == -30848 {
|
||||
return 0, d.DisconnectSocket()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package sh1106
|
||||
|
||||
// Registers
|
||||
const (
|
||||
Address = 0x3C
|
||||
|
||||
SETCONTRAST = 0x81
|
||||
DISPLAYALLON_RESUME = 0xA4
|
||||
DISPLAYALLON = 0xA5
|
||||
NORMALDISPLAY = 0xA6
|
||||
INVERTDISPLAY = 0xA7
|
||||
DISPLAYOFF = 0xAE
|
||||
DISPLAYON = 0xAF
|
||||
SETDISPLAYOFFSET = 0xD3
|
||||
SETCOMPINS = 0xDA
|
||||
SETVCOMDETECT = 0xDB
|
||||
SETDISPLAYCLOCKDIV = 0xD5
|
||||
SETPRECHARGE = 0xD9
|
||||
SETMULTIPLEX = 0xA8
|
||||
SETLOWCOLUMN = 0x00
|
||||
SETHIGHCOLUMN = 0x10
|
||||
SETSTARTLINE = 0x40
|
||||
MEMORYMODE = 0x20
|
||||
COLUMNADDR = 0x21
|
||||
PAGEADDR = 0x22
|
||||
COMSCANINC = 0xC0
|
||||
COMSCANDEC = 0xC8
|
||||
SEGREMAP = 0xA0
|
||||
CHARGEPUMP = 0x8D
|
||||
ACTIVATE_SCROLL = 0x2F
|
||||
DEACTIVATE_SCROLL = 0x2E
|
||||
SET_VERTICAL_SCROLL_AREA = 0xA3
|
||||
RIGHT_HORIZONTAL_SCROLL = 0x26
|
||||
LEFT_HORIZONTAL_SCROLL = 0x27
|
||||
VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL = 0x29
|
||||
VERTICAL_AND_LEFT_HORIZONTAL_SCROLL = 0x2A
|
||||
|
||||
EXTERNALVCC VccMode = 0x1
|
||||
SWITCHCAPVCC VccMode = 0x2
|
||||
)
|
||||
@@ -0,0 +1,326 @@
|
||||
// Package sh1106 implements a driver for the SH1106 display controller
|
||||
//
|
||||
// Copied from https://github.com/toyo/tinygo-sh1106 (under BSD 3-clause license)
|
||||
package sh1106 // import "tinygo.org/x/drivers/sh1106"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image/color"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an SPI connection.
|
||||
type Device struct {
|
||||
bus Buser
|
||||
buffer []byte
|
||||
cmdbuf [1]byte
|
||||
width int16
|
||||
height int16
|
||||
bufferSize int16
|
||||
vccState VccMode
|
||||
}
|
||||
|
||||
// Config is the configuration for the display
|
||||
type Config struct {
|
||||
Width int16
|
||||
Height int16
|
||||
VccState VccMode
|
||||
Address uint16
|
||||
}
|
||||
|
||||
type I2CBus struct {
|
||||
wire drivers.I2C
|
||||
Address uint16
|
||||
}
|
||||
|
||||
type SPIBus struct {
|
||||
wire drivers.SPI
|
||||
dcPin machine.Pin
|
||||
resetPin machine.Pin
|
||||
csPin machine.Pin
|
||||
}
|
||||
|
||||
type Buser interface {
|
||||
configure()
|
||||
tx(data []byte, isCommand bool)
|
||||
setAddress(address uint16)
|
||||
}
|
||||
|
||||
type VccMode uint8
|
||||
|
||||
// NewI2C creates a new SSD1306 connection. The I2C wire must already be configured.
|
||||
func NewI2C(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: &I2CBus{
|
||||
wire: bus,
|
||||
Address: Address,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewSPI creates a new SSD1306 connection. The SPI wire must already be configured.
|
||||
func NewSPI(bus drivers.SPI, dcPin, resetPin, csPin machine.Pin) Device {
|
||||
dcPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
resetPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
return Device{
|
||||
bus: &SPIBus{
|
||||
wire: bus,
|
||||
dcPin: dcPin,
|
||||
resetPin: resetPin,
|
||||
csPin: csPin,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Configure initializes the display with default configuration
|
||||
func (d *Device) Configure(cfg Config) {
|
||||
if cfg.Width != 0 {
|
||||
d.width = cfg.Width
|
||||
} else {
|
||||
d.width = 128
|
||||
}
|
||||
if cfg.Height != 0 {
|
||||
d.height = cfg.Height
|
||||
} else {
|
||||
d.height = 64
|
||||
}
|
||||
if cfg.Address != 0 {
|
||||
d.bus.setAddress(cfg.Address)
|
||||
}
|
||||
if cfg.VccState != 0 {
|
||||
d.vccState = cfg.VccState
|
||||
} else {
|
||||
d.vccState = SWITCHCAPVCC
|
||||
}
|
||||
d.bufferSize = d.width * d.height / 8
|
||||
d.buffer = make([]byte, d.bufferSize)
|
||||
|
||||
d.bus.configure()
|
||||
|
||||
// busyWaitDelay(100 * time.Nanosecond)
|
||||
time.Sleep(100 * time.Nanosecond)
|
||||
d.Command(DISPLAYOFF)
|
||||
d.Command(SETDISPLAYCLOCKDIV)
|
||||
d.Command(0x80)
|
||||
d.Command(SETMULTIPLEX)
|
||||
d.Command(uint8(d.height - 1))
|
||||
d.Command(SETDISPLAYOFFSET)
|
||||
d.Command(0x0)
|
||||
d.Command(SETSTARTLINE | 0x0)
|
||||
d.Command(CHARGEPUMP)
|
||||
if d.vccState == EXTERNALVCC {
|
||||
d.Command(0x10)
|
||||
} else {
|
||||
d.Command(0x14)
|
||||
}
|
||||
d.Command(MEMORYMODE)
|
||||
d.Command(0x00)
|
||||
d.Command(SEGREMAP | 0x1)
|
||||
d.Command(COMSCANDEC)
|
||||
|
||||
if (d.width == 128 && d.height == 64) || (d.width == 64 && d.height == 48) { // 128x64 or 64x48
|
||||
d.Command(SETCOMPINS)
|
||||
d.Command(0x12)
|
||||
d.Command(SETCONTRAST)
|
||||
if d.vccState == EXTERNALVCC {
|
||||
d.Command(0x9F)
|
||||
} else {
|
||||
d.Command(0xCF)
|
||||
}
|
||||
} else if d.width == 128 && d.height == 32 { // 128x32
|
||||
d.Command(SETCOMPINS)
|
||||
d.Command(0x02)
|
||||
d.Command(SETCONTRAST)
|
||||
d.Command(0x8F)
|
||||
} else if d.width == 96 && d.height == 16 { // 96x16
|
||||
d.Command(SETCOMPINS)
|
||||
d.Command(0x2)
|
||||
d.Command(SETCONTRAST)
|
||||
if d.vccState == EXTERNALVCC {
|
||||
d.Command(0x10)
|
||||
} else {
|
||||
d.Command(0xAF)
|
||||
}
|
||||
} else {
|
||||
// fail silently, it might work
|
||||
println("there's no configuration for this display's size")
|
||||
}
|
||||
|
||||
d.Command(SETPRECHARGE)
|
||||
if d.vccState == EXTERNALVCC {
|
||||
d.Command(0x22)
|
||||
} else {
|
||||
d.Command(0xF1)
|
||||
}
|
||||
d.Command(SETVCOMDETECT)
|
||||
d.Command(0x40)
|
||||
d.Command(DISPLAYALLON_RESUME)
|
||||
d.Command(NORMALDISPLAY)
|
||||
d.Command(DEACTIVATE_SCROLL)
|
||||
d.Command(DISPLAYON)
|
||||
}
|
||||
|
||||
// ClearBuffer clears the image buffer
|
||||
func (d *Device) ClearBuffer() {
|
||||
for i := int16(0); i < d.bufferSize; i++ {
|
||||
d.buffer[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// ClearDisplay clears the image buffer and clear the display
|
||||
func (d *Device) ClearDisplay() {
|
||||
d.ClearBuffer()
|
||||
d.Display()
|
||||
}
|
||||
|
||||
// Display sends the whole buffer to the screen
|
||||
func (d *Device) Display() error {
|
||||
// In the 128x64 (SPI) screen resetting to 0x0 after 128 times corrupt the buffer
|
||||
// Since we're printing the whole buffer, avoid resetting it
|
||||
if d.width != 128 || d.height != 64 {
|
||||
d.Command(COLUMNADDR)
|
||||
d.Command(0)
|
||||
d.Command(uint8(d.width - 1))
|
||||
d.Command(PAGEADDR)
|
||||
d.Command(0)
|
||||
d.Command(uint8(d.height/8) - 1)
|
||||
}
|
||||
|
||||
for pg := uint8(0); pg < uint8(d.height/8); pg++ {
|
||||
d.Command(0xB0 | (pg & 0x07)) // SET_PAGE_ADDR
|
||||
d.Command(SETLOWCOLUMN | 2)
|
||||
d.Command(SETHIGHCOLUMN | 0)
|
||||
d.Tx(d.buffer[uint16(pg)*0x80:uint16(pg+1)*0x80], false)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPixel enables or disables a pixel in the buffer
|
||||
// color.RGBA{0, 0, 0, 255} is consider transparent, anything else
|
||||
// with enable a pixel on the screen
|
||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
||||
return
|
||||
}
|
||||
byteIndex := x + (y/8)*d.width
|
||||
if c.R != 0 || c.G != 0 || c.B != 0 {
|
||||
d.buffer[byteIndex] |= 1 << uint8(y%8)
|
||||
} else {
|
||||
d.buffer[byteIndex] &^= 1 << uint8(y%8)
|
||||
}
|
||||
}
|
||||
|
||||
// GetPixel returns if the specified pixel is on (true) or off (false)
|
||||
func (d *Device) GetPixel(x int16, y int16) bool {
|
||||
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
||||
return false
|
||||
}
|
||||
byteIndex := x + (y/8)*d.width
|
||||
return (d.buffer[byteIndex] >> uint8(y%8) & 0x1) == 1
|
||||
}
|
||||
|
||||
// SetBuffer changes the whole buffer at once
|
||||
func (d *Device) SetBuffer(buffer []byte) error {
|
||||
if int16(len(buffer)) != d.bufferSize {
|
||||
//return ErrBuffer
|
||||
return errors.New("wrong size buffer")
|
||||
}
|
||||
for i := int16(0); i < d.bufferSize; i++ {
|
||||
d.buffer[i] = buffer[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) SetScroll(line int16) {
|
||||
d.Command(SETSTARTLINE + uint8(line&0b111111))
|
||||
}
|
||||
|
||||
// Command sends a command to the display
|
||||
func (d *Device) Command(command uint8) {
|
||||
d.cmdbuf[0] = command
|
||||
d.bus.tx(d.cmdbuf[:], true)
|
||||
}
|
||||
|
||||
// setAddress sets the address to the I2C bus
|
||||
func (b *I2CBus) setAddress(address uint16) {
|
||||
b.Address = address
|
||||
}
|
||||
|
||||
// setAddress does nothing, but it's required to avoid reflection
|
||||
func (b *SPIBus) setAddress(address uint16) {
|
||||
// do nothing
|
||||
println("trying to Configure an address on a SPI device")
|
||||
}
|
||||
|
||||
// configure does nothing, but it's required to avoid reflection
|
||||
func (b *I2CBus) configure() {}
|
||||
|
||||
// configure configures some pins with the SPI bus
|
||||
func (b *SPIBus) configure() {
|
||||
b.csPin.Low()
|
||||
b.dcPin.Low()
|
||||
b.resetPin.Low()
|
||||
|
||||
b.resetPin.High()
|
||||
// busyWaitDelay(time.Millisecond)
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
b.resetPin.Low()
|
||||
// busyWaitDelay(10 * time.Millisecond)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
b.resetPin.High()
|
||||
}
|
||||
|
||||
// Tx sends data to the display
|
||||
func (d *Device) Tx(data []byte, isCommand bool) {
|
||||
d.bus.tx(data, isCommand)
|
||||
}
|
||||
|
||||
// tx sends data to the display (I2CBus implementation)
|
||||
func (b *I2CBus) tx(data []byte, isCommand bool) {
|
||||
if isCommand {
|
||||
b.wire.WriteRegister(uint8(b.Address), 0x00, data)
|
||||
} else {
|
||||
b.wire.WriteRegister(uint8(b.Address), 0x40, data)
|
||||
}
|
||||
}
|
||||
|
||||
// tx sends data to the display (SPIBus implementation)
|
||||
func (b *SPIBus) tx(data []byte, isCommand bool) {
|
||||
if isCommand {
|
||||
b.csPin.High()
|
||||
riseTimeDelay()
|
||||
b.dcPin.Low()
|
||||
b.csPin.Low()
|
||||
|
||||
b.wire.Tx(data, nil)
|
||||
b.csPin.High()
|
||||
} else {
|
||||
b.csPin.High()
|
||||
riseTimeDelay()
|
||||
b.dcPin.High()
|
||||
b.csPin.Low()
|
||||
|
||||
b.wire.Tx(data, nil)
|
||||
b.csPin.High()
|
||||
}
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
return d.width, d.height
|
||||
}
|
||||
|
||||
// TODO: is this really necessary? seems to work fine without this on macropad-rp2040 at least
|
||||
func riseTimeDelay() {
|
||||
busyWaitDelay(1 * time.Microsecond)
|
||||
}
|
||||
|
||||
func busyWaitDelay(duration time.Duration) {
|
||||
for start := time.Now(); time.Since(start) < duration; {
|
||||
}
|
||||
}
|
||||
+36
-57
@@ -206,48 +206,42 @@ const (
|
||||
SX126X_PA_RAMP_3400U = 0x07 // 7 0 3400 us
|
||||
|
||||
//SX126X_CMD_SET_MODULATION_PARAMS
|
||||
SX126X_GFSK_FILTER_NONE = 0x00 // 7 0 GFSK filter: none
|
||||
SX126X_GFSK_FILTER_GAUSS_0_3 = 0x08 // 7 0 Gaussian, BT = 0.3
|
||||
SX126X_GFSK_FILTER_GAUSS_0_5 = 0x09 // 7 0 Gaussian, BT = 0.5
|
||||
SX126X_GFSK_FILTER_GAUSS_0_7 = 0x0A // 7 0 Gaussian, BT = 0.7
|
||||
SX126X_GFSK_FILTER_GAUSS_1 = 0x0B // 7 0 Gaussian, BT = 1
|
||||
SX126X_GFSK_RX_BW_4_8 = 0x1F // 7 0 GFSK Rx bandwidth: 4.8 kHz
|
||||
SX126X_GFSK_RX_BW_5_8 = 0x17 // 7 0 5.8 kHz
|
||||
SX126X_GFSK_RX_BW_7_3 = 0x0F // 7 0 7.3 kHz
|
||||
SX126X_GFSK_RX_BW_9_7 = 0x1E // 7 0 9.7 kHz
|
||||
SX126X_GFSK_RX_BW_11_7 = 0x16 // 7 0 11.7 kHz
|
||||
SX126X_GFSK_RX_BW_14_6 = 0x0E // 7 0 14.6 kHz
|
||||
SX126X_GFSK_RX_BW_19_5 = 0x1D // 7 0 19.5 kHz
|
||||
SX126X_GFSK_RX_BW_23_4 = 0x15 // 7 0 23.4 kHz
|
||||
SX126X_GFSK_RX_BW_29_3 = 0x0D // 7 0 29.3 kHz
|
||||
SX126X_GFSK_RX_BW_39_0 = 0x1C // 7 0 39.0 kHz
|
||||
SX126X_GFSK_RX_BW_46_9 = 0x14 // 7 0 46.9 kHz
|
||||
SX126X_GFSK_RX_BW_58_6 = 0x0C // 7 0 58.6 kHz
|
||||
SX126X_GFSK_RX_BW_78_2 = 0x1B // 7 0 78.2 kHz
|
||||
SX126X_GFSK_RX_BW_93_8 = 0x13 // 7 0 93.8 kHz
|
||||
SX126X_GFSK_RX_BW_117_3 = 0x0B // 7 0 117.3 kHz
|
||||
SX126X_GFSK_RX_BW_156_2 = 0x1A // 7 0 156.2 kHz
|
||||
SX126X_GFSK_RX_BW_187_2 = 0x12 // 7 0 187.2 kHz
|
||||
SX126X_GFSK_RX_BW_234_3 = 0x0A // 7 0 234.3 kHz
|
||||
SX126X_GFSK_RX_BW_312_0 = 0x19 // 7 0 312.0 kHz
|
||||
SX126X_GFSK_RX_BW_373_6 = 0x11 // 7 0 373.6 kHz
|
||||
SX126X_GFSK_RX_BW_467_0 = 0x09 // 7 0 467.0 kHz
|
||||
SX126X_LORA_BW_7_8 = 0x00 // 7 0 LoRa bandwidth: 7.8 kHz
|
||||
SX126X_LORA_BW_10_4 = 0x08 // 7 0 10.4 kHz
|
||||
SX126X_LORA_BW_15_6 = 0x01 // 7 0 15.6 kHz
|
||||
SX126X_LORA_BW_20_8 = 0x09 // 7 0 20.8 kHz
|
||||
SX126X_LORA_BW_31_25 = 0x02 // 7 0 31.25 kHz
|
||||
SX126X_LORA_BW_41_7 = 0x0A // 7 0 41.7 kHz
|
||||
SX126X_LORA_BW_62_5 = 0x03 // 7 0 62.5 kHz
|
||||
SX126X_LORA_BW_125_0 = 0x04 // 7 0 125.0 kHz
|
||||
SX126X_LORA_BW_250_0 = 0x05 // 7 0 250.0 kHz
|
||||
SX126X_LORA_BW_500_0 = 0x06 // 7 0 500.0 kHz
|
||||
SX126X_LORA_CR_4_5 = 0x01 // 7 0 LoRa coding rate: 4/5
|
||||
SX126X_LORA_CR_4_6 = 0x02 // 7 0 4/6
|
||||
SX126X_LORA_CR_4_7 = 0x03 // 7 0 4/7
|
||||
SX126X_LORA_CR_4_8 = 0x04 // 7 0 4/8
|
||||
SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF = 0x00 // 7 0 LoRa low data rate optimization: disabled
|
||||
SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON = 0x01 // 7 0 enabled
|
||||
SX126X_GFSK_FILTER_NONE = 0x00 // 7 0 GFSK filter: none
|
||||
SX126X_GFSK_FILTER_GAUSS_0_3 = 0x08 // 7 0 Gaussian, BT = 0.3
|
||||
SX126X_GFSK_FILTER_GAUSS_0_5 = 0x09 // 7 0 Gaussian, BT = 0.5
|
||||
SX126X_GFSK_FILTER_GAUSS_0_7 = 0x0A // 7 0 Gaussian, BT = 0.7
|
||||
SX126X_GFSK_FILTER_GAUSS_1 = 0x0B // 7 0 Gaussian, BT = 1
|
||||
SX126X_GFSK_RX_BW_4_8 = 0x1F // 7 0 GFSK Rx bandwidth: 4.8 kHz
|
||||
SX126X_GFSK_RX_BW_5_8 = 0x17 // 7 0 5.8 kHz
|
||||
SX126X_GFSK_RX_BW_7_3 = 0x0F // 7 0 7.3 kHz
|
||||
SX126X_GFSK_RX_BW_9_7 = 0x1E // 7 0 9.7 kHz
|
||||
SX126X_GFSK_RX_BW_11_7 = 0x16 // 7 0 11.7 kHz
|
||||
SX126X_GFSK_RX_BW_14_6 = 0x0E // 7 0 14.6 kHz
|
||||
SX126X_GFSK_RX_BW_19_5 = 0x1D // 7 0 19.5 kHz
|
||||
SX126X_GFSK_RX_BW_23_4 = 0x15 // 7 0 23.4 kHz
|
||||
SX126X_GFSK_RX_BW_29_3 = 0x0D // 7 0 29.3 kHz
|
||||
SX126X_GFSK_RX_BW_39_0 = 0x1C // 7 0 39.0 kHz
|
||||
SX126X_GFSK_RX_BW_46_9 = 0x14 // 7 0 46.9 kHz
|
||||
SX126X_GFSK_RX_BW_58_6 = 0x0C // 7 0 58.6 kHz
|
||||
SX126X_GFSK_RX_BW_78_2 = 0x1B // 7 0 78.2 kHz
|
||||
SX126X_GFSK_RX_BW_93_8 = 0x13 // 7 0 93.8 kHz
|
||||
SX126X_GFSK_RX_BW_117_3 = 0x0B // 7 0 117.3 kHz
|
||||
SX126X_GFSK_RX_BW_156_2 = 0x1A // 7 0 156.2 kHz
|
||||
SX126X_GFSK_RX_BW_187_2 = 0x12 // 7 0 187.2 kHz
|
||||
SX126X_GFSK_RX_BW_234_3 = 0x0A // 7 0 234.3 kHz
|
||||
SX126X_GFSK_RX_BW_312_0 = 0x19 // 7 0 312.0 kHz
|
||||
SX126X_GFSK_RX_BW_373_6 = 0x11 // 7 0 373.6 kHz
|
||||
SX126X_GFSK_RX_BW_467_0 = 0x09 // 7 0 467.0 kHz
|
||||
SX126X_LORA_BW_7_8 = 0x00 // 7 0 LoRa bandwidth: 7.8 kHz
|
||||
SX126X_LORA_BW_10_4 = 0x08 // 7 0 10.4 kHz
|
||||
SX126X_LORA_BW_15_6 = 0x01 // 7 0 15.6 kHz
|
||||
SX126X_LORA_BW_20_8 = 0x09 // 7 0 20.8 kHz
|
||||
SX126X_LORA_BW_31_25 = 0x02 // 7 0 31.25 kHz
|
||||
SX126X_LORA_BW_41_7 = 0x0A // 7 0 41.7 kHz
|
||||
SX126X_LORA_BW_62_5 = 0x03 // 7 0 62.5 kHz
|
||||
SX126X_LORA_BW_125_0 = 0x04 // 7 0 125.0 kHz
|
||||
SX126X_LORA_BW_250_0 = 0x05 // 7 0 250.0 kHz
|
||||
SX126X_LORA_BW_500_0 = 0x06 // 7 0 500.0 kHz
|
||||
|
||||
//SX126X_CMD_SET_PACKET_PARAMS
|
||||
SX126X_GFSK_PREAMBLE_DETECT_OFF = 0x00 // 7 0 GFSK minimum preamble length before reception starts: detector disabled
|
||||
@@ -267,12 +261,6 @@ const (
|
||||
SX126X_GFSK_CRC_2_BYTE_INV = 0x06 // 7 0 2 byte, inverted
|
||||
SX126X_GFSK_WHITENING_OFF = 0x00 // 7 0 GFSK data whitening: disabled
|
||||
SX126X_GFSK_WHITENING_ON = 0x01 // 7 0 enabled
|
||||
SX126X_LORA_HEADER_EXPLICIT = 0x00 // 7 0 LoRa header mode: explicit
|
||||
SX126X_LORA_HEADER_IMPLICIT = 0x01 // 7 0 implicit
|
||||
SX126X_LORA_CRC_OFF = 0x00 // 7 0 LoRa CRC mode: disabled
|
||||
SX126X_LORA_CRC_ON = 0x01 // 7 0 enabled
|
||||
SX126X_LORA_IQ_STANDARD = 0x00 // 7 0 LoRa IQ setup: standard
|
||||
SX126X_LORA_IQ_INVERTED = 0x01 // 7 0 inverted
|
||||
|
||||
//SX126X_CMD_SET_CAD_PARAMS
|
||||
SX126X_CAD_ON_1_SYMB = 0x00 // 7 0 number of symbols used for CAD: 1
|
||||
@@ -323,13 +311,4 @@ const (
|
||||
|
||||
SX126X_LORA_MAC_PUBLIC_SYNCWORD = 0x3444
|
||||
SX126X_LORA_MAC_PRIVATE_SYNCWORD = 0x1424
|
||||
|
||||
SX126X_LORA_SF5 = 0x05
|
||||
SX126X_LORA_SF6 = 0x06
|
||||
SX126X_LORA_SF7 = 0x07
|
||||
SX126X_LORA_SF8 = 0x08
|
||||
SX126X_LORA_SF9 = 0x09
|
||||
SX126X_LORA_SF10 = 0x0A
|
||||
SX126X_LORA_SF11 = 0x0B
|
||||
SX126X_LORA_SF12 = 0x0C
|
||||
)
|
||||
|
||||
@@ -7,11 +7,12 @@ import (
|
||||
"errors"
|
||||
"machine"
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/lora"
|
||||
)
|
||||
|
||||
// New creates a new SX126x connection.
|
||||
func New(spi drivers.SPI) *Device {
|
||||
c := make(chan RadioEvent, 10)
|
||||
c := make(chan lora.RadioEvent, 10)
|
||||
d := Device{
|
||||
spi: spi,
|
||||
radioEventChan: c,
|
||||
|
||||
+96
-89
@@ -7,7 +7,10 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/lora"
|
||||
)
|
||||
|
||||
// SX126X radio transceiver RF_IN and RF_OUT may be connected
|
||||
@@ -30,22 +33,6 @@ const (
|
||||
RFSWITCH_TX_HP = iota
|
||||
)
|
||||
|
||||
const (
|
||||
RadioEventRxDone = iota
|
||||
RadioEventTxDone = iota
|
||||
RadioEventTimeout = iota
|
||||
RadioEventWatchdog = iota
|
||||
RadioEventCrcError = iota
|
||||
RadioEventUnhandled = iota
|
||||
)
|
||||
|
||||
// RadioEvent are used for communicating in the radio Event Channel
|
||||
type RadioEvent struct {
|
||||
EventType int
|
||||
IRQStatus uint16
|
||||
EventData []byte
|
||||
}
|
||||
|
||||
const (
|
||||
PERIOD_PER_SEC = (uint32)(1000000 / 15.625) // SX1261 DS 13.1.4
|
||||
SPI_BUFFER_SIZE = 256
|
||||
@@ -53,38 +40,20 @@ const (
|
||||
|
||||
// Device wraps an SPI connection to a SX127x device.
|
||||
type Device struct {
|
||||
spi drivers.SPI // SPI bus for module communication
|
||||
radioEventChan chan RadioEvent // Channel for Receiving events
|
||||
loraConf LoraConfig // Current Lora configuration
|
||||
rfswitch RFSwitch // RF Switch, if any
|
||||
deepSleep bool // Internal Sleep state
|
||||
deviceType int // sx1261,sx1262,sx1268 (defaults sx1261)
|
||||
spi drivers.SPI // SPI bus for module communication
|
||||
rstPin, csPin machine.Pin // GPIOs for reset and chip select
|
||||
radioEventChan chan lora.RadioEvent // Channel for Receiving events
|
||||
loraConf lora.Config // Current Lora configuration
|
||||
rfswitch RFSwitch // RF Switch, if any
|
||||
deepSleep bool // Internal Sleep state
|
||||
deviceType int // sx1261,sx1262,sx1268 (defaults sx1261)
|
||||
spiBuffer [SPI_BUFFER_SIZE]uint8
|
||||
}
|
||||
|
||||
// Config holds the LoRa configuration parameters
|
||||
type LoraConfig struct {
|
||||
Freq uint32 // Frequency
|
||||
Cr uint8 // Coding Rate
|
||||
Sf uint8 // Spread Factor
|
||||
Bw uint8 // Bandwidth
|
||||
Ldr uint8 // Low Data Rate
|
||||
Preamble uint16 // PreambleLength
|
||||
SyncWord uint16 // Sync Word
|
||||
HeaderType uint8 // Header : Implicit/explicit
|
||||
Crc uint8 // CRC : Yes/No
|
||||
Iq uint8 // iq : Standard/inverted
|
||||
LoraTxPowerDBm int8 // Tx power in Dbm
|
||||
}
|
||||
|
||||
const (
|
||||
SX126X_RTC_FREQ_IN_HZ uint32 = 64000
|
||||
)
|
||||
|
||||
var (
|
||||
errUndefinedLoraConf = errors.New("Undefined Lora configuration")
|
||||
)
|
||||
|
||||
// --------------------------------------------------
|
||||
// Helper functions
|
||||
// --------------------------------------------------
|
||||
@@ -100,14 +69,8 @@ func timeoutMsToRtcSteps(timeoutMs uint32) uint32 {
|
||||
// Channel and events
|
||||
//
|
||||
// --------------------------------------------------
|
||||
// NewRadioEvent() returns a new RadioEvent that can be used in the RadioChannel
|
||||
func NewRadioEvent(eType int, irqStatus uint16, eData []byte) RadioEvent {
|
||||
r := RadioEvent{EventType: eType, IRQStatus: irqStatus, EventData: eData}
|
||||
return r
|
||||
}
|
||||
|
||||
// Get the RadioEvent channel of the device
|
||||
func (d *Device) GetRadioEventChan() chan RadioEvent {
|
||||
func (d *Device) GetRadioEventChan() chan lora.RadioEvent {
|
||||
return d.radioEventChan
|
||||
}
|
||||
|
||||
@@ -126,6 +89,13 @@ func (d *Device) SetRfSwitch(rfswitch RFSwitch) {
|
||||
// Operational modes functions
|
||||
// --------------------------------------------------
|
||||
|
||||
func (d *Device) Reset() {
|
||||
d.rstPin.Low()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
d.rstPin.High()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// DetectDevice() tries to detect the radio module by changing SyncWord value
|
||||
func (d *Device) DetectDevice() bool {
|
||||
bak := d.GetSyncWord()
|
||||
@@ -163,7 +133,7 @@ func (d *Device) SetTxContinuousWave() {
|
||||
}
|
||||
|
||||
// SetTxContinuousPreamble set device in test mode to constantly modulate LoRa preamble symbols.
|
||||
// Take care to initialize all Lora settings like it's done in LoraTx before calling this function
|
||||
// Take care to initialize all Lora settings like it's done in Tx before calling this function
|
||||
// If you don't init properly all the settings, it'll fail
|
||||
func (d *Device) SetTxContinuousPreamble() {
|
||||
if d.rfswitch != nil {
|
||||
@@ -387,11 +357,11 @@ func (d *Device) SetPacketType(packetType uint8) {
|
||||
}
|
||||
|
||||
// SetSyncWord defines the Sync Word to yse
|
||||
func (d *Device) SetSyncWord(syncword uint16) {
|
||||
func (d *Device) SetSyncWord(sw uint16) {
|
||||
var p [2]uint8
|
||||
d.loraConf.SyncWord = syncword
|
||||
p[0] = uint8((syncword >> 8) & 0xFF)
|
||||
p[1] = uint8((syncword >> 0) & 0xFF)
|
||||
d.loraConf.SyncWord = sw
|
||||
p[0] = uint8((d.loraConf.SyncWord >> 8) & 0xFF)
|
||||
p[1] = uint8((d.loraConf.SyncWord >> 0) & 0xFF)
|
||||
d.WriteRegister(SX126X_REG_LORA_SYNC_WORD_MSB, p[:])
|
||||
}
|
||||
|
||||
@@ -402,8 +372,8 @@ func (d *Device) GetSyncWord() uint16 {
|
||||
return r
|
||||
}
|
||||
|
||||
// SetLoraPublicNetwork sets Sync Word to 0x3444 (Public) or 0x1424 (Private)
|
||||
func (d *Device) SetLoraPublicNetwork(enable bool) {
|
||||
// SetPublicNetwork sets Sync Word to 0x3444 (Public) or 0x1424 (Private)
|
||||
func (d *Device) SetPublicNetwork(enable bool) {
|
||||
if enable {
|
||||
d.SetSyncWord(SX126X_LORA_MAC_PUBLIC_SYNCWORD)
|
||||
} else {
|
||||
@@ -537,47 +507,47 @@ func (d *Device) ExecGetCommand(cmd uint8, size uint8) []uint8 {
|
||||
// Configuration
|
||||
//
|
||||
|
||||
// SetLoraFrequency() Sets current Lora Frequency
|
||||
// SetFrequency() Sets current Lora Frequency
|
||||
// NB: Change will be applied at next RX / TX
|
||||
func (d *Device) SetLoraFrequency(freq uint32) {
|
||||
func (d *Device) SetFrequency(freq uint32) {
|
||||
d.loraConf.Freq = d.loraConf.Freq
|
||||
}
|
||||
|
||||
// SetLoraIqMode() defines the current IQ Mode (Standard/Inverted)
|
||||
// SetIqMode() defines the current IQ Mode (Standard/Inverted)
|
||||
// NB: Change will be applied at next RX / TX
|
||||
func (d *Device) SetLoraIqMode(mode uint8) {
|
||||
func (d *Device) SetIqMode(mode uint8) {
|
||||
if mode == 0 {
|
||||
d.loraConf.Iq = SX126X_LORA_IQ_STANDARD
|
||||
d.loraConf.Iq = lora.IQStandard
|
||||
} else {
|
||||
d.loraConf.Iq = SX126X_LORA_IQ_INVERTED
|
||||
d.loraConf.Iq = lora.IQInverted
|
||||
}
|
||||
}
|
||||
|
||||
// SetLoraCodingRate() sets current Lora Coding Rate
|
||||
// SetCodingRate() sets current Lora Coding Rate
|
||||
// NB: Change will be applied at next RX / TX
|
||||
func (d *Device) SetLoraCodingRate(cr uint8) {
|
||||
func (d *Device) SetCodingRate(cr uint8) {
|
||||
d.loraConf.Cr = cr
|
||||
}
|
||||
|
||||
// SetLoraBandwidth() sets current Lora Bandwidth
|
||||
// SetBandwidth() sets current Lora Bandwidth
|
||||
// NB: Change will be applied at next RX / TX
|
||||
func (d *Device) SetLoraBandwidth(bw uint8) {
|
||||
func (d *Device) SetBandwidth(bw uint8) {
|
||||
d.loraConf.Cr = bw
|
||||
}
|
||||
|
||||
// SetLoraCrc() sets current CRC mode (ON/OFF)
|
||||
// SetCrc() sets current CRC mode (ON/OFF)
|
||||
// NB: Change will be applied at next RX / TX
|
||||
func (d *Device) SetLoraCrc(enable bool) {
|
||||
func (d *Device) SetCrc(enable bool) {
|
||||
if enable {
|
||||
d.loraConf.Crc = SX126X_LORA_CRC_ON
|
||||
d.loraConf.Crc = lora.CRCOn
|
||||
} else {
|
||||
d.loraConf.Crc = SX126X_LORA_CRC_OFF
|
||||
d.loraConf.Crc = lora.CRCOn
|
||||
}
|
||||
}
|
||||
|
||||
// SetLoraSpreadingFactor setc surrent Lora Spreading Factor
|
||||
// SetSpreadingFactor setc surrent Lora Spreading Factor
|
||||
// NB: Change will be applied at next RX / TX
|
||||
func (d *Device) SetLoraSpreadingFactor(sf uint8) {
|
||||
func (d *Device) SetSpreadingFactor(sf uint8) {
|
||||
d.loraConf.Sf = sf
|
||||
}
|
||||
|
||||
@@ -587,9 +557,10 @@ func (d *Device) SetLoraSpreadingFactor(sf uint8) {
|
||||
//
|
||||
|
||||
// LoraConfig() defines Lora configuration for next Lora operations
|
||||
func (d *Device) LoraConfig(cnf LoraConfig) {
|
||||
func (d *Device) LoraConfig(cnf lora.Config) {
|
||||
// Save given configuration
|
||||
d.loraConf = cnf
|
||||
d.loraConf.SyncWord = syncword(int(cnf.SyncWord))
|
||||
// Switch to standby prior to configuration changes
|
||||
d.SetStandby()
|
||||
// Clear errors, disable radio interrupts for the moment
|
||||
@@ -599,24 +570,25 @@ func (d *Device) LoraConfig(cnf LoraConfig) {
|
||||
// Define radio operation mode
|
||||
d.SetPacketType(SX126X_PACKET_TYPE_LORA)
|
||||
d.SetRfFrequency(d.loraConf.Freq)
|
||||
d.SetModulationParams(d.loraConf.Sf, d.loraConf.Bw, d.loraConf.Cr, d.loraConf.Ldr)
|
||||
d.SetModulationParams(d.loraConf.Sf, bandwidth(d.loraConf.Bw), d.loraConf.Cr, d.loraConf.Ldr)
|
||||
d.SetTxParams(d.loraConf.LoraTxPowerDBm, SX126X_PA_RAMP_200U)
|
||||
d.SetSyncWord(d.loraConf.SyncWord)
|
||||
d.SetBufferBaseAddress(0, 0)
|
||||
}
|
||||
|
||||
// LoraTx sends a lora packet, (with timeout)
|
||||
func (d *Device) LoraTx(pkt []uint8, timeoutMs uint32) error {
|
||||
|
||||
// Tx sends a lora packet, (with timeout)
|
||||
func (d *Device) Tx(pkt []uint8, timeoutMs uint32) error {
|
||||
if d.loraConf.Freq == 0 {
|
||||
return errUndefinedLoraConf
|
||||
return lora.ErrUndefinedLoraConf
|
||||
}
|
||||
|
||||
if d.rfswitch != nil {
|
||||
err := d.rfswitch.SetRfSwitchMode(RFSWITCH_TX_HP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.ClearIrqStatus(SX126X_IRQ_ALL)
|
||||
irqVal := uint16(SX126X_IRQ_TX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR)
|
||||
d.SetStandby()
|
||||
@@ -625,45 +597,46 @@ func (d *Device) LoraTx(pkt []uint8, timeoutMs uint32) error {
|
||||
d.SetTxParams(d.loraConf.LoraTxPowerDBm, SX126X_PA_RAMP_200U)
|
||||
d.SetBufferBaseAddress(0, 0)
|
||||
d.WriteBuffer(pkt)
|
||||
d.SetModulationParams(d.loraConf.Sf, d.loraConf.Bw, d.loraConf.Cr, d.loraConf.Ldr)
|
||||
d.SetModulationParams(d.loraConf.Sf, bandwidth(d.loraConf.Bw), d.loraConf.Cr, d.loraConf.Ldr)
|
||||
d.SetPacketParam(d.loraConf.Preamble, d.loraConf.HeaderType, d.loraConf.Crc, uint8(len(pkt)), d.loraConf.Iq)
|
||||
d.SetDioIrqParams(irqVal, irqVal, SX126X_IRQ_NONE, SX126X_IRQ_NONE)
|
||||
d.SetSyncWord(d.loraConf.SyncWord)
|
||||
d.SetTx(timeoutMsToRtcSteps(timeoutMs))
|
||||
|
||||
msg := <-d.GetRadioEventChan()
|
||||
if msg.EventType != RadioEventTxDone {
|
||||
if msg.EventType != lora.RadioEventTxDone {
|
||||
return errors.New("Unexpected Radio Event while TX")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoraRx tries to receive a Lora packet (with timeout in milliseconds)
|
||||
func (d *Device) LoraRx(timeoutMs uint32) ([]uint8, error) {
|
||||
|
||||
func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
|
||||
if d.loraConf.Freq == 0 {
|
||||
return nil, errUndefinedLoraConf
|
||||
return nil, lora.ErrUndefinedLoraConf
|
||||
}
|
||||
|
||||
if d.rfswitch != nil {
|
||||
err := d.rfswitch.SetRfSwitchMode(RFSWITCH_RX)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
d.ClearIrqStatus(SX126X_IRQ_ALL)
|
||||
irqVal := uint16(SX126X_IRQ_RX_DONE | SX126X_IRQ_TIMEOUT | SX126X_IRQ_CRC_ERR)
|
||||
d.SetStandby()
|
||||
d.SetBufferBaseAddress(0, 0)
|
||||
d.SetModulationParams(d.loraConf.Sf, d.loraConf.Bw, d.loraConf.Cr, d.loraConf.Ldr)
|
||||
d.SetModulationParams(d.loraConf.Sf, bandwidth(d.loraConf.Bw), d.loraConf.Cr, d.loraConf.Ldr)
|
||||
d.SetPacketParam(d.loraConf.Preamble, d.loraConf.HeaderType, d.loraConf.Crc, 0xFF, d.loraConf.Iq)
|
||||
d.SetDioIrqParams(irqVal, irqVal, SX126X_IRQ_NONE, SX126X_IRQ_NONE)
|
||||
d.SetRx(timeoutMsToRtcSteps(timeoutMs))
|
||||
|
||||
msg := <-d.GetRadioEventChan()
|
||||
|
||||
if msg.EventType == RadioEventTimeout {
|
||||
if msg.EventType == lora.RadioEventTimeout {
|
||||
return nil, nil
|
||||
} else if msg.EventType != RadioEventRxDone {
|
||||
} else if msg.EventType != lora.RadioEventRxDone {
|
||||
return nil, errors.New("Unexpected Radio Event while RX")
|
||||
}
|
||||
|
||||
@@ -683,19 +656,53 @@ func (d *Device) HandleInterrupt() {
|
||||
rChan := d.GetRadioEventChan()
|
||||
|
||||
if (st & SX126X_IRQ_RX_DONE) > 0 {
|
||||
rChan <- NewRadioEvent(RadioEventRxDone, st, nil)
|
||||
rChan <- lora.NewRadioEvent(lora.RadioEventRxDone, st, nil)
|
||||
}
|
||||
|
||||
if (st & SX126X_IRQ_TX_DONE) > 0 {
|
||||
rChan <- NewRadioEvent(RadioEventTxDone, st, nil)
|
||||
rChan <- lora.NewRadioEvent(lora.RadioEventTxDone, st, nil)
|
||||
}
|
||||
|
||||
if (st & SX126X_IRQ_TIMEOUT) > 0 {
|
||||
rChan <- NewRadioEvent(RadioEventTimeout, st, nil)
|
||||
rChan <- lora.NewRadioEvent(lora.RadioEventTimeout, st, nil)
|
||||
}
|
||||
|
||||
if (st & SX126X_IRQ_CRC_ERR) > 0 {
|
||||
rChan <- NewRadioEvent(RadioEventCrcError, st, nil)
|
||||
rChan <- lora.NewRadioEvent(lora.RadioEventCrcError, st, nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func bandwidth(bw uint8) uint8 {
|
||||
switch bw {
|
||||
case lora.Bandwidth_7_8:
|
||||
return SX126X_LORA_BW_7_8
|
||||
case lora.Bandwidth_10_4:
|
||||
return SX126X_LORA_BW_10_4
|
||||
case lora.Bandwidth_15_6:
|
||||
return SX126X_LORA_BW_15_6
|
||||
case lora.Bandwidth_20_8:
|
||||
return SX126X_LORA_BW_20_8
|
||||
case lora.Bandwidth_31_25:
|
||||
return SX126X_LORA_BW_31_25
|
||||
case lora.Bandwidth_41_7:
|
||||
return SX126X_LORA_BW_41_7
|
||||
case lora.Bandwidth_62_5:
|
||||
return SX126X_LORA_BW_62_5
|
||||
case lora.Bandwidth_125_0:
|
||||
return SX126X_LORA_BW_125_0
|
||||
case lora.Bandwidth_250_0:
|
||||
return SX126X_LORA_BW_250_0
|
||||
case lora.Bandwidth_500_0:
|
||||
return SX126X_LORA_BW_500_0
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func syncword(sw int) uint16 {
|
||||
if sw == lora.SyncPublic {
|
||||
return SX126X_LORA_MAC_PUBLIC_SYNCWORD
|
||||
}
|
||||
return SX126X_LORA_MAC_PRIVATE_SYNCWORD
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package sx127x
|
||||
|
||||
const (
|
||||
// registers
|
||||
SX127X_REG_FIFO = 0x00
|
||||
SX127X_REG_OP_MODE = 0x01
|
||||
SX127X_REG_FRF_MSB = 0x06
|
||||
SX127X_REG_FRF_MID = 0x07
|
||||
SX127X_REG_FRF_LSB = 0x08
|
||||
SX127X_REG_PA_CONFIG = 0x09
|
||||
SX127X_REG_PA_RAMP = 0x0a
|
||||
SX127X_REG_OCP = 0x0b
|
||||
SX127X_REG_LNA = 0x0c
|
||||
SX127X_REG_FIFO_ADDR_PTR = 0x0d
|
||||
SX127X_REG_FIFO_TX_BASE_ADDR = 0x0e
|
||||
SX127X_REG_FIFO_RX_BASE_ADDR = 0x0f
|
||||
SX127X_REG_FIFO_RX_CURRENT_ADDR = 0x10
|
||||
SX127X_REG_IRQ_FLAGS_MASK = 0x11
|
||||
SX127X_REG_IRQ_FLAGS = 0x12
|
||||
SX127X_REG_RX_NB_BYTES = 0x13
|
||||
SX127X_REG_PKT_SNR_VALUE = 0x19
|
||||
SX127X_REG_PKT_RSSI_VALUE = 0x1a
|
||||
SX127X_REG_RSSI_VALUE = 0x1b
|
||||
SX127X_REG_MODEM_CONFIG_1 = 0x1d
|
||||
SX127X_REG_MODEM_CONFIG_2 = 0x1e
|
||||
SX127X_REG_SYMB_TIMEOUT_LSB = 0x1f
|
||||
SX127X_REG_PREAMBLE_MSB = 0x20
|
||||
SX127X_REG_PREAMBLE_LSB = 0x21
|
||||
SX127X_REG_PAYLOAD_LENGTH = 0x22
|
||||
SX127X_REG_MAX_PAYLOAD_LENGTH = 0x23
|
||||
SX127X_REG_HOP_PERIOD = 0x24
|
||||
SX127X_REG_MODEM_CONFIG_3 = 0x26
|
||||
SX127X_REG_FREQ_ERROR_MSB = 0x28
|
||||
SX127X_REG_FREQ_ERROR_MID = 0x29
|
||||
SX127X_REG_FREQ_ERROR_LSB = 0x2a
|
||||
SX127X_REG_RSSI_WIDEBAND = 0x2c
|
||||
SX127X_REG_DETECTION_OPTIMIZE = 0x31
|
||||
SX127X_REG_INVERTIQ = 0x33
|
||||
SX127X_REG_DETECTION_THRESHOLD = 0x37
|
||||
SX127X_REG_SYNC_WORD = 0x39
|
||||
SX127X_REG_INVERTIQ2 = 0x3b
|
||||
SX127X_REG_DIO_MAPPING_1 = 0x40
|
||||
SX127X_REG_DIO_MAPPING_2 = 0x41
|
||||
SX127X_REG_VERSION = 0x42
|
||||
SX127X_REG_PA_DAC = 0x4d
|
||||
// PA config
|
||||
SX127X_PA_BOOST = 0x80
|
||||
|
||||
// Bits masking the corresponding IRQs from the radio
|
||||
SX127X_IRQ_LORA_RXTOUT_MASK = uint8(0x80)
|
||||
SX127X_IRQ_LORA_RXDONE_MASK = uint8(0x40)
|
||||
SX127X_IRQ_LORA_CRCERR_MASK = uint8(0x20)
|
||||
SX127X_IRQ_LORA_HEADER_MASK = uint8(0x10)
|
||||
SX127X_IRQ_LORA_TXDONE_MASK = uint8(0x08)
|
||||
SX127X_IRQ_LORA_CDDONE_MASK = uint8(0x04)
|
||||
SX127X_IRQ_LORA_FHSSCH_MASK = uint8(0x02)
|
||||
SX127X_IRQ_LORA_CDDETD_MASK = uint8(0x01)
|
||||
|
||||
// DIO function mappings D0D1D2D3
|
||||
SX127X_MAP_DIO0_LORA_RXDONE = uint8(0x00) // 00------
|
||||
SX127X_MAP_DIO0_LORA_TXDONE = uint8(0x40) // 01------
|
||||
SX127X_MAP_DIO1_LORA_RXTOUT = uint8(0x00) // --00----
|
||||
SX127X_MAP_DIO1_LORA_NOP = uint8(0x30) // --11----
|
||||
SX127X_MAP_DIO2_LORA_NOP = uint8(0xC0) // ----11--
|
||||
|
||||
SX127X_PAYLOAD_LENGTH = uint8(0x40)
|
||||
|
||||
// Low Noise Amp
|
||||
SX127X_LNA_MAX_GAIN = uint8(0x23)
|
||||
SX127X_LNA_OFF_GAIN = uint8(0x00)
|
||||
SX127X_LNA_LOW_GAIN = uint8(0x20)
|
||||
|
||||
// Bandwidth
|
||||
SX127X_LORA_BW_7_8 = uint8(0x00)
|
||||
SX127X_LORA_BW_10_4 = uint8(0x01)
|
||||
SX127X_LORA_BW_15_6 = uint8(0x02)
|
||||
SX127X_LORA_BW_20_8 = uint8(0x03)
|
||||
SX127X_LORA_BW_31_25 = uint8(0x04)
|
||||
SX127X_LORA_BW_41_7 = uint8(0x05)
|
||||
SX127X_LORA_BW_62_5 = uint8(0x06)
|
||||
SX127X_LORA_BW_125_0 = uint8(0x07)
|
||||
SX127X_LORA_BW_250_0 = uint8(0x08)
|
||||
SX127X_LORA_BW_500_0 = uint8(0x09)
|
||||
// Automatic gain control
|
||||
SX127X_AGC_AUTO_OFF = uint8(0x00)
|
||||
SX127X_AGC_AUTO_ON = uint8(0x01)
|
||||
// Operation modes
|
||||
SX127X_OPMODE_LORA = uint8(0x80)
|
||||
SX127X_OPMODE_MASK = uint8(0x07)
|
||||
SX127X_OPMODE_SLEEP = uint8(0x00)
|
||||
SX127X_OPMODE_STANDBY = uint8(0x01)
|
||||
SX127X_OPMODE_FSTX = uint8(0x02)
|
||||
SX127X_OPMODE_TX = uint8(0x03)
|
||||
SX127X_OPMODE_FSRX = uint8(0x04)
|
||||
SX127X_OPMODE_RX = uint8(0x05)
|
||||
SX127X_OPMODE_RX_SINGLE = uint8(0x06)
|
||||
SX127X_OPMODE_CAD = uint8(0x07)
|
||||
|
||||
SX127X_LORA_MAC_PUBLIC_SYNCWORD = 0x34
|
||||
SX127X_LORA_MAC_PRIVATE_SYNCWORD = 0x14
|
||||
)
|
||||
@@ -0,0 +1,530 @@
|
||||
// Package sx127x provides a driver for SX127x LoRa transceivers.
|
||||
// References:
|
||||
// https://electronics.stackexchange.com/questions/394296/can-t-get-simple-lora-receiver-to-work
|
||||
// https://www.st.com/resource/en/user_manual/dm00300436-stm32-lora-expansion-package-for-stm32cube-stmicroelectronics.pdf
|
||||
package sx127x
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/lora"
|
||||
)
|
||||
|
||||
// So we can keep track of the origin of interruption
|
||||
const (
|
||||
SPI_BUFFER_SIZE = 256
|
||||
)
|
||||
|
||||
// Device wraps an SPI connection to a SX127x device.
|
||||
type Device struct {
|
||||
spi drivers.SPI // SPI bus for module communication
|
||||
rstPin, csPin machine.Pin // GPIOs for reset and chip select
|
||||
radioEventChan chan lora.RadioEvent // Channel for Receiving events
|
||||
loraConf lora.Config // Current Lora configuration
|
||||
deepSleep bool // Internal Sleep state
|
||||
deviceType int // sx1261,sx1262,sx1268 (defaults sx1261)
|
||||
spiBuffer [SPI_BUFFER_SIZE]uint8
|
||||
packetIndex uint8 // FIXME ... useless ?
|
||||
}
|
||||
|
||||
// --------------------------------------------------
|
||||
//
|
||||
// Channel and events
|
||||
//
|
||||
// --------------------------------------------------
|
||||
// Get the RadioEvent channel of the device
|
||||
func (d *Device) GetRadioEventChan() chan lora.RadioEvent {
|
||||
return d.radioEventChan
|
||||
}
|
||||
|
||||
// New creates a new SX127x connection. The SPI bus must already be configured.
|
||||
func New(spi machine.SPI, csPin machine.Pin, rstPin machine.Pin) *Device {
|
||||
k := Device{
|
||||
spi: spi,
|
||||
csPin: csPin,
|
||||
rstPin: rstPin,
|
||||
radioEventChan: make(chan lora.RadioEvent, 10),
|
||||
}
|
||||
return &k
|
||||
}
|
||||
|
||||
// Reset re-initialize the sx127x device
|
||||
func (d *Device) Reset() {
|
||||
d.rstPin.Low()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
d.rstPin.High()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// DetectDevice checks if device responds on the SPI bus
|
||||
func (d *Device) DetectDevice() bool {
|
||||
id := d.GetVersion()
|
||||
return (id == 0x12)
|
||||
}
|
||||
|
||||
// ReadRegister reads register value
|
||||
func (d *Device) ReadRegister(reg uint8) uint8 {
|
||||
d.csPin.Low()
|
||||
d.spi.Tx([]byte{reg & 0x7f}, nil)
|
||||
var value [1]byte
|
||||
d.spi.Tx(nil, value[:])
|
||||
d.csPin.High()
|
||||
return value[0]
|
||||
}
|
||||
|
||||
// WriteRegister writes value to register
|
||||
func (d *Device) WriteRegister(reg uint8, value uint8) uint8 {
|
||||
var response [1]byte
|
||||
d.csPin.Low()
|
||||
d.spi.Tx([]byte{reg | 0x80}, nil)
|
||||
d.spi.Tx([]byte{value}, response[:])
|
||||
d.csPin.High()
|
||||
return response[0]
|
||||
}
|
||||
|
||||
// SetOpMode changes the sx1276 mode
|
||||
func (d *Device) SetOpMode(mode uint8) {
|
||||
cur := d.ReadRegister(SX127X_REG_OP_MODE)
|
||||
new := (cur & (^SX127X_OPMODE_MASK)) | mode
|
||||
d.WriteRegister(SX127X_REG_OP_MODE, new)
|
||||
}
|
||||
|
||||
// SetOpMode changes the sx1276 mode
|
||||
func (d *Device) SetOpModeLora() {
|
||||
d.WriteRegister(SX127X_REG_OP_MODE, SX127X_OPMODE_LORA)
|
||||
}
|
||||
|
||||
// GetVersion returns hardware version of sx1276 chipset
|
||||
func (d *Device) GetVersion() uint8 {
|
||||
return (d.ReadRegister(SX127X_REG_VERSION))
|
||||
}
|
||||
|
||||
// IsTransmitting tests if a packet transmission is in progress
|
||||
func (d *Device) IsTransmitting() bool {
|
||||
return (d.ReadRegister(SX127X_REG_OP_MODE) & SX127X_OPMODE_TX) == SX127X_OPMODE_TX
|
||||
}
|
||||
|
||||
// LastPacketRSSI gives the RSSI of the last packet received
|
||||
func (d *Device) LastPacketRSSI() uint8 {
|
||||
// section 5.5.5
|
||||
var adjustValue uint8 = 157
|
||||
if d.loraConf.Freq < 868000000 {
|
||||
adjustValue = 164
|
||||
}
|
||||
return d.ReadRegister(SX127X_REG_PKT_RSSI_VALUE) - adjustValue
|
||||
}
|
||||
|
||||
// LastPacketSNR gives the SNR of the last packet received
|
||||
func (d *Device) LastPacketSNR() uint8 {
|
||||
return uint8(d.ReadRegister(SX127X_REG_PKT_SNR_VALUE) / 4)
|
||||
}
|
||||
|
||||
// GetRSSI returns current RSSI
|
||||
func (d *Device) GetRSSI() uint8 {
|
||||
return d.ReadRegister(SX127X_REG_RSSI_VALUE)
|
||||
}
|
||||
|
||||
/*
|
||||
// GetBandwidth returns the bandwidth the LoRa module is using
|
||||
func (d *Device) GetBandwidth() int32 {
|
||||
return int32(d.loraConf.Bw)
|
||||
}
|
||||
*/
|
||||
|
||||
// SetTxPower sets the transmitter output power
|
||||
func (d *Device) SetTxPower(txPower int8, paBoost bool) {
|
||||
if !paBoost {
|
||||
// RFO
|
||||
if txPower < 0 {
|
||||
txPower = 0
|
||||
} else if txPower > 14 {
|
||||
txPower = 14
|
||||
}
|
||||
d.WriteRegister(SX127X_REG_PA_CONFIG, uint8(0x70)|uint8(txPower))
|
||||
|
||||
} else {
|
||||
//PA_BOOST
|
||||
if txPower > 17 {
|
||||
if txPower > 20 {
|
||||
txPower = 20
|
||||
}
|
||||
|
||||
txPower -= 3
|
||||
|
||||
// High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.)
|
||||
d.WriteRegister(SX127X_REG_PA_DAC, 0x87)
|
||||
d.SetOCP(140)
|
||||
} else {
|
||||
if txPower < 2 {
|
||||
txPower = 2
|
||||
}
|
||||
|
||||
d.WriteRegister(SX127X_REG_PA_DAC, 0x84)
|
||||
d.SetOCP(100)
|
||||
|
||||
}
|
||||
|
||||
d.WriteRegister(SX127X_REG_PA_CONFIG, uint8(SX127X_PA_BOOST)|uint8(txPower-2))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------
|
||||
// Internal functions
|
||||
// ---------------
|
||||
|
||||
// SetRxTimeout defines RX Timeout expressed as number of symbols
|
||||
// Default timeout is 64 * Ts
|
||||
func (d *Device) SetRxTimeout(tmoutSymb uint8) {
|
||||
d.WriteRegister(SX127X_REG_SYMB_TIMEOUT_LSB, tmoutSymb)
|
||||
}
|
||||
|
||||
// SetOCP defines Overload Current Protection configuration
|
||||
func (d *Device) SetOCP(mA uint8) {
|
||||
ocpTrim := uint8(27)
|
||||
if mA < 45 {
|
||||
mA = 45
|
||||
}
|
||||
if mA <= 120 {
|
||||
ocpTrim = (mA - 45) / 5
|
||||
} else if mA <= 240 {
|
||||
ocpTrim = (mA + 30) / 10
|
||||
}
|
||||
d.WriteRegister(SX127X_REG_OCP, 0x20|(0x1F&ocpTrim))
|
||||
}
|
||||
|
||||
// SetAgcAutoOn enables Automatic Gain Control
|
||||
func (d *Device) SetAgcAuto(val uint8) {
|
||||
if val == SX127X_AGC_AUTO_ON {
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_REG_MODEM_CONFIG_3)|0x04)
|
||||
} else {
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_REG_MODEM_CONFIG_3)&0xfb)
|
||||
}
|
||||
}
|
||||
|
||||
// SetLowDataRateOptimize enables Low Data Rate Optimization
|
||||
func (d *Device) SetLowDataRateOptim(val uint8) {
|
||||
if val == lora.LowDataRateOptimizeOn {
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_REG_MODEM_CONFIG_3)|0x08)
|
||||
} else {
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_3, d.ReadRegister(SX127X_REG_MODEM_CONFIG_3)&0xf7)
|
||||
}
|
||||
}
|
||||
|
||||
// SetLowFrequencyModeOn enables Low Data Rate Optimization
|
||||
func (d *Device) SetLowFrequencyModeOn(val bool) {
|
||||
if val {
|
||||
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)|0x04)
|
||||
} else {
|
||||
d.WriteRegister(SX127X_REG_OP_MODE, d.ReadRegister(SX127X_REG_OP_MODE)&0xfb)
|
||||
}
|
||||
}
|
||||
|
||||
// SetHopPeriod sets number of symbol periods between frequency hops. (0 = disabled).
|
||||
func (d *Device) SetHopPeriod(val uint8) {
|
||||
d.WriteRegister(SX127X_REG_HOP_PERIOD, val)
|
||||
}
|
||||
|
||||
//
|
||||
// LORA FUNCTIONS
|
||||
//
|
||||
|
||||
// LoraConfig() defines Lora configuration for next Lora operations
|
||||
func (d *Device) LoraConfig(cnf lora.Config) {
|
||||
// Save given configuration
|
||||
d.loraConf = cnf
|
||||
d.loraConf.SyncWord = syncword(int(cnf.SyncWord))
|
||||
}
|
||||
|
||||
// SetFrequency updates the frequency the LoRa module is using
|
||||
func (d *Device) SetFrequency(frequency uint32) {
|
||||
d.loraConf.Freq = frequency
|
||||
var frf = (uint64(frequency) << 19) / 32000000
|
||||
d.WriteRegister(SX127X_REG_FRF_MSB, uint8(frf>>16))
|
||||
d.WriteRegister(SX127X_REG_FRF_MID, uint8(frf>>8))
|
||||
d.WriteRegister(SX127X_REG_FRF_LSB, uint8(frf>>0))
|
||||
}
|
||||
|
||||
// SetBandwidth updates the bandwidth the LoRa module is using
|
||||
func (d *Device) SetBandwidth(bw uint8) {
|
||||
d.loraConf.Bw = bandwidth(bw)
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, (d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)&0x0f)|(bw<<4))
|
||||
}
|
||||
|
||||
// SetCodingRate updates the coding rate the LoRa module is using
|
||||
func (d *Device) SetCodingRate(cr uint8) {
|
||||
d.loraConf.Cr = cr
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, (d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)&0xf1)|(cr<<1))
|
||||
}
|
||||
|
||||
// SetImplicitHeaderModeOn Enables implicit header mode ***
|
||||
func (d *Device) SetHeaderMode(headerType uint8) {
|
||||
d.loraConf.HeaderType = headerType
|
||||
if headerType == lora.HeaderImplicit {
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)|0x01)
|
||||
} else {
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_1, d.ReadRegister(SX127X_REG_MODEM_CONFIG_1)&0xfe)
|
||||
}
|
||||
}
|
||||
|
||||
// SetSpreadingFactor changes spreading factor
|
||||
func (d *Device) SetSpreadingFactor(sf uint8) {
|
||||
d.loraConf.Sf = sf
|
||||
if sf == lora.SpreadingFactor6 {
|
||||
d.WriteRegister(SX127X_REG_DETECTION_OPTIMIZE, 0xc5)
|
||||
d.WriteRegister(SX127X_REG_DETECTION_THRESHOLD, 0x0c)
|
||||
} else {
|
||||
d.WriteRegister(SX127X_REG_DETECTION_OPTIMIZE, 0xc3)
|
||||
d.WriteRegister(SX127X_REG_DETECTION_THRESHOLD, 0x0a)
|
||||
}
|
||||
var newValue = (d.ReadRegister(SX127X_REG_MODEM_CONFIG_2) & 0x0f) | ((sf << 4) & 0xf0)
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, newValue)
|
||||
}
|
||||
|
||||
// SetTxContinuousMode enable Continuous Tx mode
|
||||
func (d *Device) SetTxContinuousMode(val bool) {
|
||||
if val {
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)|0x08)
|
||||
} else {
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)&0xf7)
|
||||
}
|
||||
}
|
||||
|
||||
// SetCrc Enable CRC generation and check on payload
|
||||
func (d *Device) SetCrc(enable bool) {
|
||||
if enable {
|
||||
d.loraConf.Crc = lora.CRCOn
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)|0x04)
|
||||
} else {
|
||||
d.loraConf.Crc = lora.CRCOff
|
||||
d.WriteRegister(SX127X_REG_MODEM_CONFIG_2, d.ReadRegister(SX127X_REG_MODEM_CONFIG_2)&0xfb)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) SetPreamble(pLen uint16) {
|
||||
// Sets preamble length
|
||||
d.WriteRegister(SX127X_REG_PREAMBLE_MSB, uint8((pLen>>8)&0xFF))
|
||||
d.WriteRegister(SX127X_REG_PREAMBLE_LSB, uint8(pLen&0xFF))
|
||||
}
|
||||
|
||||
// SetSyncWord defines sync word
|
||||
func (d *Device) SetSyncWord(syncWord uint16) {
|
||||
d.loraConf.SyncWord = syncWord
|
||||
sw := uint8(syncWord & 0xFF)
|
||||
d.WriteRegister(SX127X_REG_SYNC_WORD, sw)
|
||||
}
|
||||
|
||||
// SetIQMode Sets I/Q polarity configuration
|
||||
func (d *Device) SetIqMode(val uint8) {
|
||||
d.loraConf.Iq = val
|
||||
if val == lora.IQStandard {
|
||||
//Set IQ to normal values
|
||||
d.WriteRegister(SX127X_REG_INVERTIQ, 0x27)
|
||||
d.WriteRegister(SX127X_REG_INVERTIQ2, 0x1D)
|
||||
} else {
|
||||
//Invert IQ Back
|
||||
d.WriteRegister(SX127X_REG_INVERTIQ, 0x66)
|
||||
d.WriteRegister(SX127X_REG_INVERTIQ2, 0x19)
|
||||
}
|
||||
}
|
||||
|
||||
// Tx sends a lora packet, (with timeout)
|
||||
func (d *Device) Tx(pkt []uint8, timeoutMs uint32) error {
|
||||
d.SetOpModeLora()
|
||||
d.SetOpMode(SX127X_OPMODE_SLEEP)
|
||||
|
||||
d.SetHopPeriod(0x00)
|
||||
d.SetLowFrequencyModeOn(false) // High freq mode
|
||||
d.WriteRegister(SX127X_REG_PA_RAMP, (d.ReadRegister(SX127X_REG_PA_RAMP)&0xF0)|0x08) // set PA ramp-up time 50 uSec
|
||||
d.WriteRegister(SX127X_REG_LNA, SX127X_LNA_MAX_GAIN) // Set Low Noise Amplifier to MAX
|
||||
|
||||
d.SetFrequency(d.loraConf.Freq)
|
||||
d.SetPreamble(d.loraConf.Preamble)
|
||||
d.SetSyncWord(d.loraConf.SyncWord)
|
||||
d.SetBandwidth(d.loraConf.Bw)
|
||||
d.SetSpreadingFactor(d.loraConf.Sf)
|
||||
d.SetIqMode(d.loraConf.Iq)
|
||||
d.SetCodingRate(d.loraConf.Cr)
|
||||
d.SetCrc(d.loraConf.Crc == lora.CRCOn)
|
||||
d.SetTxPower(d.loraConf.LoraTxPowerDBm, true)
|
||||
d.SetHeaderMode(d.loraConf.HeaderType)
|
||||
d.SetAgcAuto(SX127X_AGC_AUTO_ON)
|
||||
|
||||
// set the IRQ mapping DIO0=TxDone DIO1=NOP DIO2=NOP
|
||||
d.WriteRegister(SX127X_REG_DIO_MAPPING_1, SX127X_MAP_DIO0_LORA_TXDONE|SX127X_MAP_DIO1_LORA_NOP|SX127X_MAP_DIO2_LORA_NOP)
|
||||
// Clear all radio IRQ Flags
|
||||
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
|
||||
// Mask all but TxDone
|
||||
d.WriteRegister(SX127X_REG_IRQ_FLAGS_MASK, ^SX127X_IRQ_LORA_TXDONE_MASK)
|
||||
|
||||
// initialize the payload size and address pointers
|
||||
d.WriteRegister(SX127X_REG_PAYLOAD_LENGTH, uint8(len(pkt)))
|
||||
d.WriteRegister(SX127X_REG_FIFO_TX_BASE_ADDR, 0)
|
||||
d.WriteRegister(SX127X_REG_FIFO_ADDR_PTR, 0)
|
||||
|
||||
// FIFO OPs cannot take place in Sleep mode !!!
|
||||
d.SetOpMode(SX127X_OPMODE_STANDBY)
|
||||
time.Sleep(time.Millisecond)
|
||||
// Copy payload to FIFO // TODO: Bulk
|
||||
for i := 0; i < len(pkt); i++ {
|
||||
d.WriteRegister(SX127X_REG_FIFO, pkt[i])
|
||||
}
|
||||
|
||||
// Enable TX
|
||||
d.SetOpMode(SX127X_OPMODE_TX)
|
||||
|
||||
msg := <-d.GetRadioEventChan()
|
||||
if msg.EventType != lora.RadioEventTxDone {
|
||||
return errors.New("Unexpected Radio Event while TX " + string(0x30+msg.EventType))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rx tries to receive a Lora packet (with timeout in milliseconds)
|
||||
func (d *Device) Rx(timeoutMs uint32) ([]uint8, error) {
|
||||
if d.loraConf.Freq == 0 {
|
||||
return nil, lora.ErrUndefinedLoraConf
|
||||
}
|
||||
|
||||
d.SetOpModeLora()
|
||||
d.SetOpMode(SX127X_OPMODE_SLEEP)
|
||||
|
||||
d.SetHopPeriod(0x00)
|
||||
d.SetLowFrequencyModeOn(false) // High freq mode
|
||||
d.WriteRegister(SX127X_REG_PA_RAMP, (d.ReadRegister(SX127X_REG_PA_RAMP)&0xF0)|0x08) // set PA ramp-up time 50 uSec
|
||||
d.WriteRegister(SX127X_REG_LNA, SX127X_LNA_MAX_GAIN) // Set Low Noise Amplifier to MAX
|
||||
|
||||
d.SetFrequency(d.loraConf.Freq)
|
||||
d.SetPreamble(d.loraConf.Preamble)
|
||||
d.SetSyncWord(d.loraConf.SyncWord)
|
||||
d.SetBandwidth(d.loraConf.Bw)
|
||||
d.SetSpreadingFactor(d.loraConf.Sf)
|
||||
d.SetIqMode(d.loraConf.Iq)
|
||||
d.SetCodingRate(d.loraConf.Cr)
|
||||
d.SetCrc(d.loraConf.Crc == lora.CRCOn)
|
||||
d.SetTxPower(d.loraConf.LoraTxPowerDBm, true)
|
||||
d.SetHeaderMode(d.loraConf.HeaderType)
|
||||
d.SetAgcAuto(SX127X_AGC_AUTO_ON)
|
||||
|
||||
// set the IRQ mapping DIO0=RxDone DIO1=RxTimeout DIO2=NOP
|
||||
d.WriteRegister(SX127X_REG_DIO_MAPPING_1, SX127X_MAP_DIO0_LORA_RXDONE|SX127X_MAP_DIO1_LORA_RXTOUT|SX127X_MAP_DIO2_LORA_NOP)
|
||||
// Clear all radio IRQ Flags
|
||||
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
|
||||
// Mask all but RxDone
|
||||
d.WriteRegister(SX127X_REG_IRQ_FLAGS_MASK, ^(SX127X_IRQ_LORA_RXDONE_MASK | SX127X_IRQ_LORA_RXTOUT_MASK))
|
||||
// Switch to RX Mode
|
||||
d.SetOpMode(SX127X_OPMODE_RX_SINGLE) //
|
||||
// Wait for Radio Event
|
||||
|
||||
radioCh := d.GetRadioEventChan()
|
||||
|
||||
msg := <-radioCh
|
||||
if msg.EventType == lora.RadioEventTimeout {
|
||||
return nil, nil
|
||||
} else if msg.EventType != lora.RadioEventRxDone {
|
||||
return nil, errors.New("Unexpected Radio Event while RX " + string(0x30+msg.EventType))
|
||||
}
|
||||
|
||||
d.WriteRegister(SX127X_REG_FIFO_RX_BASE_ADDR, 0)
|
||||
d.WriteRegister(SX127X_REG_FIFO_ADDR_PTR, 0)
|
||||
|
||||
pLen := d.ReadRegister(SX127X_REG_RX_NB_BYTES)
|
||||
d.WriteRegister(SX127X_REG_FIFO_ADDR_PTR, d.ReadRegister(SX127X_REG_FIFO_RX_CURRENT_ADDR))
|
||||
|
||||
for i := uint8(0); i < pLen; i++ {
|
||||
d.spiBuffer[i] = d.ReadRegister(SX127X_REG_FIFO)
|
||||
}
|
||||
return d.spiBuffer[:pLen], nil
|
||||
}
|
||||
|
||||
//
|
||||
// HELPER FUNCTIONS
|
||||
//
|
||||
|
||||
// PrintRegisters outputs the sx127x transceiver registers
|
||||
func (d *Device) PrintRegisters(compact bool) {
|
||||
for i := uint8(0); i < 128; i++ {
|
||||
v := d.ReadRegister(i)
|
||||
print(v, " ")
|
||||
}
|
||||
println()
|
||||
}
|
||||
|
||||
// PrintRegisters outputs the sx127x transceiver registers
|
||||
func (d *Device) RandomU32() uint32 {
|
||||
// Disable ALL irqs
|
||||
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
|
||||
d.SetOpModeLora()
|
||||
d.SetOpMode(SX127X_OPMODE_SLEEP)
|
||||
d.SetFrequency(d.loraConf.Freq)
|
||||
d.SetOpMode(SX127X_OPMODE_RX)
|
||||
rnd := uint32(0)
|
||||
for i := 0; i < 32; i++ {
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
// Unfiltered RSSI value reading. Only takes the LSB value
|
||||
rnd |= (uint32(d.ReadRegister(SX127X_REG_RSSI_WIDEBAND)) & 0x01) << i
|
||||
}
|
||||
return rnd
|
||||
}
|
||||
|
||||
// HandleInterrupt must be called by main code on DIO state change.
|
||||
func (d *Device) HandleInterrupt() {
|
||||
// Get IRQ and clear
|
||||
st := d.ReadRegister(SX127X_REG_IRQ_FLAGS)
|
||||
d.WriteRegister(SX127X_REG_IRQ_FLAGS, 0xFF)
|
||||
|
||||
rChan := d.GetRadioEventChan()
|
||||
|
||||
if (st & SX127X_IRQ_LORA_RXDONE_MASK) > 0 {
|
||||
rChan <- lora.NewRadioEvent(lora.RadioEventRxDone, uint16(st), nil)
|
||||
}
|
||||
|
||||
if (st & SX127X_IRQ_LORA_TXDONE_MASK) > 0 {
|
||||
rChan <- lora.NewRadioEvent(lora.RadioEventTxDone, uint16(st), nil)
|
||||
}
|
||||
|
||||
if (st & SX127X_IRQ_LORA_RXTOUT_MASK) > 0 {
|
||||
rChan <- lora.NewRadioEvent(lora.RadioEventTimeout, uint16(st), nil)
|
||||
}
|
||||
|
||||
if (st & SX127X_IRQ_LORA_CRCERR_MASK) > 0 {
|
||||
rChan <- lora.NewRadioEvent(lora.RadioEventCrcError, uint16(st), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func bandwidth(bw uint8) uint8 {
|
||||
switch bw {
|
||||
case lora.Bandwidth_7_8:
|
||||
return SX127X_LORA_BW_7_8
|
||||
case lora.Bandwidth_10_4:
|
||||
return SX127X_LORA_BW_10_4
|
||||
case lora.Bandwidth_15_6:
|
||||
return SX127X_LORA_BW_15_6
|
||||
case lora.Bandwidth_20_8:
|
||||
return SX127X_LORA_BW_20_8
|
||||
case lora.Bandwidth_31_25:
|
||||
return SX127X_LORA_BW_31_25
|
||||
case lora.Bandwidth_41_7:
|
||||
return SX127X_LORA_BW_41_7
|
||||
case lora.Bandwidth_62_5:
|
||||
return SX127X_LORA_BW_62_5
|
||||
case lora.Bandwidth_125_0:
|
||||
return SX127X_LORA_BW_125_0
|
||||
case lora.Bandwidth_250_0:
|
||||
return SX127X_LORA_BW_250_0
|
||||
case lora.Bandwidth_500_0:
|
||||
return SX127X_LORA_BW_500_0
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func syncword(sw int) uint16 {
|
||||
if sw == lora.SyncPublic {
|
||||
return SX127X_LORA_MAC_PUBLIC_SYNCWORD
|
||||
}
|
||||
return SX127X_LORA_MAC_PRIVATE_SYNCWORD
|
||||
}
|
||||
Reference in New Issue
Block a user