hci: refactor to separate HCI transport implementation from interface to not always assume UART.

Signed-off-by: deadprogram <ron@hybridgroup.com>
This commit is contained in:
deadprogram 2024-05-17 18:41:03 +02:00 committed by Ron Evans
parent d46f2cc206
commit 926aeb43f6
5 changed files with 144 additions and 67 deletions

View file

@ -3,17 +3,16 @@
package bluetooth
import (
"machine"
"runtime"
"time"
)
// hciAdapter represents the implementation for the UART connection to the HCI controller.
// hciAdapter represents the implementation for the connection to the HCI controller.
type hciAdapter struct {
uart *machine.UART
hci *hci
att *att
hciport hciTransport
hci *hci
att *att
isDefault bool
scanning bool
@ -49,8 +48,8 @@ func (a *hciAdapter) Address() (MACAddress, error) {
return MACAddress{MAC: makeAddress(a.hci.address)}, nil
}
func newBLEStack(uart *machine.UART) (*hci, *att) {
h := newHCI(uart)
func newBLEStack(port hciTransport) (*hci, *att) {
h := newHCI(port)
a := newATT(h)
h.att = a

View file

@ -8,10 +8,12 @@ import (
const maxConnections = 1
// Adapter represents the UART connection to the HCI controller.
// Adapter represents a "plain" UART connection to the HCI controller.
type Adapter struct {
hciAdapter
uart *machine.UART
// used for software flow control
cts, rts machine.Pin
}
@ -20,11 +22,13 @@ type Adapter struct {
//
// Make sure to call Enable() before using it to initialize the adapter.
var DefaultAdapter = &Adapter{
isDefault: true,
connectHandler: func(device Device, connected bool) {
return
hciAdapter: hciAdapter{
isDefault: true,
connectHandler: func(device Device, connected bool) {
return
},
connectedDevices: make([]Device, 0, maxConnections),
},
connectedDevices: make([]Device, 0, maxConnections),
}
// SetUART sets the UART to use for the HCI connection.
@ -49,16 +53,66 @@ func (a *Adapter) SetSoftwareFlowControl(cts, rts machine.Pin) error {
// Enable configures the BLE stack. It must be called before any
// Bluetooth-related calls (unless otherwise indicated).
func (a *Adapter) Enable() error {
a.hci, a.att = newBLEStack(a.uart)
transport := &hciUART{uart: a.uart}
if a.cts != 0 && a.rts != 0 {
a.hci.softRTS = a.rts
a.hci.softRTS.Configure(machine.PinConfig{Mode: machine.PinOutput})
a.hci.softRTS.High()
transport.rts = a.rts
a.rts.Configure(machine.PinConfig{Mode: machine.PinOutput})
a.rts.High()
a.hci.softCTS = a.cts
transport.cts = a.cts
a.cts.Configure(machine.PinConfig{Mode: machine.PinInput})
}
a.hci, a.att = newBLEStack(transport)
a.enable()
return nil
}
type hciUART struct {
uart *machine.UART
// used for software flow control
cts, rts machine.Pin
}
func (h *hciUART) startRead() {
if h.rts != machine.NoPin {
h.rts.Low()
}
}
func (h *hciUART) endRead() {
if h.rts != machine.NoPin {
h.rts.High()
}
}
func (h *hciUART) Buffered() int {
return h.uart.Buffered()
}
func (h *hciUART) ReadByte() (byte, error) {
return h.uart.ReadByte()
}
const writeAttempts = 200
func (h *hciUART) Write(buf []byte) (int, error) {
if h.cts != machine.NoPin {
retries := writeAttempts
for h.cts.Get() {
retries--
if retries == 0 {
return 0, ErrHCITimeout
}
}
}
n, err := h.uart.Write(buf)
if err != nil {
return 0, err
}
return n, nil
}

View file

@ -9,7 +9,7 @@ import (
const maxConnections = 1
// Adapter represents the HCI connection to the NINA fw.
// Adapter represents the HCI connection to the NINA fw using the hardware UART.
type Adapter struct {
hciAdapter
}
@ -54,16 +54,15 @@ func (a *Adapter) Enable() error {
uart.Configure(cfg)
a.hci, a.att = newBLEStack(uart)
transport := &hciUART{uart: uart}
if machine.NINA_SOFT_FLOWCONTROL {
a.hci.softRTS = machine.NINA_RTS
a.hci.softRTS.Configure(machine.PinConfig{Mode: machine.PinOutput})
a.hci.softRTS.High()
machine.NINA_RTS.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.NINA_RTS.High()
a.hci.softCTS = machine.NINA_CTS
machine.NINA_CTS.Configure(machine.PinConfig{Mode: machine.PinInput})
}
a.hci, a.att = newBLEStack(transport)
return a.enable()
}
@ -84,3 +83,48 @@ func resetNINAInverted() {
machine.NINA_RESETN.High()
time.Sleep(1000 * time.Millisecond)
}
type hciUART struct {
uart *machine.UART
}
func (h *hciUART) startRead() {
if machine.NINA_SOFT_FLOWCONTROL {
machine.NINA_RTS.Low()
}
}
func (h *hciUART) endRead() {
if machine.NINA_SOFT_FLOWCONTROL {
machine.NINA_RTS.High()
}
}
func (h *hciUART) Buffered() int {
return h.uart.Buffered()
}
func (h *hciUART) ReadByte() (byte, error) {
return h.uart.ReadByte()
}
const writeAttempts = 200
func (h *hciUART) Write(buf []byte) (int, error) {
if machine.NINA_SOFT_FLOWCONTROL {
retries := writeAttempts
for machine.NINA_CTS.Get() {
retries--
if retries == 0 {
return 0, ErrHCITimeout
}
}
}
n, err := h.uart.Write(buf)
if err != nil {
return 0, err
}
return n, nil
}

64
hci.go
View file

@ -1,4 +1,4 @@
//go:build ninafw
//go:build ninafw || hci
package bluetooth
@ -6,7 +6,6 @@ import (
"encoding/binary"
"encoding/hex"
"errors"
"machine"
"time"
)
@ -122,10 +121,16 @@ type leConnectData struct {
timeout uint16
}
type hciTransport interface {
startRead()
endRead()
Buffered() int
ReadByte() (byte, error)
Write(buf []byte) (int, error)
}
type hci struct {
uart *machine.UART
softCTS machine.Pin
softRTS machine.Pin
transport hciTransport
att *att
l2cap *l2cap
buf []byte
@ -140,24 +145,19 @@ type hci struct {
pendingPkt uint16
}
func newHCI(uart *machine.UART) *hci {
func newHCI(t hciTransport) *hci {
return &hci{
uart: uart,
softCTS: machine.NoPin,
softRTS: machine.NoPin,
buf: make([]byte, 256),
transport: t,
buf: make([]byte, 256),
}
}
func (h *hci) start() error {
if h.softRTS != machine.NoPin {
h.softRTS.Low()
h.transport.startRead()
defer h.transport.endRead()
defer h.softRTS.High()
}
for h.uart.Buffered() > 0 {
h.uart.ReadByte()
for h.transport.Buffered() > 0 {
h.transport.ReadByte()
}
return nil
@ -172,15 +172,12 @@ func (h *hci) reset() error {
}
func (h *hci) poll() error {
if h.softRTS != machine.NoPin {
h.softRTS.Low()
defer h.softRTS.High()
}
h.transport.startRead()
defer h.transport.endRead()
i := 0
for h.uart.Buffered() > 0 {
data, _ := h.uart.ReadByte()
for h.transport.Buffered() > 0 {
data, _ := h.transport.ReadByte()
h.buf[i] = data
done, err := h.processPacket(i)
@ -487,25 +484,8 @@ func (h *hci) sendAclPkt(handle uint16, cid uint8, data []byte) error {
return nil
}
const writeAttempts = 200
func (h *hci) write(buf []byte) (int, error) {
if h.softCTS != machine.NoPin {
retries := writeAttempts
for h.softCTS.Get() {
retries--
if retries == 0 {
return 0, ErrHCITimeout
}
}
}
n, err := h.uart.Write(buf)
if err != nil {
return 0, err
}
return n, nil
return h.transport.Write(buf)
}
type aclDataHeader struct {

View file

@ -1,4 +1,4 @@
//go:build ninafw
//go:build ninafw || hci
package bluetooth