Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
679be8520f | ||
|
|
01e9dc99f2 | ||
|
|
ec5e62e533 | ||
|
|
367200a2c8 | ||
|
|
11814823ed | ||
|
|
e6c7ee7813 | ||
|
|
dc856dbb1c | ||
|
|
c4b1ab81c1 | ||
|
|
f33df254f1 | ||
|
|
83a6aaca1c | ||
|
|
182af71492 | ||
|
|
d4cdb3ea69 | ||
|
|
016bf80e3b |
@@ -25,7 +25,7 @@ jobs:
|
||||
uses: codespell-project/actions-codespell@master
|
||||
with:
|
||||
skip: ./ArduinoCore-API,./libraries/ESP8266SdFat,./libraries/Adafruit_TinyUSB_Arduino,./libraries/LittleFS/lib,./tools/pyserial,./pico-sdk,./.github,./docs/i2s.rst,./cores/rp2040/api,./libraries/FreeRTOS,./tools/libbearssl/bearssl,./include,./libraries/WiFi/examples/BearSSL_Server,./ota/uzlib,./libraries/http-parser/lib,./libraries/WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino,./libraries/HTTPUpdateServer/examples/SecureBearSSLUpdater/SecureBearSSLUpdater.ino,./.git,./libraries/FatFS/lib/fatfs,./libraries/FatFS/src/diskio.h,./libraries/FatFS/src/ff.cpp,./libraries/FatFS/src/ffconf.h,./libraries/FatFS/src/ffsystem.cpp,./libraries/FatFS/src/ff.h,./libraries/lwIP_WINC1500/src/driver,./libraries/lwIP_WINC1500/src/common,./libraries/lwIP_WINC1500/src/bus_wrapper,./libraries/lwIP_WINC1500/src/spi_flash
|
||||
ignore_words_list: ser,dout
|
||||
ignore_words_list: ser,dout,shiftIn
|
||||
|
||||
# Consistent style
|
||||
astyle:
|
||||
|
||||
@@ -51,6 +51,7 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
|
||||
* Melopero Shake RP2040
|
||||
* Neko Systems BL2040 Mini
|
||||
* Olimex RP2040-Pico30
|
||||
* Newsan Archi
|
||||
* nullbits Bit-C PRO
|
||||
* Pimoroni PGA2040
|
||||
* Pimoroni Plasma2040
|
||||
@@ -97,7 +98,8 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
|
||||
* digitalWrite/Read, shiftIn/Out, tone, analogWrite(PWM)/Read, temperature
|
||||
* Analog stereo audio in using DMA and the built-in ADC
|
||||
* Analog stereo audio out using PWM hardware
|
||||
* USB drive mode for data loggers (SingleFileDrive)
|
||||
* Bluetooth A2DP audio source (output) on the PicoW
|
||||
* USB drive mode for data loggers (SingleFileDrive, FatFSUSB)
|
||||
* Peripherals: SPI master/slave, Wire(I2C) master/slave, dual UART, emulated EEPROM, I2S audio input/output, Servo
|
||||
* printf (i.e. debug) output over USB serial
|
||||
|
||||
|
||||
+234
@@ -11552,6 +11552,240 @@ nekosystems_bl2040_mini.menu.uploadmethod.picodebug.upload.maximum_data_size=245
|
||||
nekosystems_bl2040_mini.menu.uploadmethod.picodebug.upload.tool=picodebug
|
||||
nekosystems_bl2040_mini.menu.uploadmethod.picodebug.upload.tool.default=picodebug
|
||||
|
||||
# -----------------------------------
|
||||
# Newsan Archi
|
||||
# -----------------------------------
|
||||
newsan_archi.name=Newsan Archi
|
||||
newsan_archi.vid.0=0x2E8A
|
||||
newsan_archi.pid.0=0x1043
|
||||
newsan_archi.vid.1=0x2E8A
|
||||
newsan_archi.pid.1=0x1143
|
||||
newsan_archi.vid.2=0x2E8A
|
||||
newsan_archi.pid.2=0x5043
|
||||
newsan_archi.vid.3=0x2E8A
|
||||
newsan_archi.pid.3=0x5143
|
||||
newsan_archi.vid.4=0x2E8A
|
||||
newsan_archi.pid.4=0x9043
|
||||
newsan_archi.vid.5=0x2E8A
|
||||
newsan_archi.pid.5=0x9143
|
||||
newsan_archi.vid.6=0x2E8A
|
||||
newsan_archi.pid.6=0xd043
|
||||
newsan_archi.vid.7=0x2E8A
|
||||
newsan_archi.pid.7=0xd143
|
||||
newsan_archi.upload_port.0.vid=0x2E8A
|
||||
newsan_archi.upload_port.0.pid=0x1043
|
||||
newsan_archi.upload_port.1.vid=0x2E8A
|
||||
newsan_archi.upload_port.1.pid=0x1143
|
||||
newsan_archi.upload_port.2.vid=0x2E8A
|
||||
newsan_archi.upload_port.2.pid=0x5043
|
||||
newsan_archi.upload_port.3.vid=0x2E8A
|
||||
newsan_archi.upload_port.3.pid=0x5143
|
||||
newsan_archi.upload_port.4.vid=0x2E8A
|
||||
newsan_archi.upload_port.4.pid=0x9043
|
||||
newsan_archi.upload_port.5.vid=0x2E8A
|
||||
newsan_archi.upload_port.5.pid=0x9143
|
||||
newsan_archi.upload_port.6.vid=0x2E8A
|
||||
newsan_archi.upload_port.6.pid=0xd043
|
||||
newsan_archi.upload_port.7.vid=0x2E8A
|
||||
newsan_archi.upload_port.7.pid=0xd143
|
||||
newsan_archi.build.usbvid=-DUSBD_VID=0x2E8A
|
||||
newsan_archi.build.usbpid=-DUSBD_PID=0x1043
|
||||
newsan_archi.build.usbpwr=-DUSBD_MAX_POWER_MA=250
|
||||
newsan_archi.build.board=NEWSAN_ARCHI
|
||||
newsan_archi.build.mcu=cortex-m0plus
|
||||
newsan_archi.build.variant=newsan_archi
|
||||
newsan_archi.upload.maximum_size=4194304
|
||||
newsan_archi.upload.wait_for_upload_port=true
|
||||
newsan_archi.upload.erase_cmd=
|
||||
newsan_archi.serial.disableDTR=false
|
||||
newsan_archi.serial.disableRTS=false
|
||||
newsan_archi.build.f_cpu=125000000
|
||||
newsan_archi.build.led=
|
||||
newsan_archi.build.core=rp2040
|
||||
newsan_archi.build.ldscript=memmap_default.ld
|
||||
newsan_archi.build.boot2=boot2_generic_03h_4_padded_checksum
|
||||
newsan_archi.build.usb_manufacturer="Newsan"
|
||||
newsan_archi.build.usb_product="Archi"
|
||||
newsan_archi.menu.flash.4194304_0=4MB (no FS)
|
||||
newsan_archi.menu.flash.4194304_0.upload.maximum_size=4190208
|
||||
newsan_archi.menu.flash.4194304_0.build.flash_total=4194304
|
||||
newsan_archi.menu.flash.4194304_0.build.flash_length=4190208
|
||||
newsan_archi.menu.flash.4194304_0.build.eeprom_start=272625664
|
||||
newsan_archi.menu.flash.4194304_0.build.fs_start=272625664
|
||||
newsan_archi.menu.flash.4194304_0.build.fs_end=272625664
|
||||
newsan_archi.menu.flash.4194304_65536=4MB (Sketch: 4032KB, FS: 64KB)
|
||||
newsan_archi.menu.flash.4194304_65536.upload.maximum_size=4124672
|
||||
newsan_archi.menu.flash.4194304_65536.build.flash_total=4194304
|
||||
newsan_archi.menu.flash.4194304_65536.build.flash_length=4124672
|
||||
newsan_archi.menu.flash.4194304_65536.build.eeprom_start=272625664
|
||||
newsan_archi.menu.flash.4194304_65536.build.fs_start=272560128
|
||||
newsan_archi.menu.flash.4194304_65536.build.fs_end=272625664
|
||||
newsan_archi.menu.flash.4194304_131072=4MB (Sketch: 3968KB, FS: 128KB)
|
||||
newsan_archi.menu.flash.4194304_131072.upload.maximum_size=4059136
|
||||
newsan_archi.menu.flash.4194304_131072.build.flash_total=4194304
|
||||
newsan_archi.menu.flash.4194304_131072.build.flash_length=4059136
|
||||
newsan_archi.menu.flash.4194304_131072.build.eeprom_start=272625664
|
||||
newsan_archi.menu.flash.4194304_131072.build.fs_start=272494592
|
||||
newsan_archi.menu.flash.4194304_131072.build.fs_end=272625664
|
||||
newsan_archi.menu.flash.4194304_262144=4MB (Sketch: 3840KB, FS: 256KB)
|
||||
newsan_archi.menu.flash.4194304_262144.upload.maximum_size=3928064
|
||||
newsan_archi.menu.flash.4194304_262144.build.flash_total=4194304
|
||||
newsan_archi.menu.flash.4194304_262144.build.flash_length=3928064
|
||||
newsan_archi.menu.flash.4194304_262144.build.eeprom_start=272625664
|
||||
newsan_archi.menu.flash.4194304_262144.build.fs_start=272363520
|
||||
newsan_archi.menu.flash.4194304_262144.build.fs_end=272625664
|
||||
newsan_archi.menu.flash.4194304_524288=4MB (Sketch: 3584KB, FS: 512KB)
|
||||
newsan_archi.menu.flash.4194304_524288.upload.maximum_size=3665920
|
||||
newsan_archi.menu.flash.4194304_524288.build.flash_total=4194304
|
||||
newsan_archi.menu.flash.4194304_524288.build.flash_length=3665920
|
||||
newsan_archi.menu.flash.4194304_524288.build.eeprom_start=272625664
|
||||
newsan_archi.menu.flash.4194304_524288.build.fs_start=272101376
|
||||
newsan_archi.menu.flash.4194304_524288.build.fs_end=272625664
|
||||
newsan_archi.menu.flash.4194304_1048576=4MB (Sketch: 3MB, FS: 1MB)
|
||||
newsan_archi.menu.flash.4194304_1048576.upload.maximum_size=3141632
|
||||
newsan_archi.menu.flash.4194304_1048576.build.flash_total=4194304
|
||||
newsan_archi.menu.flash.4194304_1048576.build.flash_length=3141632
|
||||
newsan_archi.menu.flash.4194304_1048576.build.eeprom_start=272625664
|
||||
newsan_archi.menu.flash.4194304_1048576.build.fs_start=271577088
|
||||
newsan_archi.menu.flash.4194304_1048576.build.fs_end=272625664
|
||||
newsan_archi.menu.flash.4194304_2097152=4MB (Sketch: 2MB, FS: 2MB)
|
||||
newsan_archi.menu.flash.4194304_2097152.upload.maximum_size=2093056
|
||||
newsan_archi.menu.flash.4194304_2097152.build.flash_total=4194304
|
||||
newsan_archi.menu.flash.4194304_2097152.build.flash_length=2093056
|
||||
newsan_archi.menu.flash.4194304_2097152.build.eeprom_start=272625664
|
||||
newsan_archi.menu.flash.4194304_2097152.build.fs_start=270528512
|
||||
newsan_archi.menu.flash.4194304_2097152.build.fs_end=272625664
|
||||
newsan_archi.menu.flash.4194304_3145728=4MB (Sketch: 1MB, FS: 3MB)
|
||||
newsan_archi.menu.flash.4194304_3145728.upload.maximum_size=1044480
|
||||
newsan_archi.menu.flash.4194304_3145728.build.flash_total=4194304
|
||||
newsan_archi.menu.flash.4194304_3145728.build.flash_length=1044480
|
||||
newsan_archi.menu.flash.4194304_3145728.build.eeprom_start=272625664
|
||||
newsan_archi.menu.flash.4194304_3145728.build.fs_start=269479936
|
||||
newsan_archi.menu.flash.4194304_3145728.build.fs_end=272625664
|
||||
newsan_archi.menu.freq.133=133 MHz
|
||||
newsan_archi.menu.freq.133.build.f_cpu=133000000L
|
||||
newsan_archi.menu.freq.50=50 MHz
|
||||
newsan_archi.menu.freq.50.build.f_cpu=50000000L
|
||||
newsan_archi.menu.freq.100=100 MHz
|
||||
newsan_archi.menu.freq.100.build.f_cpu=100000000L
|
||||
newsan_archi.menu.freq.120=120 MHz
|
||||
newsan_archi.menu.freq.120.build.f_cpu=120000000L
|
||||
newsan_archi.menu.freq.125=125 MHz
|
||||
newsan_archi.menu.freq.125.build.f_cpu=125000000L
|
||||
newsan_archi.menu.freq.128=128 MHz
|
||||
newsan_archi.menu.freq.128.build.f_cpu=128000000L
|
||||
newsan_archi.menu.freq.150=150 MHz (Overclock)
|
||||
newsan_archi.menu.freq.150.build.f_cpu=150000000L
|
||||
newsan_archi.menu.freq.175=175 MHz (Overclock)
|
||||
newsan_archi.menu.freq.175.build.f_cpu=175000000L
|
||||
newsan_archi.menu.freq.200=200 MHz (Overclock)
|
||||
newsan_archi.menu.freq.200.build.f_cpu=200000000L
|
||||
newsan_archi.menu.freq.225=225 MHz (Overclock)
|
||||
newsan_archi.menu.freq.225.build.f_cpu=225000000L
|
||||
newsan_archi.menu.freq.240=240 MHz (Overclock)
|
||||
newsan_archi.menu.freq.240.build.f_cpu=240000000L
|
||||
newsan_archi.menu.freq.250=250 MHz (Overclock)
|
||||
newsan_archi.menu.freq.250.build.f_cpu=250000000L
|
||||
newsan_archi.menu.freq.275=275 MHz (Overclock)
|
||||
newsan_archi.menu.freq.275.build.f_cpu=275000000L
|
||||
newsan_archi.menu.freq.300=300 MHz (Overclock)
|
||||
newsan_archi.menu.freq.300.build.f_cpu=300000000L
|
||||
newsan_archi.menu.opt.Small=Small (-Os) (standard)
|
||||
newsan_archi.menu.opt.Small.build.flags.optimize=-Os
|
||||
newsan_archi.menu.opt.Optimize=Optimize (-O)
|
||||
newsan_archi.menu.opt.Optimize.build.flags.optimize=-O
|
||||
newsan_archi.menu.opt.Optimize2=Optimize More (-O2)
|
||||
newsan_archi.menu.opt.Optimize2.build.flags.optimize=-O2
|
||||
newsan_archi.menu.opt.Optimize3=Optimize Even More (-O3)
|
||||
newsan_archi.menu.opt.Optimize3.build.flags.optimize=-O3
|
||||
newsan_archi.menu.opt.Fast=Fast (-Ofast) (maybe slower)
|
||||
newsan_archi.menu.opt.Fast.build.flags.optimize=-Ofast
|
||||
newsan_archi.menu.opt.Debug=Debug (-Og)
|
||||
newsan_archi.menu.opt.Debug.build.flags.optimize=-Og
|
||||
newsan_archi.menu.rtti.Disabled=Disabled
|
||||
newsan_archi.menu.rtti.Disabled.build.flags.rtti=-fno-rtti
|
||||
newsan_archi.menu.rtti.Enabled=Enabled
|
||||
newsan_archi.menu.rtti.Enabled.build.flags.rtti=
|
||||
newsan_archi.menu.stackprotect.Disabled=Disabled
|
||||
newsan_archi.menu.stackprotect.Disabled.build.flags.stackprotect=
|
||||
newsan_archi.menu.stackprotect.Enabled=Enabled
|
||||
newsan_archi.menu.stackprotect.Enabled.build.flags.stackprotect=-fstack-protector
|
||||
newsan_archi.menu.exceptions.Disabled=Disabled
|
||||
newsan_archi.menu.exceptions.Disabled.build.flags.exceptions=-fno-exceptions
|
||||
newsan_archi.menu.exceptions.Disabled.build.flags.libstdcpp=-lstdc++
|
||||
newsan_archi.menu.exceptions.Enabled=Enabled
|
||||
newsan_archi.menu.exceptions.Enabled.build.flags.exceptions=-fexceptions
|
||||
newsan_archi.menu.exceptions.Enabled.build.flags.libstdcpp=-lstdc++-exc
|
||||
newsan_archi.menu.dbgport.Disabled=Disabled
|
||||
newsan_archi.menu.dbgport.Disabled.build.debug_port=
|
||||
newsan_archi.menu.dbgport.Serial=Serial
|
||||
newsan_archi.menu.dbgport.Serial.build.debug_port=-DDEBUG_RP2040_PORT=Serial
|
||||
newsan_archi.menu.dbgport.Serial1=Serial1
|
||||
newsan_archi.menu.dbgport.Serial1.build.debug_port=-DDEBUG_RP2040_PORT=Serial1
|
||||
newsan_archi.menu.dbgport.Serial2=Serial2
|
||||
newsan_archi.menu.dbgport.Serial2.build.debug_port=-DDEBUG_RP2040_PORT=Serial2
|
||||
newsan_archi.menu.dbglvl.None=None
|
||||
newsan_archi.menu.dbglvl.None.build.debug_level=
|
||||
newsan_archi.menu.dbglvl.Core=Core
|
||||
newsan_archi.menu.dbglvl.Core.build.debug_level=-DDEBUG_RP2040_CORE
|
||||
newsan_archi.menu.dbglvl.SPI=SPI
|
||||
newsan_archi.menu.dbglvl.SPI.build.debug_level=-DDEBUG_RP2040_SPI
|
||||
newsan_archi.menu.dbglvl.Wire=Wire
|
||||
newsan_archi.menu.dbglvl.Wire.build.debug_level=-DDEBUG_RP2040_WIRE
|
||||
newsan_archi.menu.dbglvl.Bluetooth=Bluetooth
|
||||
newsan_archi.menu.dbglvl.Bluetooth.build.debug_level=-DDEBUG_RP2040_BLUETOOTH
|
||||
newsan_archi.menu.dbglvl.All=All
|
||||
newsan_archi.menu.dbglvl.All.build.debug_level=-DDEBUG_RP2040_WIRE -DDEBUG_RP2040_SPI -DDEBUG_RP2040_CORE -DDEBUG_RP2040_BLUETOOTH
|
||||
newsan_archi.menu.dbglvl.NDEBUG=NDEBUG
|
||||
newsan_archi.menu.dbglvl.NDEBUG.build.debug_level=-DNDEBUG
|
||||
newsan_archi.menu.usbstack.picosdk=Pico SDK
|
||||
newsan_archi.menu.usbstack.picosdk.build.usbstack_flags=
|
||||
newsan_archi.menu.usbstack.tinyusb=Adafruit TinyUSB
|
||||
newsan_archi.menu.usbstack.tinyusb.build.usbstack_flags=-DUSE_TINYUSB "-I{runtime.platform.path}/libraries/Adafruit_TinyUSB_Arduino/src/arduino"
|
||||
newsan_archi.menu.usbstack.tinyusb_host=Adafruit TinyUSB Host (native)
|
||||
newsan_archi.menu.usbstack.tinyusb_host.build.usbstack_flags=-DUSE_TINYUSB -DUSE_TINYUSB_HOST "-I{runtime.platform.path}/libraries/Adafruit_TinyUSB_Arduino/src/arduino"
|
||||
newsan_archi.menu.usbstack.nousb=No USB
|
||||
newsan_archi.menu.usbstack.nousb.build.usbstack_flags="-DNO_USB -DDISABLE_USB_SERIAL -I{runtime.platform.path}/tools/libpico"
|
||||
newsan_archi.menu.ipbtstack.ipv4only=IPv4 Only
|
||||
newsan_archi.menu.ipbtstack.ipv4only.build.libpicow=libpicow-noipv6-nobtc-noble.a
|
||||
newsan_archi.menu.ipbtstack.ipv4only.build.libpicowdefs=-DLWIP_IPV6=0 -DLWIP_IPV4=1
|
||||
newsan_archi.menu.ipbtstack.ipv4ipv6=IPv4 + IPv6
|
||||
newsan_archi.menu.ipbtstack.ipv4ipv6.build.libpicow=libpicow-ipv6-nobtc-noble.a
|
||||
newsan_archi.menu.ipbtstack.ipv4ipv6.build.libpicowdefs=-DLWIP_IPV6=1 -DLWIP_IPV4=1
|
||||
newsan_archi.menu.ipbtstack.ipv4btcble=IPv4 + Bluetooth
|
||||
newsan_archi.menu.ipbtstack.ipv4btcble.build.libpicow=libpicow-noipv6-btc-ble.a
|
||||
newsan_archi.menu.ipbtstack.ipv4btcble.build.libpicowdefs=-DLWIP_IPV6=0 -DLWIP_IPV4=1 -DENABLE_CLASSIC=1 -DENABLE_BLE=1
|
||||
newsan_archi.menu.ipbtstack.ipv4ipv6btcble=IPv4 + IPv6 + Bluetooth
|
||||
newsan_archi.menu.ipbtstack.ipv4ipv6btcble.build.libpicow=libpicow-ipv6-btc-ble.a
|
||||
newsan_archi.menu.ipbtstack.ipv4ipv6btcble.build.libpicowdefs=-DLWIP_IPV6=1 -DLWIP_IPV4=1 -DENABLE_CLASSIC=1 -DENABLE_BLE=1
|
||||
newsan_archi.menu.uploadmethod.default=Default (UF2)
|
||||
newsan_archi.menu.uploadmethod.default.build.ram_length=256k
|
||||
newsan_archi.menu.uploadmethod.default.build.debugscript=picoprobe_cmsis_dap.tcl
|
||||
newsan_archi.menu.uploadmethod.default.upload.maximum_data_size=262144
|
||||
newsan_archi.menu.uploadmethod.default.upload.tool=uf2conv
|
||||
newsan_archi.menu.uploadmethod.default.upload.tool.default=uf2conv
|
||||
newsan_archi.menu.uploadmethod.default.upload.tool.network=uf2conv-network
|
||||
newsan_archi.menu.uploadmethod.picotool=Picotool
|
||||
newsan_archi.menu.uploadmethod.picotool.build.ram_length=256k
|
||||
newsan_archi.menu.uploadmethod.picotool.build.debugscript=picoprobe.tcl
|
||||
newsan_archi.menu.uploadmethod.picotool.build.picodebugflags=-DENABLE_PICOTOOL_USB
|
||||
newsan_archi.menu.uploadmethod.picotool.upload.maximum_data_size=262144
|
||||
newsan_archi.menu.uploadmethod.picotool.upload.tool=picotool
|
||||
newsan_archi.menu.uploadmethod.picotool.upload.tool.default=picotool
|
||||
newsan_archi.menu.uploadmethod.picoprobe_cmsis_dap=Picoprobe (CMSIS-DAP)
|
||||
newsan_archi.menu.uploadmethod.picoprobe_cmsis_dap.build.ram_length=256k
|
||||
newsan_archi.menu.uploadmethod.picoprobe_cmsis_dap.build.debugscript=picoprobe_cmsis_dap.tcl
|
||||
newsan_archi.menu.uploadmethod.picoprobe_cmsis_dap.upload.maximum_data_size=262144
|
||||
newsan_archi.menu.uploadmethod.picoprobe_cmsis_dap.upload.tool=picoprobe_cmsis_dap
|
||||
newsan_archi.menu.uploadmethod.picoprobe_cmsis_dap.upload.tool.default=picoprobe_cmsis_dap
|
||||
newsan_archi.menu.uploadmethod.picodebug=Pico-Debug
|
||||
newsan_archi.menu.uploadmethod.picodebug.build.ram_length=240k
|
||||
newsan_archi.menu.uploadmethod.picodebug.build.debugscript=picodebug.tcl
|
||||
newsan_archi.menu.uploadmethod.picodebug.build.picodebugflags=-UUSE_TINYUSB -DNO_USB -DDISABLE_USB_SERIAL -I{runtime.platform.path}/tools/libpico
|
||||
newsan_archi.menu.uploadmethod.picodebug.upload.maximum_data_size=245760
|
||||
newsan_archi.menu.uploadmethod.picodebug.upload.tool=picodebug
|
||||
newsan_archi.menu.uploadmethod.picodebug.upload.tool.default=picodebug
|
||||
|
||||
# -----------------------------------
|
||||
# nullbits Bit-C PRO
|
||||
# -----------------------------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#define ARDUINO_PICO_MAJOR 3
|
||||
#define ARDUINO_PICO_MINOR 8
|
||||
#define ARDUINO_PICO_REVISION 1
|
||||
#define ARDUINO_PICO_VERSION_STR "3.8.1"
|
||||
#define ARDUINO_PICO_MINOR 9
|
||||
#define ARDUINO_PICO_REVISION 0
|
||||
#define ARDUINO_PICO_VERSION_STR "3.9.0"
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Use to create a callback thunk from C to C++
|
||||
// #define CCALLBACKNAME to a unique per-file name and #include this file
|
||||
// To make a CB use a define of the form:
|
||||
/*
|
||||
#define PACKETHANDLERCB(class, cbFcn) \
|
||||
(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), \
|
||||
static_cast<btstack_packet_handler_t>(<CCALLBACKNAMEvoid(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
|
||||
*/
|
||||
|
||||
#include <functional>
|
||||
|
||||
// Thank you to https://stackoverflow.com/questions/66474621/multiple-non-static-callbacks-of-c-member-functions for the following beautiful hack
|
||||
|
||||
#ifndef CCALLBACKNAME
|
||||
#define CCALLBACKNAME _CCallback
|
||||
#endif
|
||||
|
||||
template <typename T, int tag>
|
||||
struct CCALLBACKNAME;
|
||||
|
||||
template <typename Ret, typename... Params, int tag>
|
||||
struct CCALLBACKNAME<Ret(Params...), tag> {
|
||||
template <typename... Args>
|
||||
static Ret callback(Args... args) {
|
||||
return func(args...);
|
||||
}
|
||||
int _tag = tag;
|
||||
static std::function<Ret(Params...)> func;
|
||||
};
|
||||
|
||||
template <typename Ret, typename... Params, int tag>
|
||||
std::function<Ret(Params...)> CCALLBACKNAME<Ret(Params...), tag>::func;
|
||||
@@ -358,4 +358,16 @@ extern "C" {
|
||||
__real_raw_remove(pcb);
|
||||
}
|
||||
|
||||
extern struct netif *__real_netif_add(struct netif *netif, const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw, void *state, netif_init_fn init, netif_input_fn input);
|
||||
struct netif *__wrap_netif_add(struct netif *netif, const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw, void *state, netif_init_fn init, netif_input_fn input) {
|
||||
LWIPMutex m;
|
||||
return __real_netif_add(netif, ipaddr, netmask, gw, state, init, input);
|
||||
}
|
||||
|
||||
extern void __real_netif_remove(struct netif *netif);
|
||||
void __wrap_netif_remove(struct netif *netif) {
|
||||
LWIPMutex m;
|
||||
__real_netif_remove(netif);
|
||||
}
|
||||
|
||||
}; // extern "C"
|
||||
|
||||
@@ -1212,7 +1212,7 @@ static uint16_t handle_prepare_write_request(att_connection_t * att_connection,
|
||||
}
|
||||
|
||||
/*
|
||||
@brief transcation queue of prepared writes, e.g., after disconnect
|
||||
@brief transaction queue of prepared writes, e.g., after disconnect
|
||||
*/
|
||||
void att_clear_transaction_queue(att_connection_t * att_connection) {
|
||||
(*att_write_callback)(att_connection->con_handle, 0, ATT_TRANSACTION_MODE_CANCEL, 0, NULL, 0);
|
||||
|
||||
@@ -257,7 +257,7 @@ extern "C" {
|
||||
uint8_t * response_buffer);
|
||||
|
||||
/**
|
||||
@brief transcation queue of prepared writes, e.g., after disconnect
|
||||
@brief transaction queue of prepared writes, e.g., after disconnect
|
||||
@return att_connection
|
||||
*/
|
||||
void att_clear_transaction_queue(att_connection_t * att_connection);
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// Taken from LWIP's inet_chksum.c just to set the -O2 flag
|
||||
|
||||
/*
|
||||
Copyright (c) 2001-2004 Swedish Institute of Computer Science.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
||||
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
||||
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGE.
|
||||
|
||||
This file is part of the lwIP TCP/IP stack.
|
||||
|
||||
Author: Adam Dunkels <adam@sics.se>
|
||||
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include "lwip/opt.h"
|
||||
|
||||
#include "lwip/inet_chksum.h"
|
||||
#include "lwip/def.h"
|
||||
#include "lwip/ip_addr.h"
|
||||
|
||||
#pragma GCC optimize ("O2")
|
||||
/**
|
||||
An optimized checksum routine. Basically, it uses loop-unrolling on
|
||||
the checksum loop, treating the head and tail bytes specially, whereas
|
||||
the inner loop acts on 8 bytes at a time.
|
||||
|
||||
@arg start of buffer to be checksummed. May be an odd byte address.
|
||||
@len number of bytes in the buffer to be checksummed.
|
||||
@return host order (!) lwip checksum (non-inverted Internet sum)
|
||||
|
||||
by Curt McDowell, Broadcom Corp. December 8th, 2005
|
||||
*/
|
||||
extern "C" u16_t lwip_standard_chksum(const void *dataptr, int len) {
|
||||
const u8_t *pb = (const u8_t *)dataptr;
|
||||
const u16_t *ps;
|
||||
u16_t t = 0;
|
||||
const u32_t *pl;
|
||||
u32_t sum = 0, tmp;
|
||||
/* starts at odd byte address? */
|
||||
int odd = ((mem_ptr_t)pb & 1);
|
||||
|
||||
if (odd && len > 0) {
|
||||
((u8_t *)&t)[1] = *pb++;
|
||||
len--;
|
||||
}
|
||||
|
||||
ps = (const u16_t *)(const void *)pb;
|
||||
|
||||
if (((mem_ptr_t)ps & 3) && len > 1) {
|
||||
sum += *ps++;
|
||||
len -= 2;
|
||||
}
|
||||
|
||||
pl = (const u32_t *)(const void *)ps;
|
||||
|
||||
while (len > 7) {
|
||||
tmp = sum + *pl++; /* ping */
|
||||
if (tmp < sum) {
|
||||
tmp++; /* add back carry */
|
||||
}
|
||||
|
||||
sum = tmp + *pl++; /* pong */
|
||||
if (sum < tmp) {
|
||||
sum++; /* add back carry */
|
||||
}
|
||||
|
||||
len -= 8;
|
||||
}
|
||||
|
||||
/* make room in upper bits */
|
||||
sum = FOLD_U32T(sum);
|
||||
|
||||
ps = (const u16_t *)pl;
|
||||
|
||||
/* 16-bit aligned word remaining? */
|
||||
while (len > 1) {
|
||||
sum += *ps++;
|
||||
len -= 2;
|
||||
}
|
||||
|
||||
/* dangling tail byte remaining? */
|
||||
if (len > 0) { /* include odd byte */
|
||||
((u8_t *)&t)[0] = *(const u8_t *)ps;
|
||||
}
|
||||
|
||||
sum += t; /* add end bytes */
|
||||
|
||||
/* Fold 32-bit sum to 16 bits
|
||||
calling this twice is probably faster than if statements... */
|
||||
sum = FOLD_U32T(sum);
|
||||
sum = FOLD_U32T(sum);
|
||||
|
||||
if (odd) {
|
||||
sum = SWAP_BYTES_IN_WORD(sum);
|
||||
}
|
||||
|
||||
return (u16_t)sum;
|
||||
}
|
||||
|
||||
+2
-2
@@ -40,8 +40,8 @@ For many BTStack examples, you simply need call the included
|
||||
called afterwards to start processing (in the background).
|
||||
|
||||
You will also need to acquire the BT ``async_context`` system lock before
|
||||
calling any BTStack APIs. See the ``libraries/PicoBluetoothHID`` helper
|
||||
class for an example of how to do this.
|
||||
calling any BTStack APIs. ``__lockBluetooth`` and ``unlockBluetooth`` are
|
||||
provided in the PicoW variant code.
|
||||
|
||||
Note that if you need to modify the system ``btstack_config.h`` file, do so
|
||||
in the ``tools/libpico`` directory and rebuild the Pico SDK static library.
|
||||
|
||||
+2
-2
@@ -54,9 +54,9 @@ author = u'Earle F. Philhower, III'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'3.8.1'
|
||||
version = u'3.9.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'3.8.1'
|
||||
release = u'3.9.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
+2
-2
@@ -597,7 +597,7 @@ close
|
||||
Close the file. No other operations should be performed on *File* object
|
||||
after ``close`` function was called.
|
||||
|
||||
openNextFile (compatibiity method, not recommended for new code)
|
||||
openNextFile (compatibility method, not recommended for new code)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: cpp
|
||||
@@ -609,7 +609,7 @@ openNextFile (compatibiity method, not recommended for new code)
|
||||
Opens the next file in the directory pointed to by the File. Only valid
|
||||
when ``File.isDirectory() == true``.
|
||||
|
||||
rewindDirectory (compatibiity method, not recommended for new code)
|
||||
rewindDirectory (compatibility method, not recommended for new code)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
+37
-5
@@ -8,10 +8,10 @@ SPI pinouts can be set **before SPI.begin()** using the following calls:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
bool setRX(pin_size_t pin);
|
||||
bool setRX(pin_size_t pin); // or setMISO()
|
||||
bool setCS(pin_size_t pin);
|
||||
bool setSCK(pin_size_t pin);
|
||||
bool setTX(pin_size_t pin);
|
||||
bool setTX(pin_size_t pin); // or setMOSI()
|
||||
|
||||
Note that the ``CS`` pin can be hardware or software controlled by the sketch.
|
||||
When software controlled, the ``setCS()`` call is ignored.
|
||||
@@ -39,7 +39,39 @@ in order to consunme the received data and provide data to transmit.
|
||||
* The callbacks operate at IRQ time and may be called very frequently at high SPI frequencies. So, make then small, fast, and with no memory allocations or locking.
|
||||
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
Asynchronous Operation
|
||||
======================
|
||||
|
||||
See the SPItoMyself example for a complete Master and Slave application.
|
||||
Applications can use asynchronous SPI calls to allow for processing while long-running SPI transfers are
|
||||
being performed. For example, a game could send a full screen update out over SPI and immediately start
|
||||
processing the next frame without waiting for the first one to be sent. DMA is used to handle
|
||||
the transfer to/from the hardware freeing the CPU from bit-banging or busy waiting.
|
||||
|
||||
Note that asynchronous operations can not be intersped with normal, synchronous ones. ``transferAsync``
|
||||
should still occur after a ``beginTransaction()`` and when ``finishedAsync()`` returns ``true`` then
|
||||
``endTransaction()`` should also be called.
|
||||
|
||||
All buffers need to be valid throughout the entire operation. Read data cannot be accessed until
|
||||
the transaction is completed and can't be "peeked" at while the operation is ongoing.
|
||||
|
||||
bool transferAsync(const void \*send, void \*recv, size_t bytes)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Begins an SPI asynchronous transaction. Either ``send`` or ``recv`` can be ``nullptr`` if data only needs
|
||||
to be transferred in one direction.
|
||||
Check ``finishedAsync()`` to determine when the operation completes and conclude the transaction.
|
||||
This operation needs to allocate a buffer from heap equal to ``bytes`` in size if ``LSBMODE`` is used.
|
||||
|
||||
bool finishedAsync()
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Call to check if the asynchronous operations is completed and the buffer passed in can be either read or
|
||||
reused. Frees the allocated memory and completes the asynchronous transaction.
|
||||
|
||||
void abortAsync()
|
||||
~~~~~~~~~~~~~~~~~
|
||||
Cancels the outstanding asynchronous transaction and frees any allocated memory.
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
See the SPItoMyself and SPItoMyselfAsync examples for a complete Master and Slave application.
|
||||
|
||||
@@ -24,3 +24,41 @@ Master transmissions are buffered (up to 256 bytes) and only performed
|
||||
on ``endTransmission``, as is standard with modern Arduino Wire implementations.
|
||||
|
||||
For more detailed information, check the `Arduino Wire documentation <https://www.arduino.cc/en/reference/wire>`_ .
|
||||
|
||||
Asynchronous Operation
|
||||
----------------------
|
||||
|
||||
Applications can use asynchronous I2C calls to allow for processing while long-running I2C operations are
|
||||
being performed. For example, a game could send a full screen update out over I2C and immediately start
|
||||
processing the next frame without waiting for the first one to be sent over I2C. DMA is used to handle
|
||||
the transfer to/from the I2C hardware freeing the CPU from bit-banging or busy waiting.
|
||||
|
||||
Note that asynchronous operations can not be intersped with normal, synchronous ones. Fully complete an
|
||||
asynchronous operation before attempting to do a normal ``Wire.beginTransaction()`` or ``Wire.requestFrom``.
|
||||
Also, all buffers need to be valid throughout the entire operation. Read data cannot be accessed until
|
||||
the transaction is completed and can't be "peeked" at while the operation is ongoing.
|
||||
|
||||
|
||||
bool writeAsync(uint8_t address, const void \*buffer, size_t bytes, bool sendStop)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Begins an I2C asynchronous write transaction. Writes to ``address`` of ``bytes`` from ``buffer`` and
|
||||
at the end will send an I2C stop if ``sendStop`` is ``true``.
|
||||
Check ``finishedAsync()`` to determine when the operation completes and conclude the transaction.
|
||||
This operation needs to allocate a buffer from heap equal to 2x ``bytes`` in size.
|
||||
|
||||
bool readAsync(uint8_t address, void \*buffer, size_t bytes, bool sendStop)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Begins an I2C asynchronous read transaction. Reads from ``address`` for ``bytes`` into ``buffer`` and
|
||||
at the end will send an I2C stop if ``sendStop`` is ``true``.
|
||||
Check ``finishedAsync()`` to determine when the operation completes and conclude the transaction.
|
||||
This operation needs to allocate a buffer from heap equal to 4x ``bytes`` in size.
|
||||
|
||||
bool finishedAsync()
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Call to check if the asynchronous operations is completed and the buffer passed in can be either read or
|
||||
reused. Frees the allocated memory and completes the asynchronous transaction.
|
||||
|
||||
void abortAsync()
|
||||
~~~~~~~~~~~~~~~~~
|
||||
Cancels the outstanding asynchronous transaction and frees any allocated memory.
|
||||
|
||||
|
||||
+6
-4
@@ -38,12 +38,12 @@ extern unsigned long __lwip_rand(void);
|
||||
#define LWIP_NETIF_LINK_CALLBACK 1
|
||||
#define LWIP_NETIF_HOSTNAME 1
|
||||
#define LWIP_NETCONN 0
|
||||
#define MEM_STATS 0
|
||||
#define SYS_STATS 0
|
||||
#define MEMP_STATS 0
|
||||
#define MEM_STATS 1
|
||||
#define SYS_STATS 1
|
||||
#define MEMP_STATS 1
|
||||
#define LINK_STATS 0
|
||||
// #define ETH_PAD_SIZE 2
|
||||
#define LWIP_CHKSUM_ALGORITHM 3
|
||||
#define LWIP_CHKSUM_ALGORITHM 0
|
||||
#define LWIP_DHCP 1
|
||||
#define LWIP_IPV4 1
|
||||
#define LWIP_TCP 1
|
||||
@@ -72,6 +72,8 @@ extern void __setSystemTime(unsigned long long sec, unsigned long us);
|
||||
#define LWIP_DEBUG 1
|
||||
#define LWIP_STATS 1
|
||||
#define LWIP_STATS_DISPLAY 1
|
||||
#define MEMP_STATS 1
|
||||
#define MEM_STATS 1
|
||||
#endif
|
||||
|
||||
#define ETHARP_DEBUG LWIP_DBG_OFF
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -203,6 +203,9 @@
|
||||
-Wl,--wrap=raw_sendto
|
||||
-Wl,--wrap=raw_remove
|
||||
|
||||
-Wl,--wrap=netif_add
|
||||
-Wl,--wrap=netif_remove
|
||||
|
||||
-Wl,--wrap=cyw43_cb_process_ethernet
|
||||
-Wl,--wrap=cyw43_cb_tcpip_set_link_up
|
||||
-Wl,--wrap=cyw43_cb_tcpip_set_link_down
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// A2DPSink example - Released to the public domain in 2024 by Earle F. Philhower, III
|
||||
|
||||
// Hook up a phono plug to GP0 and GP1 (and GND of course...the 1st 3 pins on the PCB)
|
||||
// Connect wired earbuds up and connect over BT from your phone and play some music.
|
||||
|
||||
#include <BluetoothAudio.h>
|
||||
#include <PWMAudio.h>
|
||||
|
||||
PWMAudio pwm;
|
||||
A2DPSink a2dp;
|
||||
|
||||
volatile A2DPSink::PlaybackStatus status = A2DPSink::STOPPED;
|
||||
|
||||
void volumeCB(void *param, int pct) {
|
||||
(void) param;
|
||||
Serial.printf("Speaker volume changed to %d%%\n", pct);
|
||||
}
|
||||
|
||||
void connectCB(void *param, bool connected) {
|
||||
(void) param;
|
||||
if (connected) {
|
||||
Serial.printf("A2DP connection started to %s\n", bd_addr_to_str(a2dp.getSourceAddress()));
|
||||
} else {
|
||||
Serial.printf("A2DP connection stopped\n");
|
||||
}
|
||||
}
|
||||
|
||||
void playbackCB(void *param, A2DPSink::PlaybackStatus state) {
|
||||
(void) param;
|
||||
status = state;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(3000);
|
||||
Serial.printf("Starting, connect to the PicoW and start playing music\n");
|
||||
Serial.printf("Use BOOTSEL to pause/resume playback\n");
|
||||
a2dp.setName("PicoW Boom 00:00:00:00:00:00");
|
||||
a2dp.setConsumer(new BluetoothAudioConsumerPWM(pwm));
|
||||
a2dp.onVolume(volumeCB);
|
||||
a2dp.onConnect(connectCB);
|
||||
a2dp.onPlaybackStatus(playbackCB);
|
||||
a2dp.begin();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (BOOTSEL) {
|
||||
__lockBluetooth();
|
||||
if (status == A2DPSink::PAUSED) {
|
||||
a2dp.play();
|
||||
Serial.printf("Resuming\n");
|
||||
} else if (status == A2DPSink::PLAYING) {
|
||||
a2dp.pause();
|
||||
Serial.printf("Pausing\n");
|
||||
}
|
||||
__unlockBluetooth();
|
||||
while (BOOTSEL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Plays tones and Au Claire De La Lune over an A2DP connection
|
||||
// Released to the public domain by Earle Philhower <earlephilhowwer@yahoo.com>
|
||||
|
||||
#include <BluetoothAudio.h>
|
||||
#include "raw.h"
|
||||
|
||||
A2DPSource a2dp;
|
||||
|
||||
int16_t pcm[64 * 2];
|
||||
uint32_t phase = 0;
|
||||
volatile uint32_t fr = 32;
|
||||
volatile uint32_t fl = 16;
|
||||
volatile bool paused = false;
|
||||
volatile bool playwav = false;
|
||||
|
||||
void avrcpCB(void *param, avrcp_operation_id_t op, int pressed) {
|
||||
(void) param;
|
||||
if (pressed && op == AVRCP_OPERATION_ID_FORWARD) {
|
||||
// Change the sound when they hit play
|
||||
fr = random(8, 64);
|
||||
fl = random(8, 64);
|
||||
Serial.printf("Now generating %lu, %lu\n", fr, fl);
|
||||
} else if (pressed && op == AVRCP_OPERATION_ID_PLAY) {
|
||||
paused = !paused;
|
||||
if (paused) {
|
||||
Serial.printf("Pausing\n");
|
||||
} else {
|
||||
Serial.printf("Resuming\n");
|
||||
}
|
||||
} else if (pressed && op == AVRCP_OPERATION_ID_BACKWARD) {
|
||||
playwav = !playwav;
|
||||
Serial.println(playwav ? "Playing 'Au Claire De La Lune'" : "Playing tones");
|
||||
}
|
||||
}
|
||||
|
||||
void volumeCB(void *param, int pct) {
|
||||
(void) param;
|
||||
Serial.printf("Speaker volume changed to %d%%\n", pct);
|
||||
}
|
||||
|
||||
void connectCB(void *param, bool connected) {
|
||||
(void) param;
|
||||
if (connected) {
|
||||
Serial.printf("A2DP connection started to %s\n", bd_addr_to_str(a2dp.getSinkAddress()));
|
||||
} else {
|
||||
Serial.printf("A2DP connection stopped\n");
|
||||
}
|
||||
}
|
||||
|
||||
void fillPCM() {
|
||||
if (paused) {
|
||||
bzero(pcm, sizeof(pcm));
|
||||
return;
|
||||
}
|
||||
if (playwav) {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
// Audio in flash is 8-bit signed mono, so shift left to make 16-bit and then play over both channels
|
||||
pcm[i * 2] = auclairedelalune_raw[(i + phase) % sizeof(auclairedelalune_raw)] << 8;
|
||||
pcm[i * 2 + 1] = auclairedelalune_raw[(i + phase) % sizeof(auclairedelalune_raw)] << 8;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
pcm[i * 2] = ((i + phase) / fr) & 1 ? 3000 : -3000;
|
||||
pcm[i * 2 + 1] = ((i + phase) / fl) & 1 ? 1000 : -1000;
|
||||
}
|
||||
}
|
||||
phase += 64;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
delay(2000);
|
||||
a2dp.onAVRCP(avrcpCB);
|
||||
a2dp.onVolume(volumeCB);
|
||||
a2dp.onConnect(connectCB);
|
||||
a2dp.begin();
|
||||
Serial.printf("Starting, press BOOTSEL to pair to first found speaker\n");
|
||||
Serial.printf("Use the forward button on speaker to change tones\n");
|
||||
Serial.printf("Use the reverse button on speaker to alternate between tones and Au Claire De La Lune\n");
|
||||
Serial.printf("Use the play button on speaker to pause/unpause the tone\n");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
while ((size_t)a2dp.availableForWrite() > sizeof(pcm)) {
|
||||
fillPCM();
|
||||
a2dp.write((const uint8_t *)pcm, sizeof(pcm));
|
||||
}
|
||||
if (BOOTSEL) {
|
||||
while (BOOTSEL) {
|
||||
delay(1);
|
||||
}
|
||||
a2dp.disconnect();
|
||||
a2dp.clearPairing();
|
||||
Serial.printf("Connecting...");
|
||||
if (a2dp.connect()) {
|
||||
Serial.printf("Connected!\n");
|
||||
} else {
|
||||
Serial.printf("Failed! :(\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,67 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
BluetoothAudio KEYWORD1
|
||||
BluetoothHCI KEYWORD1
|
||||
A2DPSource KEYWORD1
|
||||
BTDeviceInfo KEYWORD1
|
||||
BluetoothAudioConsumer KEYWORD1
|
||||
BluetoothAudioConsumerI2S KEYWORD1
|
||||
BluetoothAudioConsumerPWM KEYWORD1
|
||||
A2DPSink KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
begin KEYWORD2
|
||||
end KEYWORD2
|
||||
|
||||
setConsumer KEYWORD2
|
||||
setFrequency KEYWORD2
|
||||
setName KEYWORD2
|
||||
setsetBufferSize KEYWORD2
|
||||
getUnderflow KEYWORD2
|
||||
getSinkAddress KEYWORD2
|
||||
scan KEYWORD2
|
||||
connect KEYWORD2
|
||||
connected KEYWORD2
|
||||
disconnect KEYWORD2
|
||||
clearPairing KEYWORD2
|
||||
connect KEYWORD2
|
||||
|
||||
play KEYWORD2
|
||||
stop KEYWORD2
|
||||
pause KEYWORD2
|
||||
fastForward KEYWORD2
|
||||
rewind KEYWORD2
|
||||
forward KEYWORD2
|
||||
backward KEYWORD2
|
||||
volumeUp KEYWORD2
|
||||
volumeDown KEYWORD2
|
||||
mute KEYWORD2
|
||||
|
||||
onTransmit KEYWORD2
|
||||
onAVRCP KEYWORD2
|
||||
onBattery KEYWORD2
|
||||
onVolume KEYWORD2
|
||||
onConnect KEYWORD2
|
||||
onPlaybackStatus KEYWORD2
|
||||
|
||||
# BTDeviceInfo
|
||||
deviceClass KEYWORD2
|
||||
address KEYWORD2
|
||||
addressString KEYWORD2
|
||||
rssi KEYWORD2
|
||||
name KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
STOPPED LITERAL
|
||||
PLAYING LITERAL
|
||||
PAUSED LITERAL
|
||||
@@ -0,0 +1,10 @@
|
||||
name=BluetoothAudio
|
||||
version=1.0
|
||||
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
sentence=Plays and receives audio over Bluetooth A2DP
|
||||
paragraph=Plays and receives audio over Bluetooth A2DP
|
||||
category=Communication
|
||||
url=http://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,800 @@
|
||||
/*
|
||||
A2DP Sink (Bluetooth audio receiver)
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "A2DPSink.h"
|
||||
#include <functional>
|
||||
|
||||
|
||||
#define CCALLBACKNAME _A2DPSINKCB
|
||||
#include <ctocppcallback.h>
|
||||
|
||||
#define PACKETHANDLERCB(class, cbFcn) \
|
||||
(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), \
|
||||
static_cast<btstack_packet_handler_t>(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
|
||||
|
||||
#define L2CAPPACKETHANDLERCB(class, cbFcn) \
|
||||
(CCALLBACKNAME<void(uint8_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), \
|
||||
static_cast<void (*)(uint8_t, uint8_t *, uint16_t)>(CCALLBACKNAME<void(uint8_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
|
||||
|
||||
#define PCMDECODERCB(class, cbFcn) \
|
||||
(CCALLBACKNAME<void(int16_t*, int, int, int, void*), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), \
|
||||
static_cast<void (*)(int16_t *, int, int, int, void *)>(CCALLBACKNAME<void(int16_t *, int, int, int, void *), __COUNTER__ - 1>::callback))
|
||||
|
||||
// Based off of the BlueKitchen A2DP sink demo
|
||||
/*
|
||||
Copyright (C) 2023 BlueKitchen GmbH
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holders nor the names of
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
4. Any redistribution, use, or modification is done solely for
|
||||
personal benefit and not for any commercial purpose or for
|
||||
monetary gain.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
Please inquire about commercial licensing options at
|
||||
contact@bluekitchen-gmbh.com
|
||||
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "btstack.h"
|
||||
#include "btstack_resample.h"
|
||||
#include "btstack_ring_buffer.h"
|
||||
|
||||
|
||||
bool A2DPSink::begin() {
|
||||
if (_running || !_consumer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// init protocols
|
||||
l2cap_init();
|
||||
sdp_init();
|
||||
#ifdef ENABLE_BLE
|
||||
// Initialize LE Security Manager. Needed for cross-transport key derivation
|
||||
sm_init();
|
||||
#endif
|
||||
|
||||
// Init profiles
|
||||
a2dp_sink_init();
|
||||
avrcp_init();
|
||||
avrcp_controller_init();
|
||||
avrcp_target_init();
|
||||
|
||||
|
||||
// Configure A2DP Sink
|
||||
a2dp_sink_register_packet_handler(PACKETHANDLERCB(A2DPSink, a2dp_sink_packet_handler));
|
||||
a2dp_sink_register_media_handler(L2CAPPACKETHANDLERCB(A2DPSink, handle_l2cap_media_data_packet));
|
||||
a2dp_sink_stream_endpoint_t * stream_endpoint = &a2dp_sink_stream_endpoint;
|
||||
avdtp_stream_endpoint_t * local_stream_endpoint = a2dp_sink_create_stream_endpoint(AVDTP_AUDIO,
|
||||
AVDTP_CODEC_SBC, media_sbc_codec_capabilities, sizeof(media_sbc_codec_capabilities),
|
||||
stream_endpoint->media_sbc_codec_configuration, sizeof(stream_endpoint->media_sbc_codec_configuration));
|
||||
if (!local_stream_endpoint) {
|
||||
DEBUGV("A2DP Source: not enough memory to create local stream endpoint\n");
|
||||
return false;
|
||||
}
|
||||
// - Store stream enpoint's SEP ID, as it is used by A2DP API to identify the stream endpoint
|
||||
stream_endpoint->a2dp_local_seid = avdtp_local_seid(local_stream_endpoint);
|
||||
|
||||
|
||||
// Configure AVRCP Controller + Target
|
||||
avrcp_register_packet_handler(PACKETHANDLERCB(A2DPSink, avrcp_packet_handler));
|
||||
avrcp_controller_register_packet_handler(PACKETHANDLERCB(A2DPSink, avrcp_controller_packet_handler));
|
||||
avrcp_target_register_packet_handler(PACKETHANDLERCB(A2DPSink, avrcp_target_packet_handler));
|
||||
|
||||
|
||||
// Configure SDP
|
||||
|
||||
// - Create and register A2DP Sink service record
|
||||
memset(sdp_avdtp_sink_service_buffer, 0, sizeof(sdp_avdtp_sink_service_buffer));
|
||||
a2dp_sink_create_sdp_record(sdp_avdtp_sink_service_buffer, sdp_create_service_record_handle(),
|
||||
AVDTP_SINK_FEATURE_MASK_HEADPHONE, NULL, NULL);
|
||||
sdp_register_service(sdp_avdtp_sink_service_buffer);
|
||||
|
||||
// - Create AVRCP Controller service record and register it with SDP. We send Category 1 commands to the media player, e.g. play/pause
|
||||
memset(sdp_avrcp_controller_service_buffer, 0, sizeof(sdp_avrcp_controller_service_buffer));
|
||||
uint16_t controller_supported_features = 1 << AVRCP_CONTROLLER_SUPPORTED_FEATURE_CATEGORY_PLAYER_OR_RECORDER;
|
||||
avrcp_controller_create_sdp_record(sdp_avrcp_controller_service_buffer, sdp_create_service_record_handle(),
|
||||
controller_supported_features, NULL, NULL);
|
||||
sdp_register_service(sdp_avrcp_controller_service_buffer);
|
||||
|
||||
// - Create and register A2DP Sink service record
|
||||
// - We receive Category 2 commands from the media player, e.g. volume up/down
|
||||
memset(sdp_avrcp_target_service_buffer, 0, sizeof(sdp_avrcp_target_service_buffer));
|
||||
uint16_t target_supported_features = 1 << AVRCP_TARGET_SUPPORTED_FEATURE_CATEGORY_MONITOR_OR_AMPLIFIER;
|
||||
avrcp_target_create_sdp_record(sdp_avrcp_target_service_buffer,
|
||||
sdp_create_service_record_handle(), target_supported_features, NULL, NULL);
|
||||
sdp_register_service(sdp_avrcp_target_service_buffer);
|
||||
|
||||
// - Create and register Device ID (PnP) service record
|
||||
memset(device_id_sdp_service_buffer, 0, sizeof(device_id_sdp_service_buffer));
|
||||
device_id_create_sdp_record(device_id_sdp_service_buffer,
|
||||
sdp_create_service_record_handle(), DEVICE_ID_VENDOR_ID_SOURCE_BLUETOOTH, BLUETOOTH_COMPANY_ID_BLUEKITCHEN_GMBH, 1, 1);
|
||||
sdp_register_service(device_id_sdp_service_buffer);
|
||||
|
||||
|
||||
// Configure GAP - discovery / connection
|
||||
|
||||
// - Set local name with a template Bluetooth address, that will be automatically
|
||||
// replaced with an actual address once it is available, i.e. when BTstack boots
|
||||
// up and starts talking to a Bluetooth module.
|
||||
if (!_name) {
|
||||
setName("PicoW A2DP 00:00:00:00:00:00");
|
||||
}
|
||||
gap_set_local_name(_name);
|
||||
|
||||
// - Allow to show up in Bluetooth inquiry
|
||||
gap_discoverable_control(1);
|
||||
|
||||
// - Set Class of Device - Service Class: Audio, Major Device Class: Audio, Minor: Loudspeaker
|
||||
gap_set_class_of_device(0x200414);
|
||||
|
||||
// - Allow for role switch in general and sniff mode
|
||||
gap_set_default_link_policy_settings(LM_LINK_POLICY_ENABLE_ROLE_SWITCH | LM_LINK_POLICY_ENABLE_SNIFF_MODE);
|
||||
|
||||
// - Allow for role switch on outgoing connections
|
||||
// - This allows A2DP Source, e.g. smartphone, to become master when we re-connect to it.
|
||||
gap_set_allow_role_switch(true);
|
||||
|
||||
|
||||
// Register for HCI events
|
||||
// hci_event_callback_registration.callback = &hci_packet_handler;
|
||||
// hci_add_event_handler(&hci_event_callback_registration);
|
||||
_hci.install();
|
||||
_running = true;
|
||||
_hci.begin();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool A2DPSink::disconnect() {
|
||||
__lockBluetooth();
|
||||
a2dp_sink_disconnect(a2dp_sink_a2dp_connection.a2dp_cid);
|
||||
__unlockBluetooth();
|
||||
if (!_running || !_connected) {
|
||||
return false;
|
||||
}
|
||||
_connected = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void A2DPSink::clearPairing() {
|
||||
disconnect();
|
||||
__lockBluetooth();
|
||||
gap_delete_all_link_keys();
|
||||
__unlockBluetooth();
|
||||
}
|
||||
|
||||
|
||||
void A2DPSink::playback_handler(int16_t * buffer, uint16_t num_audio_frames) {
|
||||
|
||||
// called from lower-layer but guaranteed to be on main thread
|
||||
if (sbc_frame_size == 0) {
|
||||
memset(buffer, 0, num_audio_frames * BYTES_PER_FRAME);
|
||||
return;
|
||||
}
|
||||
|
||||
// first fill from resampled audio
|
||||
uint32_t bytes_read;
|
||||
btstack_ring_buffer_read(&decoded_audio_ring_buffer, (uint8_t *) buffer, num_audio_frames * BYTES_PER_FRAME, &bytes_read);
|
||||
buffer += bytes_read / NUM_CHANNELS;
|
||||
num_audio_frames -= bytes_read / BYTES_PER_FRAME;
|
||||
|
||||
// then start decoding sbc frames using request_* globals
|
||||
request_buffer = buffer;
|
||||
request_frames = num_audio_frames;
|
||||
while (request_frames && btstack_ring_buffer_bytes_available(&sbc_frame_ring_buffer) >= sbc_frame_size) {
|
||||
// decode frame
|
||||
uint8_t sbc_frame[MAX_SBC_FRAME_SIZE];
|
||||
btstack_ring_buffer_read(&sbc_frame_ring_buffer, sbc_frame, sbc_frame_size, &bytes_read);
|
||||
btstack_sbc_decoder_process_data(&state, 0, sbc_frame, sbc_frame_size);
|
||||
}
|
||||
while (request_frames) {
|
||||
*(request_buffer++) = 0;
|
||||
*(request_buffer++) = 0;
|
||||
request_frames--;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void A2DPSink::handle_pcm_data(int16_t * data, int num_audio_frames, int num_channels, int sample_rate, void * context) {
|
||||
UNUSED(sample_rate);
|
||||
UNUSED(context);
|
||||
UNUSED(num_channels); // must be stereo == 2
|
||||
|
||||
// resample into request buffer - add some additional space for resampling
|
||||
uint32_t resampled_frames = btstack_resample_block(&resample_instance, data, num_audio_frames, output_buffer);
|
||||
|
||||
// store data in btstack_audio buffer first
|
||||
int frames_to_copy = btstack_min(resampled_frames, request_frames);
|
||||
memcpy(request_buffer, output_buffer, frames_to_copy * BYTES_PER_FRAME);
|
||||
request_frames -= frames_to_copy;
|
||||
request_buffer += frames_to_copy * NUM_CHANNELS;
|
||||
|
||||
// and rest in ring buffer
|
||||
int frames_to_store = resampled_frames - frames_to_copy;
|
||||
if (frames_to_store) {
|
||||
int status = btstack_ring_buffer_write(&decoded_audio_ring_buffer, (uint8_t *)&output_buffer[frames_to_copy * NUM_CHANNELS], frames_to_store * BYTES_PER_FRAME);
|
||||
if (status) {
|
||||
DEBUGV("Error storing samples in PCM ring buffer!!!\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int A2DPSink::media_processing_init(BluetoothMediaCodecConfigurationSBC * configuration) {
|
||||
if (media_initialized) {
|
||||
return 0;
|
||||
}
|
||||
btstack_sbc_decoder_init(&state, mode, PCMDECODERCB(A2DPSink, handle_pcm_data), NULL);
|
||||
|
||||
btstack_ring_buffer_init(&sbc_frame_ring_buffer, sbc_frame_storage, sizeof(sbc_frame_storage));
|
||||
btstack_ring_buffer_init(&decoded_audio_ring_buffer, decoded_audio_storage, sizeof(decoded_audio_storage));
|
||||
btstack_resample_init(&resample_instance, configuration->num_channels);
|
||||
|
||||
// setup audio playback
|
||||
_consumer->init(NUM_CHANNELS, configuration->sampling_frequency, this);
|
||||
|
||||
audio_stream_started = 0;
|
||||
media_initialized = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void A2DPSink::media_processing_start(void) {
|
||||
if (!media_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// setup audio playback
|
||||
_consumer->startStream();
|
||||
audio_stream_started = 1;
|
||||
}
|
||||
|
||||
void A2DPSink::media_processing_pause(void) {
|
||||
if (!media_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// stop audio playback
|
||||
audio_stream_started = 0;
|
||||
_consumer->stopStream();
|
||||
|
||||
// discard pending data
|
||||
btstack_ring_buffer_reset(&decoded_audio_ring_buffer);
|
||||
btstack_ring_buffer_reset(&sbc_frame_ring_buffer);
|
||||
}
|
||||
|
||||
void A2DPSink::media_processing_close(void) {
|
||||
if (!media_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
media_initialized = 0;
|
||||
audio_stream_started = 0;
|
||||
sbc_frame_size = 0;
|
||||
|
||||
// stop audio playback
|
||||
_consumer->close();
|
||||
}
|
||||
|
||||
/* @section Handle Media Data Packet
|
||||
|
||||
@text Here the audio data, are received through the handle_l2cap_media_data_packet callback.
|
||||
Currently, only the SBC media codec is supported. Hence, the media data consists of the media packet header and the SBC packet.
|
||||
The SBC frame will be stored in a ring buffer for later processing (instead of decoding it to PCM right away which would require a much larger buffer).
|
||||
If the audio stream wasn't started already and there are enough SBC frames in the ring buffer, start playback.
|
||||
*/
|
||||
|
||||
void A2DPSink::handle_l2cap_media_data_packet(uint8_t seid, uint8_t *packet, uint16_t size) {
|
||||
UNUSED(seid);
|
||||
int pos = 0;
|
||||
|
||||
avdtp_media_packet_header_t media_header;
|
||||
if (!read_media_data_header(packet, size, &pos, &media_header)) {
|
||||
return;
|
||||
}
|
||||
|
||||
avdtp_sbc_codec_header_t sbc_header;
|
||||
if (!read_sbc_header(packet, size, &pos, &sbc_header)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int packet_length = size - pos;
|
||||
uint8_t *packet_begin = packet + pos;
|
||||
|
||||
// store sbc frame size for buffer management
|
||||
sbc_frame_size = packet_length / sbc_header.num_frames;
|
||||
int status = btstack_ring_buffer_write(&sbc_frame_ring_buffer, packet_begin, packet_length);
|
||||
if (status != ERROR_CODE_SUCCESS) {
|
||||
DEBUGV("Error storing samples in SBC ring buffer!!!\n");
|
||||
}
|
||||
|
||||
// decide on audio sync drift based on number of sbc frames in queue
|
||||
int sbc_frames_in_buffer = btstack_ring_buffer_bytes_available(&sbc_frame_ring_buffer) / sbc_frame_size;
|
||||
|
||||
uint32_t resampling_factor;
|
||||
|
||||
// nominal factor (fixed-point 2^16) and compensation offset
|
||||
uint32_t nominal_factor = 0x10000;
|
||||
uint32_t compensation = 0x00100;
|
||||
|
||||
if (sbc_frames_in_buffer < OPTIMAL_FRAMES_MIN) {
|
||||
resampling_factor = nominal_factor - compensation; // stretch samples
|
||||
} else if (sbc_frames_in_buffer <= OPTIMAL_FRAMES_MAX) {
|
||||
resampling_factor = nominal_factor; // nothing to do
|
||||
} else {
|
||||
resampling_factor = nominal_factor + compensation; // compress samples
|
||||
}
|
||||
|
||||
btstack_resample_set_factor(&resample_instance, resampling_factor);
|
||||
|
||||
// start stream if enough frames buffered
|
||||
if (!audio_stream_started && sbc_frames_in_buffer >= OPTIMAL_FRAMES_MIN) {
|
||||
media_processing_start();
|
||||
}
|
||||
}
|
||||
|
||||
int A2DPSink::read_sbc_header(uint8_t * packet, int size, int * offset, avdtp_sbc_codec_header_t * sbc_header) {
|
||||
int sbc_header_len = 12; // without crc
|
||||
int pos = *offset;
|
||||
|
||||
if (size - pos < sbc_header_len) {
|
||||
DEBUGV("Not enough data to read SBC header, expected %d, received %d\n", sbc_header_len, size - pos);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sbc_header->fragmentation = get_bit16(packet[pos], 7);
|
||||
sbc_header->starting_packet = get_bit16(packet[pos], 6);
|
||||
sbc_header->last_packet = get_bit16(packet[pos], 5);
|
||||
sbc_header->num_frames = packet[pos] & 0x0f;
|
||||
pos++;
|
||||
*offset = pos;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int A2DPSink::read_media_data_header(uint8_t *packet, int size, int *offset, avdtp_media_packet_header_t *media_header) {
|
||||
int media_header_len = 12; // without crc
|
||||
int pos = *offset;
|
||||
|
||||
if (size - pos < media_header_len) {
|
||||
DEBUGV("Not enough data to read media packet header, expected %d, received %d\n", media_header_len, size - pos);
|
||||
return 0;
|
||||
}
|
||||
|
||||
media_header->version = packet[pos] & 0x03;
|
||||
media_header->padding = get_bit16(packet[pos], 2);
|
||||
media_header->extension = get_bit16(packet[pos], 3);
|
||||
media_header->csrc_count = (packet[pos] >> 4) & 0x0F;
|
||||
pos++;
|
||||
|
||||
media_header->marker = get_bit16(packet[pos], 0);
|
||||
media_header->payload_type = (packet[pos] >> 1) & 0x7F;
|
||||
pos++;
|
||||
|
||||
media_header->sequence_number = big_endian_read_16(packet, pos);
|
||||
pos += 2;
|
||||
|
||||
media_header->timestamp = big_endian_read_32(packet, pos);
|
||||
pos += 4;
|
||||
|
||||
media_header->synchronization_source = big_endian_read_32(packet, pos);
|
||||
pos += 4;
|
||||
*offset = pos;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void A2DPSink::avrcp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
UNUSED(channel);
|
||||
UNUSED(size);
|
||||
uint16_t local_cid;
|
||||
uint8_t status;
|
||||
bd_addr_t address;
|
||||
|
||||
a2dp_sink_avrcp_connection_t * connection = &a2dp_sink_avrcp_connection;
|
||||
|
||||
if (packet_type != HCI_EVENT_PACKET) {
|
||||
return;
|
||||
}
|
||||
if (hci_event_packet_get_type(packet) != HCI_EVENT_AVRCP_META) {
|
||||
return;
|
||||
}
|
||||
switch (packet[2]) {
|
||||
case AVRCP_SUBEVENT_CONNECTION_ESTABLISHED: {
|
||||
local_cid = avrcp_subevent_connection_established_get_avrcp_cid(packet);
|
||||
status = avrcp_subevent_connection_established_get_status(packet);
|
||||
if (status != ERROR_CODE_SUCCESS) {
|
||||
DEBUGV("AVRCP: Connection failed, status 0x%02x\n", status);
|
||||
connection->avrcp_cid = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
connection->avrcp_cid = local_cid;
|
||||
avrcp_subevent_connection_established_get_bd_addr(packet, address);
|
||||
DEBUGV("AVRCP: Connected to %s, cid 0x%02x\n", bd_addr_to_str(address), connection->avrcp_cid);
|
||||
|
||||
avrcp_target_support_event(connection->avrcp_cid, AVRCP_NOTIFICATION_EVENT_VOLUME_CHANGED);
|
||||
avrcp_target_support_event(connection->avrcp_cid, AVRCP_NOTIFICATION_EVENT_BATT_STATUS_CHANGED);
|
||||
avrcp_target_battery_status_changed(connection->avrcp_cid, battery_status);
|
||||
|
||||
// query supported events:
|
||||
avrcp_controller_get_supported_events(connection->avrcp_cid);
|
||||
return;
|
||||
}
|
||||
|
||||
case AVRCP_SUBEVENT_CONNECTION_RELEASED:
|
||||
DEBUGV("AVRCP: Channel released: cid 0x%02x\n", avrcp_subevent_connection_released_get_avrcp_cid(packet));
|
||||
connection->avrcp_cid = 0;
|
||||
connection->notifications_supported_by_target = 0;
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void A2DPSink::avrcp_controller_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
UNUSED(channel);
|
||||
UNUSED(size);
|
||||
|
||||
// helper to print c strings
|
||||
uint8_t avrcp_subevent_value[256];
|
||||
uint8_t play_status;
|
||||
uint8_t event_id;
|
||||
|
||||
a2dp_sink_avrcp_connection_t * avrcp_connection = &a2dp_sink_avrcp_connection;
|
||||
|
||||
if (packet_type != HCI_EVENT_PACKET) {
|
||||
return;
|
||||
}
|
||||
if (hci_event_packet_get_type(packet) != HCI_EVENT_AVRCP_META) {
|
||||
return;
|
||||
}
|
||||
if (avrcp_connection->avrcp_cid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(avrcp_subevent_value, 0, sizeof(avrcp_subevent_value));
|
||||
switch (packet[2]) {
|
||||
case AVRCP_SUBEVENT_GET_CAPABILITY_EVENT_ID:
|
||||
avrcp_connection->notifications_supported_by_target |= (1 << avrcp_subevent_get_capability_event_id_get_event_id(packet));
|
||||
break;
|
||||
case AVRCP_SUBEVENT_GET_CAPABILITY_EVENT_ID_DONE:
|
||||
|
||||
DEBUGV("AVRCP Controller: supported notifications by target:\n");
|
||||
for (event_id = (uint8_t) AVRCP_NOTIFICATION_EVENT_FIRST_INDEX; event_id < (uint8_t) AVRCP_NOTIFICATION_EVENT_LAST_INDEX; event_id++) {
|
||||
DEBUGV(" - [%s] %s\n",
|
||||
(avrcp_connection->notifications_supported_by_target & (1 << event_id)) != 0 ? "X" : " ",
|
||||
avrcp_notification2str((avrcp_notification_event_id_t)event_id));
|
||||
}
|
||||
DEBUGV("\n\n");
|
||||
|
||||
// automatically enable notifications
|
||||
avrcp_controller_enable_notification(avrcp_connection->avrcp_cid, AVRCP_NOTIFICATION_EVENT_PLAYBACK_STATUS_CHANGED);
|
||||
avrcp_controller_enable_notification(avrcp_connection->avrcp_cid, AVRCP_NOTIFICATION_EVENT_NOW_PLAYING_CONTENT_CHANGED);
|
||||
avrcp_controller_enable_notification(avrcp_connection->avrcp_cid, AVRCP_NOTIFICATION_EVENT_TRACK_CHANGED);
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_NOTIFICATION_STATE:
|
||||
event_id = (avrcp_notification_event_id_t)avrcp_subevent_notification_state_get_event_id(packet);
|
||||
DEBUGV("AVRCP Controller: %s notification registered\n", avrcp_notification2str((avrcp_notification_event_id_t)event_id));
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_NOTIFICATION_PLAYBACK_POS_CHANGED:
|
||||
DEBUGV("AVRCP Controller: Playback position changed, position %d ms\n", (unsigned int) avrcp_subevent_notification_playback_pos_changed_get_playback_position_ms(packet));
|
||||
break;
|
||||
case AVRCP_SUBEVENT_NOTIFICATION_PLAYBACK_STATUS_CHANGED:
|
||||
DEBUGV("AVRCP Controller: Playback status changed %s\n", avrcp_play_status2str(avrcp_subevent_notification_playback_status_changed_get_play_status(packet)));
|
||||
play_status = avrcp_subevent_notification_playback_status_changed_get_play_status(packet);
|
||||
switch (play_status) {
|
||||
case AVRCP_PLAYBACK_STATUS_PLAYING:
|
||||
avrcp_connection->playing = true;
|
||||
break;
|
||||
default:
|
||||
avrcp_connection->playing = false;
|
||||
break;
|
||||
}
|
||||
if (_playbackStatusCB) {
|
||||
PlaybackStatus status;
|
||||
switch (play_status) {
|
||||
case AVRCP_PLAYBACK_STATUS_PLAYING:
|
||||
status = PLAYING;
|
||||
break;
|
||||
case AVRCP_PLAYBACK_STATUS_PAUSED:
|
||||
status = PAUSED;
|
||||
break;
|
||||
default:
|
||||
status = STOPPED;
|
||||
break;
|
||||
}
|
||||
_playbackStatusCB(_playbackStatusData, status);
|
||||
}
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_NOTIFICATION_NOW_PLAYING_CONTENT_CHANGED:
|
||||
DEBUGV("AVRCP Controller: Playing content changed\n");
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_NOTIFICATION_TRACK_CHANGED:
|
||||
DEBUGV("AVRCP Controller: Track changed\n");
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_NOTIFICATION_AVAILABLE_PLAYERS_CHANGED:
|
||||
DEBUGV("AVRCP Controller: Available Players Changed\n");
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_SHUFFLE_AND_REPEAT_MODE: {
|
||||
uint8_t shuffle_mode = avrcp_subevent_shuffle_and_repeat_mode_get_shuffle_mode(packet);
|
||||
uint8_t repeat_mode = avrcp_subevent_shuffle_and_repeat_mode_get_repeat_mode(packet);
|
||||
(void) shuffle_mode;
|
||||
(void) repeat_mode;
|
||||
DEBUGV("AVRCP Controller: %s, %s\n", avrcp_shuffle2str(shuffle_mode), avrcp_repeat2str(repeat_mode));
|
||||
break;
|
||||
}
|
||||
case AVRCP_SUBEVENT_NOW_PLAYING_TRACK_INFO:
|
||||
DEBUGV("AVRCP Controller: Track %d\n", avrcp_subevent_now_playing_track_info_get_track(packet));
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_NOW_PLAYING_TOTAL_TRACKS_INFO:
|
||||
DEBUGV("AVRCP Controller: Total Tracks %d\n", avrcp_subevent_now_playing_total_tracks_info_get_total_tracks(packet));
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_NOW_PLAYING_TITLE_INFO:
|
||||
if (avrcp_subevent_now_playing_title_info_get_value_len(packet) > 0) {
|
||||
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_title_info_get_value(packet), avrcp_subevent_now_playing_title_info_get_value_len(packet));
|
||||
DEBUGV("AVRCP Controller: Title %s\n", avrcp_subevent_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_NOW_PLAYING_ARTIST_INFO:
|
||||
if (avrcp_subevent_now_playing_artist_info_get_value_len(packet) > 0) {
|
||||
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_artist_info_get_value(packet), avrcp_subevent_now_playing_artist_info_get_value_len(packet));
|
||||
DEBUGV("AVRCP Controller: Artist %s\n", avrcp_subevent_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_NOW_PLAYING_ALBUM_INFO:
|
||||
if (avrcp_subevent_now_playing_album_info_get_value_len(packet) > 0) {
|
||||
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_album_info_get_value(packet), avrcp_subevent_now_playing_album_info_get_value_len(packet));
|
||||
DEBUGV("AVRCP Controller: Album %s\n", avrcp_subevent_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_NOW_PLAYING_GENRE_INFO:
|
||||
if (avrcp_subevent_now_playing_genre_info_get_value_len(packet) > 0) {
|
||||
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_genre_info_get_value(packet), avrcp_subevent_now_playing_genre_info_get_value_len(packet));
|
||||
DEBUGV("AVRCP Controller: Genre %s\n", avrcp_subevent_value);
|
||||
}
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_PLAY_STATUS:
|
||||
DEBUGV("AVRCP Controller: Song length %" PRIu32 " ms, Song position %" PRIu32 " ms, Play status %s\n",
|
||||
avrcp_subevent_play_status_get_song_length(packet),
|
||||
avrcp_subevent_play_status_get_song_position(packet),
|
||||
avrcp_play_status2str(avrcp_subevent_play_status_get_play_status(packet)));
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_OPERATION_COMPLETE:
|
||||
DEBUGV("AVRCP Controller: %s complete\n", avrcp_operation2str(avrcp_subevent_operation_complete_get_operation_id(packet)));
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_OPERATION_START:
|
||||
DEBUGV("AVRCP Controller: %s start\n", avrcp_operation2str(avrcp_subevent_operation_start_get_operation_id(packet)));
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_NOTIFICATION_EVENT_TRACK_REACHED_END:
|
||||
DEBUGV("AVRCP Controller: Track reached end\n");
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_PLAYER_APPLICATION_VALUE_RESPONSE:
|
||||
DEBUGV("AVRCP Controller: Set Player App Value %s\n", avrcp_ctype2str(avrcp_subevent_player_application_value_response_get_command_type(packet)));
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void A2DPSink::avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
UNUSED(channel);
|
||||
UNUSED(size);
|
||||
|
||||
if (packet_type != HCI_EVENT_PACKET) {
|
||||
return;
|
||||
}
|
||||
if (hci_event_packet_get_type(packet) != HCI_EVENT_AVRCP_META) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t volume;
|
||||
char const * button_state;
|
||||
(void) button_state;
|
||||
avrcp_operation_id_t operation_id;
|
||||
|
||||
switch (packet[2]) {
|
||||
case AVRCP_SUBEVENT_NOTIFICATION_VOLUME_CHANGED:
|
||||
volume = avrcp_subevent_notification_volume_changed_get_absolute_volume(packet);
|
||||
volume_percentage = volume * 100 / 127;
|
||||
DEBUGV("AVRCP Target : Volume set to %d%% (%d)\n", volume_percentage, volume);
|
||||
_consumer->setVolume(volume);
|
||||
break;
|
||||
|
||||
case AVRCP_SUBEVENT_OPERATION:
|
||||
operation_id = (avrcp_operation_id_t)avrcp_subevent_operation_get_operation_id(packet);
|
||||
button_state = avrcp_subevent_operation_get_button_pressed(packet) > 0 ? "PRESS" : "RELEASE";
|
||||
DEBUGV("AVRCP Target: operation %s (%s)\n", avrcp_operation2str(operation_id), button_state);
|
||||
if (_avrcpCB) {
|
||||
_avrcpCB(_avrcpData, operation_id, avrcp_subevent_operation_get_button_pressed(packet) > 0);
|
||||
}
|
||||
switch (operation_id) {
|
||||
case AVRCP_OPERATION_ID_VOLUME_UP:
|
||||
DEBUGV("AVRCP Target : VOLUME UP (%s)\n", button_state);
|
||||
break;
|
||||
case AVRCP_OPERATION_ID_VOLUME_DOWN:
|
||||
DEBUGV("AVRCP Target : VOLUME DOWN (%s)\n", button_state);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
DEBUGV("AVRCP Target : Event 0x%02x is not parsed\n", packet[2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void A2DPSink::a2dp_sink_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
UNUSED(channel);
|
||||
UNUSED(size);
|
||||
uint8_t status;
|
||||
|
||||
uint8_t allocation_method;
|
||||
|
||||
if (packet_type != HCI_EVENT_PACKET) {
|
||||
return;
|
||||
}
|
||||
if (hci_event_packet_get_type(packet) != HCI_EVENT_A2DP_META) {
|
||||
return;
|
||||
}
|
||||
|
||||
a2dp_sink_a2dp_connection_t * a2dp_conn = &a2dp_sink_a2dp_connection;
|
||||
|
||||
switch (packet[2]) {
|
||||
case A2DP_SUBEVENT_SIGNALING_MEDIA_CODEC_OTHER_CONFIGURATION:
|
||||
DEBUGV("A2DP Sink : Received non SBC codec - not implemented\n");
|
||||
break;
|
||||
case A2DP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CONFIGURATION: {
|
||||
DEBUGV("A2DP Sink : Received SBC codec configuration\n");
|
||||
a2dp_conn->sbc_configuration.reconfigure = a2dp_subevent_signaling_media_codec_sbc_configuration_get_reconfigure(packet);
|
||||
a2dp_conn->sbc_configuration.num_channels = a2dp_subevent_signaling_media_codec_sbc_configuration_get_num_channels(packet);
|
||||
a2dp_conn->sbc_configuration.sampling_frequency = a2dp_subevent_signaling_media_codec_sbc_configuration_get_sampling_frequency(packet);
|
||||
a2dp_conn->sbc_configuration.block_length = a2dp_subevent_signaling_media_codec_sbc_configuration_get_block_length(packet);
|
||||
a2dp_conn->sbc_configuration.subbands = a2dp_subevent_signaling_media_codec_sbc_configuration_get_subbands(packet);
|
||||
a2dp_conn->sbc_configuration.min_bitpool_value = a2dp_subevent_signaling_media_codec_sbc_configuration_get_min_bitpool_value(packet);
|
||||
a2dp_conn->sbc_configuration.max_bitpool_value = a2dp_subevent_signaling_media_codec_sbc_configuration_get_max_bitpool_value(packet);
|
||||
|
||||
allocation_method = a2dp_subevent_signaling_media_codec_sbc_configuration_get_allocation_method(packet);
|
||||
|
||||
// Adapt Bluetooth spec definition to SBC Encoder expected input
|
||||
a2dp_conn->sbc_configuration.allocation_method = (btstack_sbc_allocation_method_t)(allocation_method - 1);
|
||||
|
||||
switch (a2dp_subevent_signaling_media_codec_sbc_configuration_get_channel_mode(packet)) {
|
||||
case AVDTP_CHANNEL_MODE_JOINT_STEREO:
|
||||
a2dp_conn->sbc_configuration.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
|
||||
break;
|
||||
case AVDTP_CHANNEL_MODE_STEREO:
|
||||
a2dp_conn->sbc_configuration.channel_mode = SBC_CHANNEL_MODE_STEREO;
|
||||
break;
|
||||
case AVDTP_CHANNEL_MODE_DUAL_CHANNEL:
|
||||
a2dp_conn->sbc_configuration.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
|
||||
break;
|
||||
case AVDTP_CHANNEL_MODE_MONO:
|
||||
a2dp_conn->sbc_configuration.channel_mode = SBC_CHANNEL_MODE_MONO;
|
||||
break;
|
||||
default:
|
||||
btstack_assert(false);
|
||||
break;
|
||||
}
|
||||
a2dp_conn->sbc_configuration.dump();
|
||||
break;
|
||||
}
|
||||
|
||||
case A2DP_SUBEVENT_STREAM_ESTABLISHED:
|
||||
status = a2dp_subevent_stream_established_get_status(packet);
|
||||
if (status != ERROR_CODE_SUCCESS) {
|
||||
DEBUGV("A2DP Sink : Streaming connection failed, status 0x%02x\n", status);
|
||||
break;
|
||||
}
|
||||
|
||||
a2dp_subevent_stream_established_get_bd_addr(packet, a2dp_conn->addr);
|
||||
a2dp_conn->a2dp_cid = a2dp_subevent_stream_established_get_a2dp_cid(packet);
|
||||
a2dp_conn->a2dp_local_seid = a2dp_subevent_stream_established_get_local_seid(packet);
|
||||
a2dp_conn->stream_state = STREAM_STATE_OPEN;
|
||||
|
||||
DEBUGV("A2DP Sink : Streaming connection is established, address %s, cid 0x%02x, local seid %d\n",
|
||||
bd_addr_to_str(a2dp_conn->addr), a2dp_conn->a2dp_cid, a2dp_conn->a2dp_local_seid);
|
||||
memcpy(_sourceAddress, a2dp_conn->addr, sizeof(_sourceAddress));
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_STREAM_STARTED:
|
||||
DEBUGV("A2DP Sink : Stream started\n");
|
||||
a2dp_conn->stream_state = STREAM_STATE_PLAYING;
|
||||
if (a2dp_conn->sbc_configuration.reconfigure) {
|
||||
media_processing_close();
|
||||
}
|
||||
// prepare media processing
|
||||
media_processing_init(&a2dp_conn->sbc_configuration);
|
||||
// audio stream is started when buffer reaches minimal level
|
||||
_connected = true;
|
||||
if (_connectCB) {
|
||||
_connectCB(_connectData, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_STREAM_SUSPENDED:
|
||||
DEBUGV("A2DP Sink : Stream paused\n");
|
||||
a2dp_conn->stream_state = STREAM_STATE_PAUSED;
|
||||
media_processing_pause();
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_STREAM_RELEASED:
|
||||
DEBUGV("A2DP Sink : Stream released\n");
|
||||
a2dp_conn->stream_state = STREAM_STATE_CLOSED;
|
||||
media_processing_close();
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_SIGNALING_CONNECTION_RELEASED:
|
||||
DEBUGV("A2DP Sink : Signaling connection released\n");
|
||||
a2dp_conn->a2dp_cid = 0;
|
||||
media_processing_close();
|
||||
_connected = false;
|
||||
if (_connectCB) {
|
||||
_connectCB(_connectData, false);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/*
|
||||
A2DP Sink (Bluetooth audio receiver)
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "BluetoothHCI.h"
|
||||
#include "BluetoothAudioConsumer.h"
|
||||
#include "BluetoothMediaConfigurationSBC.h"
|
||||
|
||||
#include "btstack.h"
|
||||
#include "btstack_resample.h"
|
||||
#include "btstack_ring_buffer.h"
|
||||
|
||||
class A2DPSink : public Stream {
|
||||
public:
|
||||
A2DPSink() {
|
||||
}
|
||||
virtual int available() override {
|
||||
return 0; // Unreadable, this is output only
|
||||
}
|
||||
|
||||
virtual int read() override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual int peek() override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void flush() override {
|
||||
}
|
||||
virtual size_t write(const uint8_t *buffer, size_t size) override {
|
||||
(void) buffer;
|
||||
(void) size;
|
||||
return 0;
|
||||
}
|
||||
virtual int availableForWrite() override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual size_t write(uint8_t s) override {
|
||||
(void) s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool setName(const char *name) {
|
||||
if (_running) {
|
||||
return false;
|
||||
}
|
||||
free(_name);
|
||||
_name = strdup(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void onTransmit(void (*cb)(void *), void *cbData = nullptr) {
|
||||
_transmitCB = cb;
|
||||
_transmitData = cbData;
|
||||
}
|
||||
|
||||
void onAVRCP(void (*cb)(void *, avrcp_operation_id_t, int), void *cbData = nullptr) {
|
||||
_avrcpCB = cb;
|
||||
_avrcpData = cbData;
|
||||
}
|
||||
|
||||
void onBattery(void (*cb)(void *, avrcp_battery_status_t), void *cbData = nullptr) {
|
||||
_batteryCB = cb;
|
||||
_batteryData = cbData;
|
||||
}
|
||||
|
||||
void onVolume(void (*cb)(void *, int), void *cbData = nullptr) {
|
||||
_volumeCB = cb;
|
||||
_volumeData = cbData;
|
||||
}
|
||||
|
||||
void onConnect(void (*cb)(void *, bool), void *cbData = nullptr) {
|
||||
_connectCB = cb;
|
||||
_connectData = cbData;
|
||||
}
|
||||
|
||||
typedef enum { STOPPED, PLAYING, PAUSED } PlaybackStatus;
|
||||
void onPlaybackStatus(void (*cb)(void *, PlaybackStatus), void *cbData = nullptr) {
|
||||
_playbackStatusCB = cb;
|
||||
_playbackStatusData = cbData;
|
||||
}
|
||||
|
||||
const uint8_t *getSourceAddress() {
|
||||
if (!_connected) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return _sourceAddress;
|
||||
}
|
||||
}
|
||||
|
||||
void setConsumer(BluetoothAudioConsumer_ *c) {
|
||||
_consumer = c;
|
||||
}
|
||||
|
||||
bool begin();
|
||||
bool disconnect();
|
||||
void clearPairing();
|
||||
|
||||
void playback_handler(int16_t * buffer, uint16_t num_audio_frames);
|
||||
|
||||
void play() {
|
||||
if (_connected) {
|
||||
avrcp_controller_play(a2dp_sink_avrcp_connection.avrcp_cid);
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (_connected) {
|
||||
avrcp_controller_stop(a2dp_sink_avrcp_connection.avrcp_cid);
|
||||
}
|
||||
}
|
||||
|
||||
void pause() {
|
||||
if (_connected) {
|
||||
avrcp_controller_pause(a2dp_sink_avrcp_connection.avrcp_cid);
|
||||
}
|
||||
}
|
||||
|
||||
void fastForward() {
|
||||
if (_connected) {
|
||||
avrcp_controller_fast_forward(a2dp_sink_avrcp_connection.avrcp_cid);
|
||||
}
|
||||
}
|
||||
|
||||
void rewind() {
|
||||
if (_connected) {
|
||||
avrcp_controller_rewind(a2dp_sink_avrcp_connection.avrcp_cid);
|
||||
}
|
||||
}
|
||||
|
||||
void forward() {
|
||||
if (_connected) {
|
||||
avrcp_controller_forward(a2dp_sink_avrcp_connection.avrcp_cid);
|
||||
}
|
||||
}
|
||||
|
||||
void backward() {
|
||||
if (_connected) {
|
||||
avrcp_controller_backward(a2dp_sink_avrcp_connection.avrcp_cid);
|
||||
}
|
||||
}
|
||||
|
||||
void volumeUp() {
|
||||
if (_connected) {
|
||||
avrcp_controller_volume_up(a2dp_sink_avrcp_connection.avrcp_cid);
|
||||
}
|
||||
}
|
||||
|
||||
void volumeDown() {
|
||||
if (_connected) {
|
||||
avrcp_controller_volume_down(a2dp_sink_avrcp_connection.avrcp_cid);
|
||||
}
|
||||
}
|
||||
|
||||
void mute() {
|
||||
if (_connected) {
|
||||
avrcp_controller_mute(a2dp_sink_avrcp_connection.avrcp_cid);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void handle_pcm_data(int16_t * data, int num_audio_frames, int num_channels, int sample_rate, void * context);
|
||||
|
||||
int media_processing_init(BluetoothMediaCodecConfigurationSBC * configuration);
|
||||
void media_processing_start();
|
||||
void media_processing_pause();
|
||||
void media_processing_close();
|
||||
|
||||
void handle_l2cap_media_data_packet(uint8_t seid, uint8_t *packet, uint16_t size);
|
||||
int read_sbc_header(uint8_t * packet, int size, int * offset, avdtp_sbc_codec_header_t * sbc_header);
|
||||
int read_media_data_header(uint8_t *packet, int size, int *offset, avdtp_media_packet_header_t *media_header);
|
||||
void avrcp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
void avrcp_controller_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
void avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
void a2dp_sink_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
|
||||
BluetoothHCI_ _hci;
|
||||
BluetoothAudioConsumer_ *_consumer = nullptr;
|
||||
bool _running = false;
|
||||
bool _connected = false;
|
||||
|
||||
// Callbacks
|
||||
void (*_transmitCB)(void *) = nullptr;
|
||||
void *_transmitData;
|
||||
void (*_avrcpCB)(void *, avrcp_operation_id_t, int) = nullptr;
|
||||
void *_avrcpData;
|
||||
void (*_batteryCB)(void *, avrcp_battery_status_t) = nullptr;
|
||||
void *_batteryData;
|
||||
void (*_volumeCB)(void *, int) = nullptr;
|
||||
void *_volumeData;
|
||||
void (*_connectCB)(void *, bool) = nullptr;
|
||||
void *_connectData;
|
||||
void (*_playbackStatusCB)(void *, PlaybackStatus) = nullptr;
|
||||
void *_playbackStatusData;
|
||||
char *_name = nullptr;
|
||||
uint8_t _sourceAddress[6];
|
||||
|
||||
enum { NUM_CHANNELS = 2, BYTES_PER_FRAME = (2 * NUM_CHANNELS), MAX_SBC_FRAME_SIZE = 120 };
|
||||
|
||||
uint8_t sdp_avdtp_sink_service_buffer[150];
|
||||
uint8_t sdp_avrcp_target_service_buffer[150];
|
||||
uint8_t sdp_avrcp_controller_service_buffer[200];
|
||||
uint8_t device_id_sdp_service_buffer[100];
|
||||
|
||||
// we support all configurations with bitpool 2-53
|
||||
const uint8_t media_sbc_codec_capabilities[4] = {
|
||||
0xFF,//(AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO,
|
||||
0xFF,//(AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS,
|
||||
2, 53
|
||||
};
|
||||
|
||||
// SBC Decoder for WAV file or live playback
|
||||
btstack_sbc_decoder_state_t state;
|
||||
btstack_sbc_mode_t mode = SBC_MODE_STANDARD;
|
||||
|
||||
// ring buffer for SBC Frames
|
||||
// below 30: add samples, 30-40: fine, above 40: drop samples
|
||||
|
||||
enum { OPTIMAL_FRAMES_MIN = 60, OPTIMAL_FRAMES_MAX = 80, ADDITIONAL_FRAMES = 30 };
|
||||
uint8_t sbc_frame_storage[(OPTIMAL_FRAMES_MAX + ADDITIONAL_FRAMES) * MAX_SBC_FRAME_SIZE];
|
||||
btstack_ring_buffer_t sbc_frame_ring_buffer;
|
||||
unsigned int sbc_frame_size;
|
||||
|
||||
// overflow buffer for not fully used sbc frames, with additional frames for resampling
|
||||
uint8_t decoded_audio_storage[(128 + 16) * BYTES_PER_FRAME];
|
||||
btstack_ring_buffer_t decoded_audio_ring_buffer;
|
||||
|
||||
int media_initialized = 0;
|
||||
int audio_stream_started;
|
||||
|
||||
btstack_resample_t resample_instance;
|
||||
|
||||
// temp storage of lower-layer request for audio samples
|
||||
int16_t * request_buffer;
|
||||
int request_frames;
|
||||
|
||||
// sink state
|
||||
int volume_percentage = 0;
|
||||
avrcp_battery_status_t battery_status = AVRCP_BATTERY_STATUS_WARNING;
|
||||
|
||||
typedef enum {
|
||||
STREAM_STATE_CLOSED,
|
||||
STREAM_STATE_OPEN,
|
||||
STREAM_STATE_PLAYING,
|
||||
STREAM_STATE_PAUSED,
|
||||
} stream_state_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t a2dp_local_seid;
|
||||
uint8_t media_sbc_codec_configuration[4];
|
||||
} a2dp_sink_stream_endpoint_t;
|
||||
a2dp_sink_stream_endpoint_t a2dp_sink_stream_endpoint;
|
||||
|
||||
typedef struct {
|
||||
bd_addr_t addr;
|
||||
uint16_t a2dp_cid;
|
||||
uint8_t a2dp_local_seid;
|
||||
stream_state_t stream_state;
|
||||
BluetoothMediaCodecConfigurationSBC sbc_configuration;
|
||||
} a2dp_sink_a2dp_connection_t;
|
||||
a2dp_sink_a2dp_connection_t a2dp_sink_a2dp_connection;
|
||||
|
||||
typedef struct {
|
||||
bd_addr_t addr;
|
||||
uint16_t avrcp_cid;
|
||||
bool playing;
|
||||
uint16_t notifications_supported_by_target;
|
||||
} a2dp_sink_avrcp_connection_t;
|
||||
a2dp_sink_avrcp_connection_t a2dp_sink_avrcp_connection;
|
||||
|
||||
int16_t output_buffer[(128 + 16) * NUM_CHANNELS]; // 16 * 8 * 2
|
||||
};
|
||||
@@ -0,0 +1,722 @@
|
||||
/*
|
||||
A1DP Source (Bluetooth audio sender)
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
// Based heavily off of the a2dp_source example from BlueKitchen BTStack
|
||||
|
||||
/*
|
||||
Copyright (C) 2016 BlueKitchen GmbH
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holders nor the names of
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
4. Any redistribution, use, or modification is done solely for
|
||||
personal benefit and not for any commercial purpose or for
|
||||
monetary gain.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
Please inquire about commercial licensing options at
|
||||
contact@bluekitchen-gmbh.com
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "A2DPSource.h"
|
||||
|
||||
#define CCALLBACKNAME _A2DPSOURCECB
|
||||
#include <ctocppcallback.h>
|
||||
|
||||
#define PACKETHANDLERCB(class, cbFcn) \
|
||||
(_A2DPSOURCECB<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), \
|
||||
static_cast<btstack_packet_handler_t>(_A2DPSOURCECB<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
|
||||
|
||||
#define TIMEOUTHANDLERCB(class, cbFcn) \
|
||||
(_A2DPSOURCECB<void(btstack_timer_source_t *), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1), \
|
||||
static_cast<void(*)(btstack_timer_source_t*)>(_A2DPSOURCECB<void(btstack_timer_source_t*), __COUNTER__ - 1>::callback))
|
||||
|
||||
|
||||
bool A2DPSource::begin() {
|
||||
if (_running) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_pcmBuffer = (int16_t *)malloc(_pcmBufferSize * sizeof(int16_t));
|
||||
if (!_pcmBuffer) {
|
||||
DEBUGV("A2DPSource: OOM for pcm buffer\n");
|
||||
return false;
|
||||
}
|
||||
_pcmWriter = 0;
|
||||
_pcmReader = 0;
|
||||
|
||||
// Request role change on reconnecting headset to always use them in slave mode
|
||||
hci_set_master_slave_policy(0);
|
||||
// enabled EIR
|
||||
hci_set_inquiry_mode(INQUIRY_MODE_RSSI_AND_EIR);
|
||||
|
||||
l2cap_init();
|
||||
|
||||
#ifdef ENABLE_BLE
|
||||
// Initialize LE Security Manager. Needed for cross-transport key derivation
|
||||
sm_init();
|
||||
#endif
|
||||
|
||||
// Initialize A2DP Source
|
||||
a2dp_source_init();
|
||||
a2dp_source_register_packet_handler(PACKETHANDLERCB(A2DPSource, a2dp_source_packet_handler));
|
||||
|
||||
// Create stream endpoint
|
||||
avdtp_stream_endpoint_t * local_stream_endpoint = a2dp_source_create_stream_endpoint(AVDTP_AUDIO, AVDTP_CODEC_SBC, media_sbc_codec_capabilities, sizeof(media_sbc_codec_capabilities), media_sbc_codec_configuration, sizeof(media_sbc_codec_configuration));
|
||||
if (!local_stream_endpoint) {
|
||||
DEBUGV("A2DP Source: not enough memory to create local stream endpoint\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store stream endpoint's SEP ID, as it is used by A2DP API to identify the stream endpoint
|
||||
media_tracker.local_seid = avdtp_local_seid(local_stream_endpoint);
|
||||
avdtp_source_register_delay_reporting_category(media_tracker.local_seid);
|
||||
|
||||
// Initialize AVRCP Service
|
||||
avrcp_init();
|
||||
avrcp_register_packet_handler(PACKETHANDLERCB(A2DPSource, avrcp_packet_handler));
|
||||
// Initialize AVRCP Target
|
||||
avrcp_target_init();
|
||||
avrcp_target_register_packet_handler(PACKETHANDLERCB(A2DPSource, avrcp_target_packet_handler));
|
||||
|
||||
// Initialize AVRCP Controller
|
||||
avrcp_controller_init();
|
||||
avrcp_controller_register_packet_handler(PACKETHANDLERCB(A2DPSource, avrcp_controller_packet_handler));
|
||||
|
||||
// Initialize SDP,
|
||||
sdp_init();
|
||||
|
||||
// Create A2DP Source service record and register it with SDP
|
||||
memset(sdp_a2dp_source_service_buffer, 0, sizeof(sdp_a2dp_source_service_buffer));
|
||||
a2dp_source_create_sdp_record(sdp_a2dp_source_service_buffer, 0x10001, AVDTP_SOURCE_FEATURE_MASK_PLAYER, NULL, NULL);
|
||||
sdp_register_service(sdp_a2dp_source_service_buffer);
|
||||
|
||||
// Create AVRCP Target service record and register it with SDP. We receive Category 1 commands from the headphone, e.g. play/pause
|
||||
memset(sdp_avrcp_target_service_buffer, 0, sizeof(sdp_avrcp_target_service_buffer));
|
||||
uint16_t supported_features = AVRCP_FEATURE_MASK_CATEGORY_PLAYER_OR_RECORDER;
|
||||
#ifdef AVRCP_BROWSING_ENABLED
|
||||
supported_features |= AVRCP_FEATURE_MASK_BROWSING;
|
||||
#endif
|
||||
avrcp_target_create_sdp_record(sdp_avrcp_target_service_buffer, 0x10002, supported_features, NULL, NULL);
|
||||
sdp_register_service(sdp_avrcp_target_service_buffer);
|
||||
|
||||
// Create AVRCP Controller service record and register it with SDP. We send Category 2 commands to the headphone, e.g. volume up/down
|
||||
memset(sdp_avrcp_controller_service_buffer, 0, sizeof(sdp_avrcp_controller_service_buffer));
|
||||
uint16_t controller_supported_features = AVRCP_FEATURE_MASK_CATEGORY_MONITOR_OR_AMPLIFIER;
|
||||
avrcp_controller_create_sdp_record(sdp_avrcp_controller_service_buffer, 0x10003, controller_supported_features, NULL, NULL);
|
||||
sdp_register_service(sdp_avrcp_controller_service_buffer);
|
||||
|
||||
// Register Device ID (PnP) service SDP record
|
||||
memset(device_id_sdp_service_buffer, 0, sizeof(device_id_sdp_service_buffer));
|
||||
device_id_create_sdp_record(device_id_sdp_service_buffer, 0x10004, DEVICE_ID_VENDOR_ID_SOURCE_BLUETOOTH, BLUETOOTH_COMPANY_ID_BLUEKITCHEN_GMBH, 1, 1);
|
||||
sdp_register_service(device_id_sdp_service_buffer);
|
||||
|
||||
// Set local name with a template Bluetooth address, that will be automatically
|
||||
// replaced with a actual address once it is available, i.e. when BTstack boots
|
||||
// up and starts talking to a Bluetooth module.
|
||||
if (!_name) {
|
||||
setName("PicoW A2DP 00:00:00:00:00:00");
|
||||
}
|
||||
gap_set_local_name(_name);
|
||||
gap_discoverable_control(1);
|
||||
gap_set_class_of_device(0x200408);
|
||||
|
||||
// Register for HCI events.
|
||||
_hci.install();
|
||||
_running = true;
|
||||
_hci.begin();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool A2DPSource::connect(const uint8_t *addr) {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
while (!_hci.running()) {
|
||||
delay(10);
|
||||
}
|
||||
uint8_t a[6];
|
||||
if (addr) {
|
||||
memcpy(a, addr, sizeof(a));
|
||||
auto ret = !a2dp_source_establish_stream(a, &media_tracker.a2dp_cid);
|
||||
return ret;
|
||||
} else {
|
||||
clearPairing();
|
||||
auto l = scan();
|
||||
for (auto e : l) {
|
||||
DEBUGV("Scan connecting %s at %s ... ", e.name(), e.addressString());
|
||||
memcpy(a, e.address(), sizeof(a));
|
||||
if (!a2dp_source_establish_stream(a, &media_tracker.a2dp_cid)) {
|
||||
DEBUGV("Connection established\n");
|
||||
return true;
|
||||
}
|
||||
DEBUGV("Failed\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool A2DPSource::disconnect() {
|
||||
__lockBluetooth();
|
||||
a2dp_source_disconnect(media_tracker.a2dp_cid);
|
||||
__unlockBluetooth();
|
||||
if (!_running || !_connected) {
|
||||
return false;
|
||||
}
|
||||
_connected = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void A2DPSource::clearPairing() {
|
||||
disconnect();
|
||||
__lockBluetooth();
|
||||
gap_delete_all_link_keys();
|
||||
__unlockBluetooth();
|
||||
}
|
||||
|
||||
// from Print (see notes on write() methods below)
|
||||
size_t A2DPSource::write(const uint8_t *buffer, size_t size) {
|
||||
size_t count = 0;
|
||||
size /= 2;
|
||||
__lockBluetooth();
|
||||
// First copy from writer to either end of
|
||||
uint32_t start = _pcmWriter;
|
||||
uint32_t end = _pcmReader > _pcmWriter ? _pcmReader : _pcmBufferSize;
|
||||
if (end - start > size) {
|
||||
end = start + size;
|
||||
}
|
||||
memcpy(_pcmBuffer + start, buffer, (end - start) * sizeof(int16_t));
|
||||
count += (end - start) * sizeof(int16_t);
|
||||
size -= end - start;
|
||||
_pcmWriter += end - start;
|
||||
_pcmWriter %= _pcmBufferSize;
|
||||
// Possibly wraparound to 0 if still left
|
||||
if (size) {
|
||||
start = 0;
|
||||
end = _pcmReader;
|
||||
if (end - start > size) {
|
||||
end = size;
|
||||
}
|
||||
memcpy(_pcmBuffer + start, buffer, (end - start) * sizeof(int16_t));
|
||||
count += (end - start) * sizeof(int16_t);
|
||||
size -= end - start;
|
||||
_pcmWriter += end - start;
|
||||
_pcmWriter %= _pcmBufferSize;
|
||||
}
|
||||
__unlockBluetooth();
|
||||
return count;
|
||||
}
|
||||
|
||||
int A2DPSource::availableForWrite() {
|
||||
int avail = 0;
|
||||
__lockBluetooth();
|
||||
if (_pcmWriter == _pcmReader) {
|
||||
avail = _pcmBufferSize - 1;
|
||||
} else if (_pcmReader > _pcmWriter) {
|
||||
avail = _pcmReader - _pcmWriter - 1;
|
||||
} else {
|
||||
avail = _pcmBufferSize - _pcmWriter + _pcmReader - 1;
|
||||
}
|
||||
__unlockBluetooth();
|
||||
return avail;
|
||||
}
|
||||
|
||||
void A2DPSource::a2dp_timer_start(a2dp_media_sending_context_t * context) {
|
||||
context->max_media_payload_size = btstack_min(a2dp_max_media_payload_size(context->a2dp_cid, context->local_seid), SBC_STORAGE_SIZE);
|
||||
context->sbc_storage_count = 0;
|
||||
context->sbc_ready_to_send = 0;
|
||||
context->streaming = 1;
|
||||
btstack_run_loop_remove_timer(&context->audio_timer);
|
||||
btstack_run_loop_set_timer_handler(&context->audio_timer, TIMEOUTHANDLERCB(A2DPSource, a2dp_audio_timeout_handler));
|
||||
btstack_run_loop_set_timer_context(&context->audio_timer, context);
|
||||
btstack_run_loop_set_timer(&context->audio_timer, AUDIO_TIMEOUT_MS);
|
||||
btstack_run_loop_add_timer(&context->audio_timer);
|
||||
}
|
||||
|
||||
void A2DPSource::a2dp_timer_stop(a2dp_media_sending_context_t * context) {
|
||||
context->time_audio_data_sent = 0;
|
||||
context->acc_num_missed_samples = 0;
|
||||
context->samples_ready = 0;
|
||||
context->streaming = 1;
|
||||
context->sbc_storage_count = 0;
|
||||
context->sbc_ready_to_send = 0;
|
||||
btstack_run_loop_remove_timer(&context->audio_timer);
|
||||
}
|
||||
|
||||
int A2DPSource::a2dp_fill_sbc_audio_buffer(a2dp_media_sending_context_t * context) {
|
||||
// perform sbc encoding
|
||||
int total_num_bytes_read = 0;
|
||||
unsigned int num_audio_samples_per_sbc_buffer = btstack_sbc_encoder_num_audio_frames();
|
||||
while (context->samples_ready >= num_audio_samples_per_sbc_buffer
|
||||
&& (context->max_media_payload_size - context->sbc_storage_count) >= btstack_sbc_encoder_sbc_buffer_length()) {
|
||||
|
||||
if ((_pcmWriter / 256) != (_pcmReader / 256)) {
|
||||
btstack_sbc_encoder_process_data(_pcmBuffer + _pcmReader);
|
||||
asm volatile("" ::: "memory"); // Ensure the data is processed before advancing
|
||||
auto next_reader = (_pcmReader + 256) % _pcmBufferSize;
|
||||
asm volatile("" ::: "memory"); // Ensure the reader value is only written once, correctly
|
||||
_pcmReader = next_reader;
|
||||
} else {
|
||||
_underflow = true;
|
||||
// Just keep sending old data
|
||||
btstack_sbc_encoder_process_data(_pcmBuffer + _pcmReader);
|
||||
}
|
||||
|
||||
uint16_t sbc_frame_size = btstack_sbc_encoder_sbc_buffer_length();
|
||||
uint8_t *sbc_frame = btstack_sbc_encoder_sbc_buffer();
|
||||
|
||||
total_num_bytes_read += num_audio_samples_per_sbc_buffer;
|
||||
// first byte in sbc storage contains sbc media header
|
||||
memcpy(&context->sbc_storage[1 + context->sbc_storage_count], sbc_frame, sbc_frame_size);
|
||||
context->sbc_storage_count += sbc_frame_size;
|
||||
context->samples_ready -= num_audio_samples_per_sbc_buffer;
|
||||
}
|
||||
return total_num_bytes_read;
|
||||
}
|
||||
|
||||
void A2DPSource::a2dp_audio_timeout_handler(btstack_timer_source_t * timer) {
|
||||
a2dp_media_sending_context_t * context = (a2dp_media_sending_context_t *) btstack_run_loop_get_timer_context(timer);
|
||||
btstack_run_loop_set_timer(&context->audio_timer, AUDIO_TIMEOUT_MS);
|
||||
btstack_run_loop_add_timer(&context->audio_timer);
|
||||
uint32_t now = btstack_run_loop_get_time_ms();
|
||||
|
||||
uint32_t update_period_ms = AUDIO_TIMEOUT_MS;
|
||||
if (context->time_audio_data_sent > 0) {
|
||||
update_period_ms = now - context->time_audio_data_sent;
|
||||
}
|
||||
|
||||
uint32_t num_samples = (update_period_ms * _frequency) / 1000;
|
||||
context->acc_num_missed_samples += (update_period_ms * _frequency) % 1000;
|
||||
|
||||
while (context->acc_num_missed_samples >= 1000) {
|
||||
num_samples++;
|
||||
context->acc_num_missed_samples -= 1000;
|
||||
}
|
||||
context->time_audio_data_sent = now;
|
||||
context->samples_ready += num_samples;
|
||||
|
||||
if (context->sbc_ready_to_send) {
|
||||
return;
|
||||
}
|
||||
|
||||
a2dp_fill_sbc_audio_buffer(context);
|
||||
|
||||
if ((context->sbc_storage_count + btstack_sbc_encoder_sbc_buffer_length()) > context->max_media_payload_size) {
|
||||
// schedule sending
|
||||
context->sbc_ready_to_send = 1;
|
||||
a2dp_source_stream_endpoint_request_can_send_now(context->a2dp_cid, context->local_seid);
|
||||
}
|
||||
}
|
||||
|
||||
void A2DPSource::a2dp_send_media_packet() {
|
||||
int num_bytes_in_frame = btstack_sbc_encoder_sbc_buffer_length();
|
||||
int bytes_in_storage = media_tracker.sbc_storage_count;
|
||||
uint8_t num_sbc_frames = bytes_in_storage / num_bytes_in_frame;
|
||||
// Prepend SBC Header
|
||||
media_tracker.sbc_storage[0] = num_sbc_frames; // (fragmentation << 7) | (starting_packet << 6) | (last_packet << 5) | num_frames;
|
||||
a2dp_source_stream_send_media_payload_rtp(media_tracker.a2dp_cid, media_tracker.local_seid, 0,
|
||||
media_tracker.rtp_timestamp,
|
||||
media_tracker.sbc_storage, bytes_in_storage + 1);
|
||||
|
||||
// update rtp_timestamp
|
||||
unsigned int num_audio_samples_per_sbc_buffer = btstack_sbc_encoder_num_audio_frames();
|
||||
media_tracker.rtp_timestamp += num_sbc_frames * num_audio_samples_per_sbc_buffer;
|
||||
|
||||
media_tracker.sbc_storage_count = 0;
|
||||
media_tracker.sbc_ready_to_send = 0;
|
||||
if (_transmitCB) {
|
||||
_transmitCB(_transmitData);
|
||||
}
|
||||
}
|
||||
|
||||
void A2DPSource::a2dp_source_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
UNUSED(channel);
|
||||
UNUSED(size);
|
||||
uint8_t status;
|
||||
uint8_t local_seid;
|
||||
bd_addr_t address;
|
||||
uint16_t cid;
|
||||
|
||||
avdtp_channel_mode_t channel_mode;
|
||||
uint8_t allocation_method;
|
||||
|
||||
if (packet_type != HCI_EVENT_PACKET) {
|
||||
return;
|
||||
}
|
||||
if (hci_event_packet_get_type(packet) != HCI_EVENT_A2DP_META) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (hci_event_a2dp_meta_get_subevent_code(packet)) {
|
||||
case A2DP_SUBEVENT_SIGNALING_CONNECTION_ESTABLISHED:
|
||||
a2dp_subevent_signaling_connection_established_get_bd_addr(packet, address);
|
||||
cid = a2dp_subevent_signaling_connection_established_get_a2dp_cid(packet);
|
||||
status = a2dp_subevent_signaling_connection_established_get_status(packet);
|
||||
|
||||
if (status != ERROR_CODE_SUCCESS) {
|
||||
DEBUGV("A2DP Source: Connection failed, status 0x%02x, cid 0x%02x, a2dp_cid 0x%02x \n", status, cid, media_tracker.a2dp_cid);
|
||||
media_tracker.a2dp_cid = 0;
|
||||
break;
|
||||
}
|
||||
media_tracker.a2dp_cid = cid;
|
||||
media_tracker.volume = 32;
|
||||
memcpy(_sinkAddress, address, sizeof(_sinkAddress));
|
||||
DEBUGV("A2DP Source: Connected to address %s, a2dp cid 0x%02x, local seid 0x%02x.\n", bd_addr_to_str(address), media_tracker.a2dp_cid, media_tracker.local_seid);
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_SIGNALING_MEDIA_CODEC_SBC_CONFIGURATION: {
|
||||
cid = avdtp_subevent_signaling_media_codec_sbc_configuration_get_avdtp_cid(packet);
|
||||
if (cid != media_tracker.a2dp_cid) {
|
||||
return;
|
||||
}
|
||||
|
||||
media_tracker.remote_seid = a2dp_subevent_signaling_media_codec_sbc_configuration_get_remote_seid(packet);
|
||||
|
||||
sbc_configuration.reconfigure = a2dp_subevent_signaling_media_codec_sbc_configuration_get_reconfigure(packet);
|
||||
sbc_configuration.num_channels = a2dp_subevent_signaling_media_codec_sbc_configuration_get_num_channels(packet);
|
||||
sbc_configuration.sampling_frequency = a2dp_subevent_signaling_media_codec_sbc_configuration_get_sampling_frequency(packet);
|
||||
sbc_configuration.block_length = a2dp_subevent_signaling_media_codec_sbc_configuration_get_block_length(packet);
|
||||
sbc_configuration.subbands = a2dp_subevent_signaling_media_codec_sbc_configuration_get_subbands(packet);
|
||||
sbc_configuration.min_bitpool_value = a2dp_subevent_signaling_media_codec_sbc_configuration_get_min_bitpool_value(packet);
|
||||
sbc_configuration.max_bitpool_value = a2dp_subevent_signaling_media_codec_sbc_configuration_get_max_bitpool_value(packet);
|
||||
|
||||
channel_mode = (avdtp_channel_mode_t) a2dp_subevent_signaling_media_codec_sbc_configuration_get_channel_mode(packet);
|
||||
allocation_method = a2dp_subevent_signaling_media_codec_sbc_configuration_get_allocation_method(packet);
|
||||
|
||||
DEBUGV("A2DP Source: Received SBC codec configuration, sampling frequency %u, a2dp_cid 0x%02x, local seid 0x%02x, remote seid 0x%02x.\n",
|
||||
sbc_configuration.sampling_frequency, cid,
|
||||
a2dp_subevent_signaling_media_codec_sbc_configuration_get_local_seid(packet),
|
||||
a2dp_subevent_signaling_media_codec_sbc_configuration_get_remote_seid(packet));
|
||||
|
||||
// Adapt Bluetooth spec definition to SBC Encoder expected input
|
||||
sbc_configuration.allocation_method = (btstack_sbc_allocation_method_t)(allocation_method - 1);
|
||||
switch (channel_mode) {
|
||||
case AVDTP_CHANNEL_MODE_JOINT_STEREO:
|
||||
sbc_configuration.channel_mode = SBC_CHANNEL_MODE_JOINT_STEREO;
|
||||
break;
|
||||
case AVDTP_CHANNEL_MODE_STEREO:
|
||||
sbc_configuration.channel_mode = SBC_CHANNEL_MODE_STEREO;
|
||||
break;
|
||||
case AVDTP_CHANNEL_MODE_DUAL_CHANNEL:
|
||||
sbc_configuration.channel_mode = SBC_CHANNEL_MODE_DUAL_CHANNEL;
|
||||
break;
|
||||
case AVDTP_CHANNEL_MODE_MONO:
|
||||
sbc_configuration.channel_mode = SBC_CHANNEL_MODE_MONO;
|
||||
break;
|
||||
default:
|
||||
btstack_assert(false);
|
||||
break;
|
||||
}
|
||||
sbc_configuration.dump();
|
||||
|
||||
btstack_sbc_encoder_init(&sbc_encoder_state, SBC_MODE_STANDARD,
|
||||
sbc_configuration.block_length, sbc_configuration.subbands,
|
||||
sbc_configuration.allocation_method, sbc_configuration.sampling_frequency,
|
||||
sbc_configuration.max_bitpool_value,
|
||||
sbc_configuration.channel_mode);
|
||||
break;
|
||||
}
|
||||
|
||||
case A2DP_SUBEVENT_SIGNALING_DELAY_REPORTING_CAPABILITY:
|
||||
DEBUGV("A2DP Source: remote supports delay report, remote seid %d\n",
|
||||
avdtp_subevent_signaling_delay_reporting_capability_get_remote_seid(packet));
|
||||
break;
|
||||
case A2DP_SUBEVENT_SIGNALING_CAPABILITIES_DONE:
|
||||
DEBUGV("A2DP Source: All capabilities reported, remote seid %d\n",
|
||||
avdtp_subevent_signaling_capabilities_done_get_remote_seid(packet));
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_SIGNALING_DELAY_REPORT:
|
||||
DEBUGV("A2DP Source: Received delay report of %d.%0d ms, local seid %d\n",
|
||||
avdtp_subevent_signaling_delay_report_get_delay_100us(packet) / 10, avdtp_subevent_signaling_delay_report_get_delay_100us(packet) % 10,
|
||||
avdtp_subevent_signaling_delay_report_get_local_seid(packet));
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_STREAM_ESTABLISHED:
|
||||
a2dp_subevent_stream_established_get_bd_addr(packet, address);
|
||||
status = a2dp_subevent_stream_established_get_status(packet);
|
||||
if (status != ERROR_CODE_SUCCESS) {
|
||||
DEBUGV("A2DP Source: Stream failed, status 0x%02x.\n", status);
|
||||
break;
|
||||
}
|
||||
|
||||
local_seid = a2dp_subevent_stream_established_get_local_seid(packet);
|
||||
cid = a2dp_subevent_stream_established_get_a2dp_cid(packet);
|
||||
(void) local_seid;
|
||||
DEBUGV("A2DP Source: Stream established a2dp_cid 0x%02x, local_seid 0x%02x, remote_seid 0x%02x\n", cid, local_seid, a2dp_subevent_stream_established_get_remote_seid(packet));
|
||||
|
||||
media_tracker.stream_opened = 1;
|
||||
status = a2dp_source_start_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_STREAM_RECONFIGURED:
|
||||
status = a2dp_subevent_stream_reconfigured_get_status(packet);
|
||||
local_seid = a2dp_subevent_stream_reconfigured_get_local_seid(packet);
|
||||
cid = a2dp_subevent_stream_reconfigured_get_a2dp_cid(packet);
|
||||
|
||||
if (status != ERROR_CODE_SUCCESS) {
|
||||
DEBUGV("A2DP Source: Stream reconfiguration failed, status 0x%02x\n", status);
|
||||
break;
|
||||
}
|
||||
|
||||
DEBUGV("A2DP Source: Stream reconfigured a2dp_cid 0x%02x, local_seid 0x%02x\n", cid, local_seid);
|
||||
status = a2dp_source_start_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_STREAM_STARTED:
|
||||
local_seid = a2dp_subevent_stream_started_get_local_seid(packet);
|
||||
cid = a2dp_subevent_stream_started_get_a2dp_cid(packet);
|
||||
|
||||
play_info.status = AVRCP_PLAYBACK_STATUS_PLAYING;
|
||||
if (media_tracker.avrcp_cid) {
|
||||
avrcp_target_set_now_playing_info(media_tracker.avrcp_cid, &_currentTrack, _tracks);
|
||||
avrcp_target_set_playback_status(media_tracker.avrcp_cid, AVRCP_PLAYBACK_STATUS_PLAYING);
|
||||
}
|
||||
a2dp_timer_start(&media_tracker);
|
||||
DEBUGV("A2DP Source: Stream started, a2dp_cid 0x%02x, local_seid 0x%02x\n", cid, local_seid);
|
||||
_connected = true;
|
||||
if (_connectCB) {
|
||||
_connectCB(_connectData, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_STREAMING_CAN_SEND_MEDIA_PACKET_NOW:
|
||||
local_seid = a2dp_subevent_streaming_can_send_media_packet_now_get_local_seid(packet);
|
||||
cid = a2dp_subevent_signaling_media_codec_sbc_configuration_get_a2dp_cid(packet);
|
||||
a2dp_send_media_packet();
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_STREAM_SUSPENDED:
|
||||
local_seid = a2dp_subevent_stream_suspended_get_local_seid(packet);
|
||||
cid = a2dp_subevent_stream_suspended_get_a2dp_cid(packet);
|
||||
|
||||
play_info.status = AVRCP_PLAYBACK_STATUS_PAUSED;
|
||||
if (media_tracker.avrcp_cid) {
|
||||
avrcp_target_set_playback_status(media_tracker.avrcp_cid, AVRCP_PLAYBACK_STATUS_PAUSED);
|
||||
}
|
||||
DEBUGV("A2DP Source: Stream paused, a2dp_cid 0x%02x, local_seid 0x%02x\n", cid, local_seid);
|
||||
|
||||
a2dp_timer_stop(&media_tracker);
|
||||
break;
|
||||
|
||||
case A2DP_SUBEVENT_STREAM_RELEASED:
|
||||
play_info.status = AVRCP_PLAYBACK_STATUS_STOPPED;
|
||||
cid = a2dp_subevent_stream_released_get_a2dp_cid(packet);
|
||||
local_seid = a2dp_subevent_stream_released_get_local_seid(packet);
|
||||
|
||||
DEBUGV("A2DP Source: Stream released, a2dp_cid 0x%02x, local_seid 0x%02x\n", cid, local_seid);
|
||||
|
||||
if (cid == media_tracker.a2dp_cid) {
|
||||
media_tracker.stream_opened = 0;
|
||||
DEBUGV("A2DP Source: Stream released.\n");
|
||||
}
|
||||
if (media_tracker.avrcp_cid) {
|
||||
avrcp_target_set_now_playing_info(media_tracker.avrcp_cid, NULL, _tracks);
|
||||
avrcp_target_set_playback_status(media_tracker.avrcp_cid, AVRCP_PLAYBACK_STATUS_STOPPED);
|
||||
}
|
||||
a2dp_timer_stop(&media_tracker);
|
||||
break;
|
||||
case A2DP_SUBEVENT_SIGNALING_CONNECTION_RELEASED:
|
||||
cid = a2dp_subevent_signaling_connection_released_get_a2dp_cid(packet);
|
||||
if (cid == media_tracker.a2dp_cid) {
|
||||
media_tracker.avrcp_cid = 0;
|
||||
media_tracker.a2dp_cid = 0;
|
||||
DEBUGV("A2DP Source: Signaling released.\n\n");
|
||||
_connected = false;
|
||||
if (_connectCB) {
|
||||
_connectCB(_connectData, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void A2DPSource::avrcp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
UNUSED(channel);
|
||||
UNUSED(size);
|
||||
bd_addr_t event_addr;
|
||||
uint16_t local_cid;
|
||||
uint8_t status = ERROR_CODE_SUCCESS;
|
||||
|
||||
if (packet_type != HCI_EVENT_PACKET) {
|
||||
return;
|
||||
}
|
||||
if (hci_event_packet_get_type(packet) != HCI_EVENT_AVRCP_META) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (packet[2]) {
|
||||
case AVRCP_SUBEVENT_CONNECTION_ESTABLISHED:
|
||||
local_cid = avrcp_subevent_connection_established_get_avrcp_cid(packet);
|
||||
status = avrcp_subevent_connection_established_get_status(packet);
|
||||
if (status != ERROR_CODE_SUCCESS) {
|
||||
DEBUGV("AVRCP: Connection failed, local cid 0x%02x, status 0x%02x\n", local_cid, status);
|
||||
return;
|
||||
}
|
||||
media_tracker.avrcp_cid = local_cid;
|
||||
avrcp_subevent_connection_established_get_bd_addr(packet, event_addr);
|
||||
|
||||
DEBUGV("AVRCP: Channel to %s successfully opened, avrcp_cid 0x%02x\n", bd_addr_to_str(event_addr), media_tracker.avrcp_cid);
|
||||
|
||||
avrcp_target_support_event(media_tracker.avrcp_cid, AVRCP_NOTIFICATION_EVENT_PLAYBACK_STATUS_CHANGED);
|
||||
avrcp_target_support_event(media_tracker.avrcp_cid, AVRCP_NOTIFICATION_EVENT_TRACK_CHANGED);
|
||||
avrcp_target_support_event(media_tracker.avrcp_cid, AVRCP_NOTIFICATION_EVENT_NOW_PLAYING_CONTENT_CHANGED);
|
||||
avrcp_target_set_now_playing_info(media_tracker.avrcp_cid, NULL, _tracks);
|
||||
|
||||
DEBUGV("Enable Volume Change notification\n");
|
||||
avrcp_controller_enable_notification(media_tracker.avrcp_cid, AVRCP_NOTIFICATION_EVENT_VOLUME_CHANGED);
|
||||
DEBUGV("Enable Battery Status Change notification\n");
|
||||
avrcp_controller_enable_notification(media_tracker.avrcp_cid, AVRCP_NOTIFICATION_EVENT_BATT_STATUS_CHANGED);
|
||||
return;
|
||||
|
||||
case AVRCP_SUBEVENT_CONNECTION_RELEASED:
|
||||
DEBUGV("AVRCP Target: Disconnected, avrcp_cid 0x%02x\n", avrcp_subevent_connection_released_get_avrcp_cid(packet));
|
||||
media_tracker.avrcp_cid = 0;
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (status != ERROR_CODE_SUCCESS) {
|
||||
DEBUGV("Responding to event 0x%02x failed, status 0x%02x\n", packet[2], status);
|
||||
}
|
||||
}
|
||||
|
||||
void A2DPSource::avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
UNUSED(channel);
|
||||
UNUSED(size);
|
||||
uint8_t status = ERROR_CODE_SUCCESS;
|
||||
|
||||
if (packet_type != HCI_EVENT_PACKET) {
|
||||
return;
|
||||
}
|
||||
if (hci_event_packet_get_type(packet) != HCI_EVENT_AVRCP_META) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool button_pressed;
|
||||
char const * button_state;
|
||||
avrcp_operation_id_t operation_id;
|
||||
|
||||
switch (packet[2]) {
|
||||
case AVRCP_SUBEVENT_PLAY_STATUS_QUERY:
|
||||
status = avrcp_target_play_status(media_tracker.avrcp_cid, play_info.song_length_ms, play_info.song_position_ms, play_info.status);
|
||||
break;
|
||||
// case AVRCP_SUBEVENT_NOW_PLAYING_INFO_QUERY:
|
||||
// status = avrcp_target_now_playing_info(avrcp_cid);
|
||||
// break;
|
||||
case AVRCP_SUBEVENT_OPERATION:
|
||||
operation_id = (avrcp_operation_id_t)avrcp_subevent_operation_get_operation_id(packet);
|
||||
button_pressed = avrcp_subevent_operation_get_button_pressed(packet) > 0;
|
||||
button_state = button_pressed ? "PRESS" : "RELEASE";
|
||||
(void) button_state;
|
||||
DEBUGV("AVRCP Target: operation %s (%s)\n", avrcp_operation2str(operation_id), button_state);
|
||||
if (_avrcpCB) {
|
||||
_avrcpCB(_avrcpData, operation_id, button_pressed);
|
||||
}
|
||||
if (!button_pressed) {
|
||||
break;
|
||||
}
|
||||
switch (operation_id) {
|
||||
case AVRCP_OPERATION_ID_PLAY:
|
||||
status = a2dp_source_start_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
|
||||
break;
|
||||
case AVRCP_OPERATION_ID_PAUSE:
|
||||
status = a2dp_source_pause_stream(media_tracker.a2dp_cid, media_tracker.local_seid);
|
||||
break;
|
||||
case AVRCP_OPERATION_ID_STOP:
|
||||
status = a2dp_source_disconnect(media_tracker.a2dp_cid);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (status != ERROR_CODE_SUCCESS) {
|
||||
DEBUGV("Responding to event 0x%02x failed, status 0x%02x\n", packet[2], status);
|
||||
}
|
||||
}
|
||||
|
||||
void A2DPSource::avrcp_controller_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
UNUSED(channel);
|
||||
UNUSED(size);
|
||||
|
||||
if (packet_type != HCI_EVENT_PACKET) {
|
||||
return;
|
||||
}
|
||||
if (hci_event_packet_get_type(packet) != HCI_EVENT_AVRCP_META) {
|
||||
return;
|
||||
}
|
||||
if (!media_tracker.avrcp_cid) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (packet[2]) {
|
||||
case AVRCP_SUBEVENT_NOTIFICATION_VOLUME_CHANGED:
|
||||
DEBUGV("AVRCP Controller: Notification Absolute Volume %d %%\n", avrcp_subevent_notification_volume_changed_get_absolute_volume(packet) * 100 / 127);
|
||||
if (_volumeCB) {
|
||||
_volumeCB(_volumeData, avrcp_subevent_notification_volume_changed_get_absolute_volume(packet) * 100 / 127);
|
||||
}
|
||||
break;
|
||||
case AVRCP_SUBEVENT_NOTIFICATION_EVENT_BATT_STATUS_CHANGED:
|
||||
// see avrcp_battery_status_t
|
||||
DEBUGV("AVRCP Controller: Notification Battery Status 0x%02x\n", avrcp_subevent_notification_event_batt_status_changed_get_battery_status(packet));
|
||||
if (_batteryCB) {
|
||||
_batteryCB(_batteryData, (avrcp_battery_status_t)avrcp_subevent_notification_event_batt_status_changed_get_battery_status(packet));
|
||||
}
|
||||
break;
|
||||
case AVRCP_SUBEVENT_NOTIFICATION_STATE:
|
||||
DEBUGV("AVRCP Controller: Notification %s - %s\n",
|
||||
avrcp_event2str(avrcp_subevent_notification_state_get_event_id(packet)),
|
||||
avrcp_subevent_notification_state_get_enabled(packet) != 0 ? "enabled" : "disabled");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
A1DP Source (Bluetooth audio sender)
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "BluetoothHCI.h"
|
||||
#include "BluetoothMediaConfigurationSBC.h"
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
|
||||
class A2DPSource : public Stream {
|
||||
public:
|
||||
A2DPSource() {
|
||||
}
|
||||
|
||||
bool setFrequency(uint32_t rate) {
|
||||
if (_running || ((rate != 44100) && (rate != 48000))) {
|
||||
return false;
|
||||
}
|
||||
_frequency = rate;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool setName(const char *name) {
|
||||
if (_running) {
|
||||
return false;
|
||||
}
|
||||
free(_name);
|
||||
_name = strdup(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void onTransmit(void (*cb)(void *), void *cbData = nullptr) {
|
||||
_transmitCB = cb;
|
||||
_transmitData = cbData;
|
||||
}
|
||||
|
||||
void onAVRCP(void (*cb)(void *, avrcp_operation_id_t, int), void *cbData = nullptr) {
|
||||
_avrcpCB = cb;
|
||||
_avrcpData = cbData;
|
||||
}
|
||||
|
||||
void onBattery(void (*cb)(void *, avrcp_battery_status_t), void *cbData = nullptr) {
|
||||
_batteryCB = cb;
|
||||
_batteryData = cbData;
|
||||
}
|
||||
|
||||
void onVolume(void (*cb)(void *, int), void *cbData = nullptr) {
|
||||
_volumeCB = cb;
|
||||
_volumeData = cbData;
|
||||
}
|
||||
|
||||
void onConnect(void (*cb)(void *, bool), void *cbData = nullptr) {
|
||||
_connectCB = cb;
|
||||
_connectData = cbData;
|
||||
}
|
||||
|
||||
bool setBufferSize(size_t size) {
|
||||
if (_running || (size & 127) || (size < 1024)) {
|
||||
return false;
|
||||
}
|
||||
_pcmBufferSize = size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getUnderflow() {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
__lockBluetooth();
|
||||
auto ret = _underflow;
|
||||
_underflow = false;
|
||||
__unlockBluetooth();
|
||||
return ret;
|
||||
}
|
||||
|
||||
const uint8_t *getSinkAddress() {
|
||||
if (!_connected) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return _sinkAddress;
|
||||
}
|
||||
}
|
||||
|
||||
bool begin();
|
||||
|
||||
std::list<BTDeviceInfo> scan(uint32_t mask = BluetoothHCI_::speaker_cod, int scanTimeSec = 5, bool async = false) {
|
||||
return _hci.scan(mask, scanTimeSec, async);
|
||||
}
|
||||
|
||||
bool connect(const uint8_t *addr = nullptr);
|
||||
|
||||
bool connected() {
|
||||
return _connected;
|
||||
}
|
||||
|
||||
bool disconnect();
|
||||
void clearPairing();
|
||||
|
||||
// from Stream
|
||||
virtual int available() override {
|
||||
return 0; // Unreadable, this is output only
|
||||
}
|
||||
|
||||
virtual int read() override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual int peek() override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void flush() override {
|
||||
}
|
||||
|
||||
// from Print (see notes on write() methods below)
|
||||
virtual size_t write(const uint8_t *buffer, size_t size) override;
|
||||
virtual int availableForWrite() override;
|
||||
|
||||
virtual size_t write(uint8_t s) override {
|
||||
(void) s;
|
||||
return 0; // Never any reason to write 8-bit data, make it always fail.
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
BluetoothHCI_ _hci;
|
||||
bool _running = false;
|
||||
bool _connected = false;
|
||||
|
||||
char *_name = nullptr;
|
||||
int _frequency = 44100;
|
||||
|
||||
// Callbacks
|
||||
void (*_transmitCB)(void *) = nullptr;
|
||||
void *_transmitData;
|
||||
void (*_avrcpCB)(void *, avrcp_operation_id_t, int) = nullptr;
|
||||
void *_avrcpData;
|
||||
void (*_batteryCB)(void *, avrcp_battery_status_t) = nullptr;
|
||||
void *_batteryData;
|
||||
void (*_volumeCB)(void *, int) = nullptr;
|
||||
void *_volumeData;
|
||||
void (*_connectCB)(void *, bool) = nullptr;
|
||||
void *_connectData;
|
||||
|
||||
const uint8_t media_sbc_codec_capabilities[4] = {
|
||||
(AVDTP_SBC_44100 << 4) | AVDTP_SBC_STEREO,
|
||||
0xFF,//(AVDTP_SBC_BLOCK_LENGTH_16 << 4) | (AVDTP_SBC_SUBBANDS_8 << 2) | AVDTP_SBC_ALLOCATION_METHOD_LOUDNESS,
|
||||
2, 53
|
||||
};
|
||||
uint8_t media_sbc_codec_configuration[4];
|
||||
|
||||
enum {SBC_STORAGE_SIZE = 1030, AUDIO_TIMEOUT_MS = 5};
|
||||
typedef struct {
|
||||
uint16_t a2dp_cid;
|
||||
uint8_t local_seid;
|
||||
uint8_t remote_seid;
|
||||
uint8_t stream_opened;
|
||||
uint16_t avrcp_cid;
|
||||
|
||||
uint32_t time_audio_data_sent; // ms
|
||||
uint32_t acc_num_missed_samples;
|
||||
uint32_t samples_ready;
|
||||
btstack_timer_source_t audio_timer;
|
||||
uint8_t streaming;
|
||||
int max_media_payload_size;
|
||||
uint32_t rtp_timestamp;
|
||||
|
||||
uint8_t sbc_storage[SBC_STORAGE_SIZE];
|
||||
uint16_t sbc_storage_count;
|
||||
uint8_t sbc_ready_to_send;
|
||||
|
||||
uint8_t volume;
|
||||
} a2dp_media_sending_context_t;
|
||||
|
||||
uint8_t sdp_a2dp_source_service_buffer[150];
|
||||
uint8_t sdp_avrcp_target_service_buffer[200];
|
||||
uint8_t sdp_avrcp_controller_service_buffer[200];
|
||||
uint8_t device_id_sdp_service_buffer[100];
|
||||
|
||||
BluetoothMediaCodecConfigurationSBC sbc_configuration;
|
||||
btstack_sbc_encoder_state_t sbc_encoder_state;
|
||||
|
||||
a2dp_media_sending_context_t media_tracker;
|
||||
|
||||
typedef struct {
|
||||
uint8_t track_id[8];
|
||||
uint32_t song_length_ms;
|
||||
avrcp_playback_status_t status;
|
||||
uint32_t song_position_ms; // 0xFFFFFFFF if not supported
|
||||
} avrcp_play_status_info_t;
|
||||
avrcp_play_status_info_t play_info;
|
||||
|
||||
void a2dp_timer_start(a2dp_media_sending_context_t * context);
|
||||
void a2dp_timer_stop(a2dp_media_sending_context_t * context);
|
||||
|
||||
int16_t *_pcmBuffer = nullptr;
|
||||
size_t _pcmBufferSize = 4096; // Multiple of 128 required
|
||||
uint32_t _pcmWriter;
|
||||
uint32_t _pcmReader;
|
||||
bool _underflow;
|
||||
|
||||
int a2dp_fill_sbc_audio_buffer(a2dp_media_sending_context_t * context);
|
||||
|
||||
void a2dp_audio_timeout_handler(btstack_timer_source_t * timer);
|
||||
|
||||
void a2dp_send_media_packet();
|
||||
|
||||
int _tracks = 1;
|
||||
avrcp_track_t _currentTrack;
|
||||
uint8_t _sinkAddress[6];
|
||||
|
||||
void a2dp_source_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
|
||||
void avrcp_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
void avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
void avrcp_controller_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "BluetoothDevice.h"
|
||||
#include "BluetoothHCI.h"
|
||||
#include "BluetoothMediaConfigurationSBC.h"
|
||||
#include "A2DPSource.h"
|
||||
#include "A2DPSink.h"
|
||||
#include "BluetoothAudioConsumer.h"
|
||||
#include "BluetoothAudioConsumerPWM.h"
|
||||
#include "BluetoothAudioConsumerI2S.h"
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
Bluetooth A2DP audio stream consumer
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
class A2DPSink;
|
||||
|
||||
class BluetoothAudioConsumer_ {
|
||||
public:
|
||||
BluetoothAudioConsumer_() {
|
||||
_state = STATE_OFF;
|
||||
}
|
||||
virtual ~BluetoothAudioConsumer_() {
|
||||
/* noop */
|
||||
}
|
||||
virtual bool init(uint8_t channels, uint32_t samplerate, A2DPSink *a2dpSink) = 0;
|
||||
virtual void setVolume(uint8_t gain) = 0;
|
||||
virtual void startStream() = 0;
|
||||
virtual void stopStream() = 0;
|
||||
virtual void close() = 0;
|
||||
protected:
|
||||
typedef enum { STATE_OFF = 0, STATE_INITIALIZED, STATE_STREAMING } State;
|
||||
A2DPSink *_a2dpSink;
|
||||
State _state;
|
||||
uint8_t _gain;
|
||||
int _channels;
|
||||
int _samplerate;
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Bluetooth A2DP audio stream consumer - PWMAudio
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "BluetoothAudioConsumerI2S.h"
|
||||
#include "A2DPSink.h"
|
||||
#include <functional>
|
||||
|
||||
#define CCALLBACKNAME _BTA2DPI2S
|
||||
#include <ctocppcallback.h>
|
||||
#define NOPARAMCB(class, cbFcn) \
|
||||
(CCALLBACKNAME<void(void), __COUNTER__>::func = std::bind(&class::cbFcn, this), \
|
||||
static_cast<void (*)(void)>(CCALLBACKNAME<void(void), __COUNTER__ - 1>::callback))
|
||||
|
||||
bool BluetoothAudioConsumerI2S::init(uint8_t channels, uint32_t samplerate, A2DPSink *a2dpSink) {
|
||||
_channels = channels;
|
||||
_samplerate = samplerate;
|
||||
_a2dpSink = a2dpSink;
|
||||
_i2s->setBuffers(16, 64);
|
||||
_i2s->onTransmit(NOPARAMCB(BluetoothAudioConsumerI2S, fill));
|
||||
if (_i2s->begin(samplerate)) {
|
||||
_state = STATE_INITIALIZED;
|
||||
_gain = 64;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BluetoothAudioConsumerI2S::setVolume(uint8_t gain) {
|
||||
_gain = gain;
|
||||
}
|
||||
|
||||
void BluetoothAudioConsumerI2S::startStream() {
|
||||
if (_state != STATE_INITIALIZED) {
|
||||
return;
|
||||
}
|
||||
_state = STATE_STREAMING;
|
||||
}
|
||||
|
||||
void BluetoothAudioConsumerI2S::stopStream() {
|
||||
if (_state != STATE_STREAMING) {
|
||||
return;
|
||||
}
|
||||
_state = STATE_INITIALIZED;
|
||||
}
|
||||
|
||||
void BluetoothAudioConsumerI2S::close() {
|
||||
if (_state == STATE_STREAMING) {
|
||||
stopStream();
|
||||
}
|
||||
_state = STATE_OFF;
|
||||
_i2s->end();
|
||||
}
|
||||
|
||||
void BluetoothAudioConsumerI2S::fill() {
|
||||
int num_samples = _i2s->availableForWrite() / 2;
|
||||
int16_t buff[32 * 2];
|
||||
while (num_samples > 63) {
|
||||
_a2dpSink->playback_handler((int16_t *) buff, 32);
|
||||
num_samples -= 64;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
int32_t tmp = buff[i];
|
||||
tmp *= _gain;
|
||||
tmp >>= 8;
|
||||
_i2s->write((int16_t)tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Bluetooth A2DP audio stream consumer - I2S
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <I2S.h>
|
||||
#include "BluetoothAudioConsumer.h"
|
||||
|
||||
class BluetoothAudioConsumerI2S : public BluetoothAudioConsumer_ {
|
||||
public:
|
||||
BluetoothAudioConsumerI2S(I2S &i2s) : BluetoothAudioConsumer_() {
|
||||
_i2s = &i2s;
|
||||
}
|
||||
|
||||
virtual bool init(uint8_t channels, uint32_t samplerate, A2DPSink *a2dpSink) override;
|
||||
virtual void setVolume(uint8_t gain) override;
|
||||
virtual void startStream() override;
|
||||
virtual void stopStream() override;
|
||||
virtual void close() override;
|
||||
|
||||
private:
|
||||
I2S *_i2s;
|
||||
void fill();
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Bluetooth A2DP audio stream consumer - PWMAudio
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include "BluetoothAudioConsumerPWM.h"
|
||||
#include "A2DPSink.h"
|
||||
#include <functional>
|
||||
|
||||
#define CCALLBACKNAME _BTA2DPPWM
|
||||
#include <ctocppcallback.h>
|
||||
#define NOPARAMCB(class, cbFcn) \
|
||||
(CCALLBACKNAME<void(void), __COUNTER__>::func = std::bind(&class::cbFcn, this), \
|
||||
static_cast<void (*)(void)>(CCALLBACKNAME<void(void), __COUNTER__ - 1>::callback))
|
||||
|
||||
bool BluetoothAudioConsumerPWM::init(uint8_t channels, uint32_t samplerate, A2DPSink *a2dpSink) {
|
||||
_channels = channels;
|
||||
_samplerate = samplerate;
|
||||
_a2dpSink = a2dpSink;
|
||||
_pwm->setStereo(channels == 2);
|
||||
_pwm->setBuffers(16, 64);
|
||||
_pwm->onTransmit(NOPARAMCB(BluetoothAudioConsumerPWM, fill));
|
||||
if (_pwm->begin(samplerate)) {
|
||||
_state = STATE_INITIALIZED;
|
||||
_gain = 64;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BluetoothAudioConsumerPWM::setVolume(uint8_t gain) {
|
||||
_gain = gain;
|
||||
}
|
||||
|
||||
void BluetoothAudioConsumerPWM::startStream() {
|
||||
if (_state != STATE_INITIALIZED) {
|
||||
return;
|
||||
}
|
||||
_state = STATE_STREAMING;
|
||||
}
|
||||
|
||||
void BluetoothAudioConsumerPWM::stopStream() {
|
||||
if (_state != STATE_STREAMING) {
|
||||
return;
|
||||
}
|
||||
_state = STATE_INITIALIZED;
|
||||
}
|
||||
|
||||
void BluetoothAudioConsumerPWM::close() {
|
||||
if (_state == STATE_STREAMING) {
|
||||
stopStream();
|
||||
}
|
||||
_state = STATE_OFF;
|
||||
_pwm->end();
|
||||
}
|
||||
|
||||
void BluetoothAudioConsumerPWM::fill() {
|
||||
int num_samples = _pwm->availableForWrite() / 2;
|
||||
int16_t buff[32 * 2];
|
||||
while (num_samples > 63) {
|
||||
_a2dpSink->playback_handler((int16_t *) buff, 32);
|
||||
num_samples -= 64;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
int32_t tmp = buff[i];
|
||||
tmp *= _gain;
|
||||
tmp >>= 8;
|
||||
_pwm->write((int16_t)tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Bluetooth A2DP audio stream consumer - PWMAudio
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <PWMAudio.h>
|
||||
#include "BluetoothAudioConsumer.h"
|
||||
|
||||
class BluetoothAudioConsumerPWM : public BluetoothAudioConsumer_ {
|
||||
public:
|
||||
BluetoothAudioConsumerPWM(PWMAudio &pwm) : BluetoothAudioConsumer_() {
|
||||
_pwm = &pwm;
|
||||
}
|
||||
|
||||
virtual bool init(uint8_t channels, uint32_t samplerate, A2DPSink *a2dpSink) override;
|
||||
virtual void setVolume(uint8_t gain) override;
|
||||
virtual void startStream() override;
|
||||
virtual void stopStream() override;
|
||||
virtual void close() override;
|
||||
|
||||
private:
|
||||
PWMAudio *_pwm;
|
||||
void fill();
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Bluetooth device helper class
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class BTDeviceInfo {
|
||||
public:
|
||||
BTDeviceInfo(uint32_t dc, const uint8_t addr[6], int rssi, const char *name) {
|
||||
_deviceClass = dc;
|
||||
memcpy(_address, addr, sizeof(_address));
|
||||
sprintf(_addressString, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
|
||||
_rssi = rssi;
|
||||
_name = strdup(name);
|
||||
}
|
||||
// Copy constructor to ensure we deep-copy the string
|
||||
BTDeviceInfo(const BTDeviceInfo &b) {
|
||||
_deviceClass = b._deviceClass;
|
||||
memcpy(_address, b._address, sizeof(_address));
|
||||
memcpy(_addressString, b._addressString, sizeof(_addressString));
|
||||
_rssi = b._rssi;
|
||||
_name = strdup(b._name);
|
||||
}
|
||||
~BTDeviceInfo() {
|
||||
free(_name);
|
||||
}
|
||||
uint32_t deviceClass() {
|
||||
return _deviceClass;
|
||||
}
|
||||
const uint8_t *address() {
|
||||
return _address;
|
||||
}
|
||||
const char *addressString() {
|
||||
return _addressString;
|
||||
}
|
||||
int rssi() {
|
||||
return _rssi;
|
||||
}
|
||||
const char *name() {
|
||||
return _name;
|
||||
}
|
||||
private:
|
||||
uint32_t _deviceClass;
|
||||
uint8_t _address[6];
|
||||
char _addressString[18];
|
||||
int8_t _rssi;
|
||||
char *_name;
|
||||
};
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
Bluetooth HCI packet handler class
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "btstack.h"
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "BluetoothHCI.h"
|
||||
|
||||
#define CCALLBACKNAME _BTHCICB
|
||||
#include <ctocppcallback.h>
|
||||
|
||||
|
||||
#define PACKETHANDLERCB(class, cbFcn) \
|
||||
(_BTHCICB<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), \
|
||||
static_cast<btstack_packet_handler_t>(_BTHCICB<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
|
||||
|
||||
|
||||
void BluetoothHCI_::install() {
|
||||
// Register for HCI events.
|
||||
hci_event_callback_registration.callback = PACKETHANDLERCB(BluetoothHCI_, hci_packet_handler);
|
||||
hci_add_event_handler(&hci_event_callback_registration);
|
||||
}
|
||||
|
||||
void BluetoothHCI_::begin() {
|
||||
_running = true;
|
||||
hci_power_control(HCI_POWER_ON);
|
||||
}
|
||||
|
||||
void BluetoothHCI_::uninstall() {
|
||||
__lockBluetooth();
|
||||
hci_remove_event_handler(&hci_event_callback_registration);
|
||||
_running = false;
|
||||
__unlockBluetooth();
|
||||
}
|
||||
|
||||
bool BluetoothHCI_::running() {
|
||||
return _hciRunning;
|
||||
}
|
||||
|
||||
std::list<BTDeviceInfo> BluetoothHCI_::scan(uint32_t mask, int scanTimeSec, bool async) {
|
||||
_scanMask = mask;
|
||||
_btdList.clear();
|
||||
if (!_running) {
|
||||
return _btdList;
|
||||
}
|
||||
_scanning = true;
|
||||
while (!_hciRunning) {
|
||||
DEBUGV("HCI::scan(): Waiting for HCI to come up\n");
|
||||
delay(10);
|
||||
}
|
||||
int inquiryTime = (scanTimeSec * 1000) / 1280; // divide by 1.280
|
||||
if (!inquiryTime) {
|
||||
inquiryTime = 1;
|
||||
}
|
||||
DEBUGV("HCI::scan(): inquiry start\n");
|
||||
__lockBluetooth();
|
||||
gap_inquiry_start(inquiryTime);
|
||||
__unlockBluetooth();
|
||||
if (async) {
|
||||
return _btdList;
|
||||
}
|
||||
|
||||
while (_scanning) {
|
||||
delay(10);
|
||||
}
|
||||
DEBUGV("HCI::scan(): inquiry end\n");
|
||||
return _btdList;
|
||||
}
|
||||
|
||||
bool BluetoothHCI_::scanAsyncDone() {
|
||||
return _scanning;
|
||||
}
|
||||
std::list<BTDeviceInfo> BluetoothHCI_::scanAsyncResult() {
|
||||
return _btdList;
|
||||
}
|
||||
|
||||
|
||||
void BluetoothHCI_::hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
(void)channel;
|
||||
(void)size;
|
||||
|
||||
if (packet_type != HCI_EVENT_PACKET) {
|
||||
return;
|
||||
}
|
||||
|
||||
bd_addr_t address;
|
||||
uint32_t cod;
|
||||
int8_t rssi;
|
||||
const char *name;
|
||||
char name_buffer[241];
|
||||
|
||||
switch (hci_event_packet_get_type(packet)) {
|
||||
case BTSTACK_EVENT_STATE:
|
||||
_hciRunning = (btstack_event_state_get_state(packet) == HCI_STATE_WORKING);
|
||||
break;
|
||||
case HCI_EVENT_PIN_CODE_REQUEST:
|
||||
hci_event_pin_code_request_get_bd_addr(packet, address);
|
||||
gap_pin_code_response(address, "0000");
|
||||
break;
|
||||
case GAP_EVENT_INQUIRY_RESULT:
|
||||
if (!_scanning) {
|
||||
return;
|
||||
}
|
||||
gap_event_inquiry_result_get_bd_addr(packet, address);
|
||||
cod = gap_event_inquiry_result_get_class_of_device(packet);
|
||||
if (gap_event_inquiry_result_get_rssi_available(packet)) {
|
||||
rssi = gap_event_inquiry_result_get_rssi(packet);
|
||||
} else {
|
||||
rssi = -128;
|
||||
}
|
||||
if (gap_event_inquiry_result_get_name_available(packet)) {
|
||||
int name_len = gap_event_inquiry_result_get_name_len(packet);
|
||||
memcpy(name_buffer, gap_event_inquiry_result_get_name(packet), name_len);
|
||||
name_buffer[name_len] = 0;
|
||||
name = name_buffer;
|
||||
} else {
|
||||
name = "";
|
||||
}
|
||||
DEBUGV("HCI: Scan found '%s', COD 0x%08X, RSSI %d, MAC %02X:%02X:%02X:%02X:%02X:%02X\n", name, (unsigned int)cod, rssi, address[0], address[1], address[2], address[3], address[4], address[5]);
|
||||
if ((_scanMask & cod) == _scanMask) {
|
||||
// Sometimes we get multiple reports for the same MAC, so remove any old reports since newer will have newer RSSI
|
||||
bool updated = false;
|
||||
for (auto itr = _btdList.begin(); (itr != _btdList.end()) && !updated; itr++) {
|
||||
if (!memcmp(itr->address(), address, sizeof(address))) {
|
||||
// Sometimes the name is missing on reports, so if we found it once preserve it
|
||||
if (!name_buffer[0] && itr->name()[0]) {
|
||||
strcpy(name_buffer, itr->name());
|
||||
}
|
||||
_btdList.erase(itr);
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
BTDeviceInfo btd(cod, address, rssi, name);
|
||||
_btdList.push_back(btd);
|
||||
}
|
||||
break;
|
||||
case GAP_EVENT_INQUIRY_COMPLETE:
|
||||
_scanning = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
Bluetooth HCI packet handler class
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "BluetoothDevice.h"
|
||||
#include <btstack.h>
|
||||
|
||||
|
||||
class BluetoothHCI_ {
|
||||
public:
|
||||
void install();
|
||||
void begin();
|
||||
void uninstall();
|
||||
bool running();
|
||||
|
||||
static const uint32_t speaker_cod = 0x200000 | 0x040000 | 0x000400; // Service Class: Rendering | Audio, Major Device Class: Audio
|
||||
static const uint32_t any_cod = 0;
|
||||
std::list<BTDeviceInfo> scan(uint32_t mask, int scanTimeSec = 5, bool async = false);
|
||||
bool scanAsyncDone();
|
||||
std::list<BTDeviceInfo> scanAsyncResult();
|
||||
|
||||
private:
|
||||
void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
btstack_packet_callback_registration_t hci_event_callback_registration;
|
||||
volatile bool _hciRunning = false;
|
||||
uint32_t _scanMask;
|
||||
std::list<BTDeviceInfo> _btdList;
|
||||
volatile bool _scanning = false;
|
||||
bool _running = false;
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
A1DP Source (Bluetooth audio sender)
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class BluetoothMediaCodecConfigurationSBC {
|
||||
public:
|
||||
uint8_t reconfigure;
|
||||
uint8_t num_channels;
|
||||
uint16_t sampling_frequency;
|
||||
uint8_t block_length;
|
||||
uint8_t subbands;
|
||||
uint8_t min_bitpool_value;
|
||||
uint8_t max_bitpool_value;
|
||||
btstack_sbc_channel_mode_t channel_mode;
|
||||
btstack_sbc_allocation_method_t allocation_method;
|
||||
|
||||
void dump() {
|
||||
DEBUGV(" - num_channels: %d\n", num_channels);
|
||||
DEBUGV(" - sampling_frequency: %d\n", sampling_frequency);
|
||||
DEBUGV(" - channel_mode: %d\n", channel_mode);
|
||||
DEBUGV(" - block_length: %d\n", block_length);
|
||||
DEBUGV(" - subbands: %d\n", subbands);
|
||||
DEBUGV(" - allocation_method: %d\n", allocation_method);
|
||||
DEBUGV(" - bitpool_value [%d, %d] \n", min_bitpool_value, max_bitpool_value);
|
||||
DEBUGV("\n");
|
||||
}
|
||||
};
|
||||
@@ -29,7 +29,7 @@ const char *softAP_password = APPSK;
|
||||
/* hostname for mDNS. Should work at least on windows. Try http://picow.local */
|
||||
const char *myHostname = "picow";
|
||||
|
||||
/* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
|
||||
/* Don't set this wifi credentials. They are configured at runtime and stored on EEPROM */
|
||||
char ssid[33] = "";
|
||||
char password[65] = "";
|
||||
|
||||
|
||||
+1
-1
Submodule libraries/ESP8266SdFat updated: 16ba999e1d...c6b7f4a6fb
@@ -12,7 +12,7 @@
|
||||
#include <task.h>
|
||||
#include <semphr.h>
|
||||
|
||||
#define SERIAL_PORT Serial1
|
||||
#define SERIAL_PORT Serial
|
||||
#define BLINK_ON_TIME 250
|
||||
#define BLINK_OFF_TIME 500
|
||||
|
||||
@@ -58,6 +58,7 @@ void led_ON(void *pvParameters)
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
delay(BLINK_ON_TIME);
|
||||
xSemaphoreGive( xSemaphore );
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +72,7 @@ void led_OFF(void *pvParameters)
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
delay(BLINK_OFF_TIME);
|
||||
xSemaphoreGive( xSemaphore );
|
||||
delay(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ setCanSendNowCB KEYWORD2
|
||||
startHID KEYWORD2
|
||||
connected KEYWORD2
|
||||
send KEYWORD2
|
||||
lockBluetooth KEYWORD2
|
||||
unlockBluetooth KEYWORD2
|
||||
getCID KEYWORD2
|
||||
setBattery KEYWORD2
|
||||
|
||||
|
||||
@@ -20,4 +20,80 @@
|
||||
|
||||
#include "PicoBluetoothBLEHID.h"
|
||||
|
||||
#define CCALLBACKNAME _BLEHIDCB
|
||||
#include <ctocppcallback.h>
|
||||
|
||||
#define PACKETHANDLERCB(class, cbFcn) \
|
||||
(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), \
|
||||
static_cast<btstack_packet_handler_t>(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
|
||||
|
||||
bool PicoBluetoothBLEHID_::startHID(const char *localName, const char *hidName, uint16_t appearance, const uint8_t *hidDescriptor, uint16_t hidDescriptorSize, int battery) {
|
||||
if (_running) {
|
||||
return false;
|
||||
}
|
||||
_running = true;
|
||||
|
||||
_buildAdvData(localName, appearance);
|
||||
_buildAttdb(hidName);
|
||||
|
||||
_battery = battery;
|
||||
|
||||
// Setup L2CAP
|
||||
l2cap_init();
|
||||
|
||||
// Setup SM
|
||||
sm_init();
|
||||
sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
|
||||
sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_BONDING);
|
||||
|
||||
// Setup ATT server
|
||||
att_server_init(_attdb, NULL, NULL);
|
||||
|
||||
// Setup battery service
|
||||
battery_service_server_init(battery);
|
||||
|
||||
// Setup device information service
|
||||
device_information_service_server_init();
|
||||
|
||||
// Setup HID Device service, depending on activated reports
|
||||
uint8_t numreports = 1; //start with 1 (feature report)
|
||||
if (__BLEInstallKeyboard) {
|
||||
numreports += 2; //add keycodes + consumer keys
|
||||
}
|
||||
if (__BLEInstallMouse) {
|
||||
numreports += 1;
|
||||
}
|
||||
if (__BLEInstallJoystick) {
|
||||
numreports += 1;
|
||||
}
|
||||
//allocate memory for hid reports
|
||||
_reportStorage = (hids_device_report_t *) malloc(sizeof(hids_device_report_t) * numreports);
|
||||
hids_device_init_with_storage(0, hidDescriptor, hidDescriptorSize, numreports, _reportStorage);
|
||||
|
||||
// Setup advertisements
|
||||
uint16_t adv_int_min = 0x0030;
|
||||
uint16_t adv_int_max = 0x0030;
|
||||
uint8_t adv_type = 0;
|
||||
bd_addr_t null_addr;
|
||||
memset(null_addr, 0, 6);
|
||||
gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00);
|
||||
gap_advertisements_set_data(_advDataLen, _advData);
|
||||
gap_advertisements_enable(1);
|
||||
|
||||
// Register for HCI events
|
||||
_hci_event_callback_registration.callback = PACKETHANDLERCB(PicoBluetoothBLEHID_, packetHandler);
|
||||
hci_add_event_handler(&_hci_event_callback_registration);
|
||||
|
||||
// Register for SM events
|
||||
_sm_event_callback_registration.callback = PACKETHANDLERCB(PicoBluetoothBLEHID_, packetHandler);
|
||||
sm_add_event_handler(&_sm_event_callback_registration);
|
||||
|
||||
// Register for HIDS events
|
||||
hids_device_register_packet_handler(PACKETHANDLERCB(PicoBluetoothBLEHID_, packetHandler));
|
||||
|
||||
// GO!
|
||||
hci_power_control(HCI_POWER_ON);
|
||||
return true;
|
||||
}
|
||||
|
||||
PicoBluetoothBLEHID_ PicoBluetoothBLEHID;
|
||||
|
||||
@@ -79,79 +79,9 @@ public:
|
||||
_onCanSendNow = cb;
|
||||
}
|
||||
|
||||
bool startHID(const char *localName, const char *hidName, uint16_t appearance, const uint8_t *hidDescriptor, uint16_t hidDescriptorSize, int battery = 100) {
|
||||
if (_running) {
|
||||
return false;
|
||||
}
|
||||
_running = true;
|
||||
|
||||
_buildAdvData(localName, appearance);
|
||||
_buildAttdb(hidName);
|
||||
|
||||
_battery = battery;
|
||||
|
||||
// Setup L2CAP
|
||||
l2cap_init();
|
||||
|
||||
// Setup SM
|
||||
sm_init();
|
||||
sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
|
||||
sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_BONDING);
|
||||
|
||||
// Setup ATT server
|
||||
att_server_init(_attdb, NULL, NULL);
|
||||
|
||||
// Setup battery service
|
||||
battery_service_server_init(battery);
|
||||
|
||||
// Setup device information service
|
||||
device_information_service_server_init();
|
||||
|
||||
// Setup HID Device service, depending on activated reports
|
||||
uint8_t numreports = 1; //start with 1 (feature report)
|
||||
if (__BLEInstallKeyboard) {
|
||||
numreports += 2; //add keycodes + consumer keys
|
||||
}
|
||||
if (__BLEInstallMouse) {
|
||||
numreports += 1;
|
||||
}
|
||||
if (__BLEInstallJoystick) {
|
||||
numreports += 1;
|
||||
}
|
||||
//allocate memory for hid reports
|
||||
_reportStorage = (hids_device_report_t *) malloc(sizeof(hids_device_report_t) * numreports);
|
||||
hids_device_init_with_storage(0, hidDescriptor, hidDescriptorSize, numreports, _reportStorage);
|
||||
|
||||
// Setup advertisements
|
||||
uint16_t adv_int_min = 0x0030;
|
||||
uint16_t adv_int_max = 0x0030;
|
||||
uint8_t adv_type = 0;
|
||||
bd_addr_t null_addr;
|
||||
memset(null_addr, 0, 6);
|
||||
gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00);
|
||||
gap_advertisements_set_data(_advDataLen, _advData);
|
||||
gap_advertisements_enable(1);
|
||||
|
||||
// Register for HCI events
|
||||
_hci_event_callback_registration.callback = PacketHandlerWrapper;
|
||||
hci_add_event_handler(&_hci_event_callback_registration);
|
||||
|
||||
// Register for SM events
|
||||
_sm_event_callback_registration.callback = PacketHandlerWrapper;
|
||||
sm_add_event_handler(&_sm_event_callback_registration);
|
||||
|
||||
// Register for HIDS events
|
||||
hids_device_register_packet_handler(PacketHandlerWrapper);
|
||||
|
||||
// GO!
|
||||
hci_power_control(HCI_POWER_ON);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void PacketHandlerWrapper(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t packet_size) {
|
||||
PicoBluetoothBLEHID.packetHandler(packet_type, channel, packet, packet_size);
|
||||
}
|
||||
bool startHID(const char *localName, const char *hidName, uint16_t appearance, const uint8_t *hidDescriptor, uint16_t hidDescriptorSize, int battery = 100);
|
||||
|
||||
private:
|
||||
void packetHandler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
uint8_t result;
|
||||
uint8_t reportID;
|
||||
@@ -214,14 +144,6 @@ public:
|
||||
if (result) {
|
||||
Serial.printf("Error sending %d - report ID: %d\n", result, reportID);
|
||||
}
|
||||
//else Serial.printf("Sent report for ID: %d\n",reportID);
|
||||
#if 0
|
||||
Serial.printf("Sending report for ID %d, len: %d:\n", reportID, _sendReportLen);
|
||||
for (uint8_t i = 0; i < _sendReportLen; i++) {
|
||||
Serial.printf("0x%02X - ", ((const uint8_t *)_sendReport)[i]);
|
||||
}
|
||||
Serial.println("");
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -237,7 +159,7 @@ public:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
bool end() {
|
||||
if (_running) {
|
||||
hci_power_control(HCI_POWER_OFF);
|
||||
@@ -259,9 +181,9 @@ public:
|
||||
_needToSend = true;
|
||||
_sendReport = rpt;
|
||||
_sendReportLen = len;
|
||||
lockBluetooth();
|
||||
__lockBluetooth();
|
||||
hids_device_request_can_send_now_event(_con_handle);
|
||||
unlockBluetooth();
|
||||
__unlockBluetooth();
|
||||
while (connected() && _needToSend) {
|
||||
/* noop busy wait */
|
||||
}
|
||||
@@ -276,14 +198,6 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
static void lockBluetooth() {
|
||||
async_context_acquire_lock_blocking(cyw43_arch_async_context());
|
||||
}
|
||||
|
||||
static void unlockBluetooth() {
|
||||
async_context_release_lock(cyw43_arch_async_context());
|
||||
}
|
||||
|
||||
uint8_t *_attdb = nullptr;
|
||||
int _attdbLen = 0;
|
||||
|
||||
|
||||
@@ -20,4 +20,81 @@
|
||||
|
||||
#include "PicoBluetoothHID.h"
|
||||
|
||||
#define CCALLBACKNAME _BTHIDCB
|
||||
#include <ctocppcallback.h>
|
||||
|
||||
#define PACKETHANDLERCB(class, cbFcn) \
|
||||
(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), \
|
||||
static_cast<btstack_packet_handler_t>(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
|
||||
|
||||
bool PicoBluetoothHID_::startHID(const char *localName, const char *hidName, uint16_t hidClass, uint8_t hidSubclass, const uint8_t *hidDescriptor, uint16_t hidDescriptorSize) {
|
||||
if (_running) {
|
||||
return false;
|
||||
}
|
||||
_running = true;
|
||||
// Allow finding via inquiry
|
||||
gap_discoverable_control(1);
|
||||
// Use Limited Discoverable Mode; Peripheral; Keyboard as CoD
|
||||
gap_set_class_of_device(hidClass);
|
||||
// Set local name to be identified - zeroes will be replaced by actual BD ADDR
|
||||
gap_set_local_name(localName);
|
||||
// Allow for role switch in general and sniff mode
|
||||
gap_set_default_link_policy_settings(LM_LINK_POLICY_ENABLE_ROLE_SWITCH | LM_LINK_POLICY_ENABLE_SNIFF_MODE);
|
||||
// Allow for role switch on outgoing connections - this allow HID Host to become master when we re-connect to it
|
||||
gap_set_allow_role_switch(true);
|
||||
|
||||
// L2CAP
|
||||
l2cap_init();
|
||||
#ifdef ENABLE_BLE
|
||||
// Initialize LE Security Manager. Needed for cross-transport key derivation
|
||||
sm_init();
|
||||
#endif
|
||||
|
||||
// SDP Server
|
||||
sdp_init();
|
||||
bzero(_hid_service_buffer, sizeof(_hid_service_buffer));
|
||||
|
||||
const uint8_t hid_boot_device = 0;
|
||||
const uint8_t hid_virtual_cable = 0;
|
||||
const uint8_t hid_remote_wake = 1;
|
||||
const uint8_t hid_reconnect_initiate = 1;
|
||||
const uint8_t hid_normally_connectable = 1;
|
||||
// When not set to 0xffff, sniff and sniff subrating are enabled
|
||||
const uint16_t host_max_latency = 1600;
|
||||
const uint16_t host_min_timeout = 3200;
|
||||
|
||||
hid_sdp_record_t hid_params = {
|
||||
hidClass, hidSubclass,
|
||||
hid_virtual_cable, hid_remote_wake,
|
||||
hid_reconnect_initiate, (bool)hid_normally_connectable,
|
||||
(bool)hid_boot_device,
|
||||
host_max_latency, host_min_timeout,
|
||||
3200,
|
||||
hidDescriptor,
|
||||
hidDescriptorSize,
|
||||
hidName
|
||||
};
|
||||
|
||||
hid_create_sdp_record(_hid_service_buffer, 0x10001, &hid_params);
|
||||
sdp_register_service(_hid_service_buffer);
|
||||
|
||||
// See https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers if you don't have a USB Vendor ID and need a Bluetooth Vendor ID
|
||||
// device info: BlueKitchen GmbH, product 1, version 1
|
||||
device_id_create_sdp_record(_device_id_sdp_service_buffer, 0x10003, DEVICE_ID_VENDOR_ID_SOURCE_BLUETOOTH, BLUETOOTH_COMPANY_ID_BLUEKITCHEN_GMBH, 1, 1);
|
||||
sdp_register_service(_device_id_sdp_service_buffer);
|
||||
|
||||
// HID Device
|
||||
hid_device_init(hid_boot_device, hidDescriptorSize, hidDescriptor);
|
||||
|
||||
// register for HCI events
|
||||
_hci_event_callback_registration.callback = PACKETHANDLERCB(PicoBluetoothHID_, packetHandler);
|
||||
hci_add_event_handler(&_hci_event_callback_registration);
|
||||
|
||||
// register for HID events
|
||||
hid_device_register_packet_handler(PACKETHANDLERCB(PicoBluetoothHID_, packetHandler));
|
||||
|
||||
hci_power_control(HCI_POWER_ON);
|
||||
return true;
|
||||
}
|
||||
|
||||
PicoBluetoothHID_ PicoBluetoothHID;
|
||||
|
||||
@@ -68,80 +68,9 @@ public:
|
||||
_onCanSendNow = cb;
|
||||
}
|
||||
|
||||
bool startHID(const char *localName, const char *hidName, uint16_t hidClass, uint8_t hidSubclass, const uint8_t *hidDescriptor, uint16_t hidDescriptorSize) {
|
||||
if (_running) {
|
||||
return false;
|
||||
}
|
||||
_running = true;
|
||||
// Allow finding via inquiry
|
||||
gap_discoverable_control(1);
|
||||
// Use Limited Discoverable Mode; Peripheral; Keyboard as CoD
|
||||
gap_set_class_of_device(hidClass);
|
||||
// Set local name to be identified - zeroes will be replaced by actual BD ADDR
|
||||
gap_set_local_name(localName);
|
||||
// Allow for role switch in general and sniff mode
|
||||
gap_set_default_link_policy_settings(LM_LINK_POLICY_ENABLE_ROLE_SWITCH | LM_LINK_POLICY_ENABLE_SNIFF_MODE);
|
||||
// Allow for role switch on outgoing connections - this allow HID Host to become master when we re-connect to it
|
||||
gap_set_allow_role_switch(true);
|
||||
|
||||
// L2CAP
|
||||
l2cap_init();
|
||||
#ifdef ENABLE_BLE
|
||||
// Initialize LE Security Manager. Needed for cross-transport key derivation
|
||||
sm_init();
|
||||
#endif
|
||||
|
||||
// SDP Server
|
||||
sdp_init();
|
||||
bzero(_hid_service_buffer, sizeof(_hid_service_buffer));
|
||||
|
||||
const uint8_t hid_boot_device = 0;
|
||||
const uint8_t hid_virtual_cable = 0;
|
||||
const uint8_t hid_remote_wake = 1;
|
||||
const uint8_t hid_reconnect_initiate = 1;
|
||||
const uint8_t hid_normally_connectable = 1;
|
||||
// When not set to 0xffff, sniff and sniff subrating are enabled
|
||||
const uint16_t host_max_latency = 1600;
|
||||
const uint16_t host_min_timeout = 3200;
|
||||
|
||||
hid_sdp_record_t hid_params = {
|
||||
hidClass, hidSubclass,
|
||||
hid_virtual_cable, hid_remote_wake,
|
||||
hid_reconnect_initiate, (bool)hid_normally_connectable,
|
||||
(bool)hid_boot_device,
|
||||
host_max_latency, host_min_timeout,
|
||||
3200,
|
||||
hidDescriptor,
|
||||
hidDescriptorSize,
|
||||
hidName
|
||||
};
|
||||
|
||||
hid_create_sdp_record(_hid_service_buffer, 0x10001, &hid_params);
|
||||
sdp_register_service(_hid_service_buffer);
|
||||
|
||||
// See https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers if you don't have a USB Vendor ID and need a Bluetooth Vendor ID
|
||||
// device info: BlueKitchen GmbH, product 1, version 1
|
||||
device_id_create_sdp_record(_device_id_sdp_service_buffer, 0x10003, DEVICE_ID_VENDOR_ID_SOURCE_BLUETOOTH, BLUETOOTH_COMPANY_ID_BLUEKITCHEN_GMBH, 1, 1);
|
||||
sdp_register_service(_device_id_sdp_service_buffer);
|
||||
|
||||
// HID Device
|
||||
hid_device_init(hid_boot_device, hidDescriptorSize, hidDescriptor);
|
||||
|
||||
// register for HCI events
|
||||
_hci_event_callback_registration.callback = PacketHandlerWrapper;
|
||||
hci_add_event_handler(&_hci_event_callback_registration);
|
||||
|
||||
// register for HID events
|
||||
hid_device_register_packet_handler(PacketHandlerWrapper);
|
||||
|
||||
hci_power_control(HCI_POWER_ON);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void PacketHandlerWrapper(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t packet_size) {
|
||||
PicoBluetoothHID.packetHandler(packet_type, channel, packet, packet_size);
|
||||
}
|
||||
bool startHID(const char *localName, const char *hidName, uint16_t hidClass, uint8_t hidSubclass, const uint8_t *hidDescriptor, uint16_t hidDescriptorSize);
|
||||
|
||||
private:
|
||||
void packetHandler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
uint8_t status;
|
||||
if (type != HCI_EVENT_PACKET) {
|
||||
@@ -198,7 +127,7 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
bool end() {
|
||||
if (_running) {
|
||||
hci_power_control(HCI_POWER_OFF);
|
||||
@@ -217,23 +146,15 @@ public:
|
||||
_sendReportID = id;
|
||||
_sendReport = rpt;
|
||||
_sendReportLen = len;
|
||||
lockBluetooth();
|
||||
__lockBluetooth();
|
||||
hid_device_request_can_send_now_event(getCID());
|
||||
unlockBluetooth();
|
||||
__unlockBluetooth();
|
||||
while (connected() && _needToSend) {
|
||||
/* noop busy wait */
|
||||
}
|
||||
return connected();
|
||||
}
|
||||
|
||||
static void lockBluetooth() {
|
||||
async_context_acquire_lock_blocking(cyw43_arch_async_context());
|
||||
}
|
||||
|
||||
static void unlockBluetooth() {
|
||||
async_context_release_lock(cyw43_arch_async_context());
|
||||
}
|
||||
|
||||
uint16_t getCID() {
|
||||
return _hid_cid;
|
||||
}
|
||||
|
||||
@@ -1336,7 +1336,7 @@ bool MDNSResponder::_writeMDNSHostDomain(const char* p_pcHostname, bool p_bPrepe
|
||||
|
||||
A very simple form of name compression is applied here: see '_writeMDNSHostDomain'
|
||||
The cache differentiates of course between service domains which includes
|
||||
the instance name (p_bIncludeName is set) and thoose who don't.
|
||||
the instance name (p_bIncludeName is set) and those who don't.
|
||||
|
||||
*/
|
||||
bool
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// Shows how to use SPISlave on a single device in asynchronous mode
|
||||
// Core0 runs as an SPI master and initiates a transmission to the slave
|
||||
// Core1 runs the SPI Slave mode and provides a unique reply to messages from the master
|
||||
//
|
||||
// Released to the public domain 2024 by Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
#include <SPI.h>
|
||||
#include <SPISlave.h>
|
||||
|
||||
// Wiring:
|
||||
// Master RX GP0 <-> GP11 Slave TX
|
||||
// Master CS GP1 <-> GP9 Slave CS
|
||||
// Master CK GP2 <-> GP10 Slave CK
|
||||
// Master TX GP3 <-> GP8 Slave RX
|
||||
|
||||
SPISettings spisettings(1000000, MSBFIRST, SPI_MODE0);
|
||||
|
||||
// Core 0 will be SPI master
|
||||
void setup() {
|
||||
SPI.setRX(0);
|
||||
SPI.setCS(1);
|
||||
SPI.setSCK(2);
|
||||
SPI.setTX(3);
|
||||
SPI.begin(true);
|
||||
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
int transmits = 0;
|
||||
void loop() {
|
||||
char msg[42];
|
||||
int loops = 0;
|
||||
memset(msg, 0, sizeof(msg));
|
||||
sprintf(msg, "What's up? This is transmission %d", transmits);
|
||||
Serial.printf("\n\nM-SEND: '%s'\n", msg);
|
||||
SPI.beginTransaction(spisettings);
|
||||
SPI.transferAsync(msg, msg, sizeof(msg));
|
||||
while (!SPI.finishedAsync()) {
|
||||
loops++;
|
||||
}
|
||||
SPI.endTransaction();
|
||||
Serial.printf("M-RECV: '%s', idle loops %d\n", msg, loops);
|
||||
transmits++;
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
// Core 1 will be SPI slave
|
||||
|
||||
volatile bool recvBuffReady = false;
|
||||
char recvBuff[42] = "";
|
||||
int recvIdx = 0;
|
||||
void recvCallback(uint8_t *data, size_t len) {
|
||||
memcpy(recvBuff + recvIdx, data, len);
|
||||
recvIdx += len;
|
||||
if (recvIdx == sizeof(recvBuff)) {
|
||||
recvBuffReady = true;
|
||||
recvIdx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int sendcbs = 0;
|
||||
// Note that the buffer needs to be long lived, the SPISlave doesn't copy it. So no local stack variables, only globals or heap(malloc/new) allocations.
|
||||
char sendBuff[42];
|
||||
void sentCallback() {
|
||||
memset(sendBuff, 0, sizeof(sendBuff));
|
||||
sprintf(sendBuff, "Slave to Master Xmission %d", sendcbs++);
|
||||
SPISlave1.setData((uint8_t*)sendBuff, sizeof(sendBuff));
|
||||
}
|
||||
|
||||
// Note that we use SPISlave1 here **not** because we're running on
|
||||
// Core 1, but because SPI0 is being used already. You can use
|
||||
// SPISlave or SPISlave1 on any core.
|
||||
void setup1() {
|
||||
SPISlave1.setRX(8);
|
||||
SPISlave1.setCS(9);
|
||||
SPISlave1.setSCK(10);
|
||||
SPISlave1.setTX(11);
|
||||
// Ensure we start with something to send...
|
||||
sentCallback();
|
||||
// Hook our callbacks into the slave
|
||||
SPISlave1.onDataRecv(recvCallback);
|
||||
SPISlave1.onDataSent(sentCallback);
|
||||
SPISlave1.begin(spisettings);
|
||||
delay(3000);
|
||||
Serial.println("S-INFO: SPISlave started");
|
||||
}
|
||||
|
||||
void loop1() {
|
||||
if (recvBuffReady) {
|
||||
Serial.printf("S-RECV: '%s'\n", recvBuff);
|
||||
recvBuffReady = false;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,9 @@ setRX KEYWORD2
|
||||
setTX KEYWORD2
|
||||
setSCK KEYWORD2
|
||||
setCS KEYWORD2
|
||||
transferAsync KEYWORD2
|
||||
finishedAsync KEYWORD2
|
||||
abortAsync KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
#include "SPI.h"
|
||||
#include <hardware/dma.h>
|
||||
#include <hardware/spi.h>
|
||||
#include <hardware/gpio.h>
|
||||
#include <hardware/structs/iobank0.h>
|
||||
@@ -218,7 +219,7 @@ void SPIClassRP2040::beginTransaction(SPISettings settings) {
|
||||
void SPIClassRP2040::endTransaction(void) {
|
||||
noInterrupts(); // Avoid race condition so the GPIO IRQs won't come back until all state is restored
|
||||
DEBUGSPI("SPI::endTransaction()\n");
|
||||
// Re-enablke IRQs
|
||||
// Re-enable IRQs
|
||||
for (auto entry : _usingIRQs) {
|
||||
int gpio = entry.first;
|
||||
int mode = entry.second;
|
||||
@@ -230,6 +231,103 @@ void SPIClassRP2040::endTransaction(void) {
|
||||
interrupts();
|
||||
}
|
||||
|
||||
bool SPIClassRP2040::transferAsync(const void *send, void *recv, size_t bytes) {
|
||||
DEBUGSPI("SPI::transferAsync(%p, %p, %d)\n", send, recv, bytes);
|
||||
const uint8_t *txbuff = reinterpret_cast<const uint8_t *>(send);
|
||||
uint8_t *rxbuff = reinterpret_cast<uint8_t *>(recv);
|
||||
_dummy = 0xffffffff;
|
||||
|
||||
if (!_initted || (!send && !recv)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_channelDMA = dma_claim_unused_channel(false);
|
||||
if (_channelDMA == -1) {
|
||||
return false;
|
||||
}
|
||||
_channelSendDMA = dma_claim_unused_channel(false);
|
||||
if (_channelSendDMA == -1) {
|
||||
dma_channel_unclaim(_channelDMA);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (send && (_spis.getBitOrder() != MSBFIRST)) {
|
||||
_dmaBuffer = (uint8_t *)malloc(bytes);
|
||||
if (!_dmaBuffer) {
|
||||
dma_channel_unclaim(_channelDMA);
|
||||
dma_channel_unclaim(_channelSendDMA);
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < bytes; i++) {
|
||||
_dmaBuffer[i] = reverseByte(txbuff[i]);
|
||||
}
|
||||
}
|
||||
_dmaBytes = bytes;
|
||||
_rxFinalBuffer = rxbuff;
|
||||
|
||||
hw_write_masked(&spi_get_hw(_spi)->cr0, (8 - 1) << SPI_SSPCR0_DSS_LSB, SPI_SSPCR0_DSS_BITS); // Fast set to 8-bits
|
||||
|
||||
dma_channel_config c = dma_channel_get_default_config(_channelSendDMA);
|
||||
channel_config_set_transfer_data_size(&c, DMA_SIZE_8); // 8b transfers into SPI FIFO
|
||||
channel_config_set_read_increment(&c, send ? true : false); // Reading incrementing addresses
|
||||
channel_config_set_write_increment(&c, false); // Writing to the same FIFO address
|
||||
channel_config_set_dreq(&c, spi_get_dreq(_spi, true)); // Wait for the TX FIFO specified
|
||||
channel_config_set_chain_to(&c, _channelSendDMA); // No chaining
|
||||
channel_config_set_irq_quiet(&c, true); // No need for IRQ
|
||||
dma_channel_configure(_channelSendDMA, &c, &spi_get_hw(_spi)->dr, !send ? (uint8_t *)&_dummy : (_spis.getBitOrder() != MSBFIRST ? _dmaBuffer : txbuff), bytes, false);
|
||||
|
||||
c = dma_channel_get_default_config(_channelDMA);
|
||||
channel_config_set_transfer_data_size(&c, DMA_SIZE_8); // 8b transfers into SPI FIFO
|
||||
channel_config_set_read_increment(&c, false); // Reading same FIFO address
|
||||
channel_config_set_write_increment(&c, recv ? true : false); // Writing to the buffer
|
||||
channel_config_set_dreq(&c, spi_get_dreq(_spi, false)); // Wait for the RX FIFO specified
|
||||
channel_config_set_chain_to(&c, _channelDMA); // No chaining
|
||||
channel_config_set_irq_quiet(&c, true); // No need for IRQ
|
||||
dma_channel_configure(_channelDMA, &c, !recv ? (uint8_t *)&_dummy : rxbuff, &spi_get_hw(_spi)->dr, bytes, false);
|
||||
|
||||
spi_get_hw(_spi)->dmacr = 1 | (1 << 1); // TDMAE | RDMAE
|
||||
|
||||
dma_channel_start(_channelDMA);
|
||||
dma_channel_start(_channelSendDMA);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SPIClassRP2040::finishedAsync() {
|
||||
if (!_initted) {
|
||||
return true;
|
||||
}
|
||||
if (dma_channel_is_busy(_channelDMA) || (spi_get_hw(_spi)->sr & SPI_SSPSR_BSY_BITS)) {
|
||||
return false;
|
||||
}
|
||||
dma_channel_cleanup(_channelDMA);
|
||||
dma_channel_unclaim(_channelDMA);
|
||||
dma_channel_cleanup(_channelSendDMA);
|
||||
dma_channel_unclaim(_channelSendDMA);
|
||||
spi_get_hw(_spi)->dmacr = 0;
|
||||
if (_spis.getBitOrder() != MSBFIRST) {
|
||||
for (int i = 0; i < _dmaBytes; i++) {
|
||||
_rxFinalBuffer[i] = reverseByte(_rxFinalBuffer[i]);
|
||||
}
|
||||
free(_dmaBuffer);
|
||||
_dmaBuffer = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SPIClassRP2040::abortAsync() {
|
||||
if (!_initted) {
|
||||
return;
|
||||
}
|
||||
dma_channel_cleanup(_channelDMA);
|
||||
dma_channel_unclaim(_channelDMA);
|
||||
dma_channel_cleanup(_channelSendDMA);
|
||||
dma_channel_unclaim(_channelSendDMA);
|
||||
spi_get_hw(_spi)->dmacr = 0;
|
||||
free(_dmaBuffer);
|
||||
_dmaBuffer = nullptr;
|
||||
}
|
||||
|
||||
|
||||
bool SPIClassRP2040::setRX(pin_size_t pin) {
|
||||
constexpr uint32_t valid[2] = { __bitset({0, 4, 16, 20}) /* SPI0 */,
|
||||
__bitset({8, 12, 24, 28}) /* SPI1 */
|
||||
|
||||
@@ -39,15 +39,28 @@ public:
|
||||
// Sends one buffer and receives into another, much faster! can set rx or txbuf to nullptr
|
||||
void transfer(const void *txbuf, void *rxbuf, size_t count) override;
|
||||
|
||||
// DMA/asynchronous transfers. Do not combime with synchronous runs or bad stuff will happen
|
||||
// All buffers must be valid for entire DMA and not touched until `finished()` returns true.
|
||||
bool transferAsync(const void *send, void *recv, size_t bytes);
|
||||
bool finishedAsync(); // Call to check if the async operations is completed and the buffer can be reused/read
|
||||
void abortAsync(); // Cancel an outstanding async operation
|
||||
|
||||
|
||||
// Call before/after every complete transaction
|
||||
void beginTransaction(SPISettings settings) override;
|
||||
void endTransaction(void) override;
|
||||
|
||||
// Assign pins, call before begin()
|
||||
bool setRX(pin_size_t pin);
|
||||
inline bool setMISO(pin_size_t pin) {
|
||||
return setRX(pin);
|
||||
}
|
||||
bool setCS(pin_size_t pin);
|
||||
bool setSCK(pin_size_t pin);
|
||||
bool setTX(pin_size_t pin);
|
||||
inline bool setMOSI(pin_size_t pin) {
|
||||
return setTX(pin);
|
||||
}
|
||||
|
||||
// Call once to init/deinit SPI class, select pins, etc.
|
||||
virtual void begin() override {
|
||||
@@ -86,6 +99,14 @@ private:
|
||||
bool _initted; // Transaction begun
|
||||
|
||||
std::map<int, int> _usingIRQs;
|
||||
|
||||
// DMA
|
||||
int _channelDMA;
|
||||
int _channelSendDMA;
|
||||
uint8_t *_dmaBuffer = nullptr;
|
||||
int _dmaBytes;
|
||||
uint8_t *_rxFinalBuffer;
|
||||
uint32_t _dummy;
|
||||
};
|
||||
|
||||
extern SPIClassRP2040 SPI;
|
||||
|
||||
@@ -20,6 +20,13 @@
|
||||
|
||||
#include "SerialBT.h"
|
||||
#include <CoreMutex.h>
|
||||
#define CCALLBACKNAME _SERIALBTCB
|
||||
#include <ctocppcallback.h>
|
||||
|
||||
#define PACKETHANDLERCB(class, cbFcn) \
|
||||
(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), \
|
||||
static_cast<btstack_packet_handler_t>(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
|
||||
|
||||
|
||||
bool SerialBT_::setFIFOSize(size_t size) {
|
||||
if (!size || _running) {
|
||||
@@ -48,7 +55,7 @@ void SerialBT_::begin(unsigned long baud, uint16_t config) {
|
||||
_reader = 0;
|
||||
|
||||
// register for HCI events
|
||||
_hci_event_callback_registration.callback = &SerialBT_::PacketHandlerWrapper;
|
||||
_hci_event_callback_registration.callback = PACKETHANDLERCB(SerialBT_, packetHandler);
|
||||
hci_add_event_handler(&_hci_event_callback_registration);
|
||||
|
||||
l2cap_init();
|
||||
@@ -59,7 +66,7 @@ void SerialBT_::begin(unsigned long baud, uint16_t config) {
|
||||
#endif
|
||||
|
||||
rfcomm_init();
|
||||
rfcomm_register_service(SerialBT_::PacketHandlerWrapper, RFCOMM_SERVER_CHANNEL, 0xffff); // reserved channel, mtu limited by l2cap
|
||||
rfcomm_register_service(PACKETHANDLERCB(SerialBT_, packetHandler), RFCOMM_SERVER_CHANNEL, 0xffff); // reserved channel, mtu limited by l2cap
|
||||
|
||||
// init SDP, create record for SPP and register with SDP
|
||||
sdp_init();
|
||||
@@ -84,9 +91,9 @@ void SerialBT_::end() {
|
||||
_running = false;
|
||||
|
||||
hci_power_control(HCI_POWER_OFF);
|
||||
lockBluetooth();
|
||||
__lockBluetooth();
|
||||
delete[] _queue;
|
||||
unlockBluetooth();
|
||||
__unlockBluetooth();
|
||||
}
|
||||
|
||||
int SerialBT_::peek() {
|
||||
@@ -121,10 +128,10 @@ bool SerialBT_::overflow() {
|
||||
return false;
|
||||
}
|
||||
|
||||
lockBluetooth();
|
||||
__lockBluetooth();
|
||||
bool ovf = _overflow;
|
||||
_overflow = false;
|
||||
unlockBluetooth();
|
||||
__unlockBluetooth();
|
||||
|
||||
return ovf;
|
||||
}
|
||||
@@ -160,9 +167,9 @@ size_t SerialBT_::write(const uint8_t *p, size_t len) {
|
||||
}
|
||||
_writeBuff = p;
|
||||
_writeLen = len;
|
||||
lockBluetooth();
|
||||
__lockBluetooth();
|
||||
rfcomm_request_can_send_now_event(_channelID);
|
||||
unlockBluetooth();
|
||||
__unlockBluetooth();
|
||||
while (_connected && _writeLen) {
|
||||
/* noop busy wait */
|
||||
}
|
||||
|
||||
@@ -60,26 +60,13 @@ public:
|
||||
(void) unused;
|
||||
}
|
||||
|
||||
static void PacketHandlerWrapper(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t packet_size) {
|
||||
SerialBT.packetHandler(packet_type, channel, packet, packet_size);
|
||||
}
|
||||
|
||||
void packetHandler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
|
||||
static void lockBluetooth() {
|
||||
async_context_acquire_lock_blocking(cyw43_arch_async_context());
|
||||
}
|
||||
|
||||
static void unlockBluetooth() {
|
||||
async_context_release_lock(cyw43_arch_async_context());
|
||||
}
|
||||
|
||||
private:
|
||||
bool _running = false;
|
||||
mutex_t _mutex;
|
||||
bool _overflow = false;
|
||||
volatile bool _connected = false;
|
||||
|
||||
void packetHandler(uint8_t type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
|
||||
// Lockless, IRQ-handled circular queue
|
||||
uint32_t _writer;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// Simple asynchronous I2C master and slave demo - Earle F. Philhower, III
|
||||
// Released into the public domain.
|
||||
//
|
||||
// Using both onboard I2C interfaces, have one master and one slave
|
||||
// and send data both ways between them.
|
||||
//
|
||||
// Uses the async Wire calls to allow applications to do other work while
|
||||
// I2C transactions are ongoing.
|
||||
//
|
||||
// To run, connect GPIO0 to GPIO2, GPIO1 to GPIO3 on a single Pico
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(5000);
|
||||
Wire.setSDA(0);
|
||||
Wire.setSCL(1);
|
||||
Wire.begin();
|
||||
Wire1.setSDA(2);
|
||||
Wire1.setSCL(3);
|
||||
Wire1.begin(0x30);
|
||||
Wire1.onReceive(recv);
|
||||
Wire1.onRequest(req);
|
||||
}
|
||||
|
||||
static char buff[100];
|
||||
|
||||
void loop() {
|
||||
static int p;
|
||||
char b[90];
|
||||
int loops;
|
||||
|
||||
// Write a value over I2C to the slave
|
||||
Serial.println("\n\nSending...");
|
||||
sprintf(b, "This buffer is larger than the I2C FIFO by a whole lot, Pass #%d", p++);
|
||||
Wire.writeAsync(0x30, b, strlen(b), true);
|
||||
|
||||
// A real application would go do some useful work here and check the
|
||||
// finishedAsync value when it needs the I2S operation to be completed
|
||||
// Here' we'll just increment a counter to show how much work could be done...
|
||||
loops = 0;
|
||||
while (!Wire.finishedAsync()) {
|
||||
loops++;
|
||||
}
|
||||
Serial.printf("Write idle loops: %d\n", loops);
|
||||
|
||||
// Ensure the slave processing is done and print it out
|
||||
delay(1000);
|
||||
Serial.printf("buff: '%s'\n", buff);
|
||||
|
||||
Serial.printf("Receiving...\n");
|
||||
|
||||
// Read from the slave and print out
|
||||
bzero(b, sizeof(b));
|
||||
Wire.readAsync(0x30, b, 73, true);
|
||||
loops = 0;
|
||||
while (!Wire.finishedAsync()) {
|
||||
loops++;
|
||||
}
|
||||
Serial.printf("Read idle loops: %d\n", loops);
|
||||
Serial.print("recv: '");
|
||||
for (int i = 0; i < 73; i++) {
|
||||
Serial.print(b[i]);
|
||||
}
|
||||
Serial.println("'");
|
||||
delay(1000);
|
||||
}
|
||||
|
||||
// These are called in an **INTERRUPT CONTEXT** which means NO serial port
|
||||
// access (i.e. Serial.print is illegal) and no memory allocations, etc.
|
||||
|
||||
// Called when the I2C slave gets written to
|
||||
void recv(int len) {
|
||||
int i;
|
||||
// Just stuff the sent bytes into a global the main routine can pick up and use
|
||||
for (i = 0; i < len; i++) {
|
||||
buff[i] = Wire1.read();
|
||||
}
|
||||
buff[i] = 0;
|
||||
}
|
||||
|
||||
// Called when the I2C slave is read from
|
||||
void req() {
|
||||
static int ctr = 765;
|
||||
char buff[100];
|
||||
// Return a simple incrementing hex value
|
||||
sprintf(buff, "Slave responds with a message that's longer than FIFO as well, id #%06X", (ctr++) % 65535);
|
||||
Wire1.write(buff, 73);
|
||||
}
|
||||
@@ -21,6 +21,10 @@ onReceive KEYWORD2
|
||||
onRequest KEYWORD2
|
||||
setSDA KEYWORD2
|
||||
setSCL KEYWORD2
|
||||
writeAsync KEYWORD2
|
||||
readAsync KEYWORD2
|
||||
finishedAsync KEYWORD2
|
||||
abortAsync KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Instances (KEYWORD2)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <hardware/dma.h>
|
||||
#include <hardware/gpio.h>
|
||||
#include <hardware/i2c.h>
|
||||
#include <hardware/irq.h>
|
||||
@@ -441,6 +442,176 @@ void TwoWire::flush(void) {
|
||||
}
|
||||
|
||||
|
||||
// DMA/asynchronous transfers. Do not combime with synchronous runs or bad stuff will happen
|
||||
// All buffers must be valid for entire DMA and not touched until `finished()` returns true.
|
||||
bool TwoWire::writeAsync(uint8_t address, const void *buffer, size_t bytes, bool sendStop) {
|
||||
if (!_running || _txBegun || _rxBegun) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We need to expand the data to include side-channel start/stop bits for the I2C FIFO
|
||||
_dmaBuffer = (uint16_t*)malloc(bytes * 2);
|
||||
if (!_dmaBuffer) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t *srcBuff = (const uint8_t *)buffer;
|
||||
for (size_t i = 0; i < bytes; i++) {
|
||||
bool first = i == 0;
|
||||
bool last = i == bytes - 1;
|
||||
_dmaBuffer[i] = bool_to_bit(first && _i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB | bool_to_bit(last && sendStop) << I2C_IC_DATA_CMD_STOP_LSB | srcBuff[i];
|
||||
}
|
||||
|
||||
_channelDMA = dma_claim_unused_channel(false);
|
||||
if (_channelDMA == -1) {
|
||||
free(_dmaBuffer);
|
||||
_dmaBuffer = nullptr;
|
||||
return false;
|
||||
}
|
||||
dma_channel_config c = dma_channel_get_default_config(_channelDMA);
|
||||
channel_config_set_transfer_data_size(&c, DMA_SIZE_16); // 16b transfers into I2C FIFO
|
||||
channel_config_set_read_increment(&c, true); // Reading incrementing addresses
|
||||
channel_config_set_write_increment(&c, false); // Writing to the same FIFO address
|
||||
channel_config_set_dreq(&c, i2c_get_dreq(_i2c, true)); // Wait for the TX FIFO specified
|
||||
channel_config_set_chain_to(&c, _channelDMA); // No chaining
|
||||
channel_config_set_irq_quiet(&c, true); // No need for IRQ
|
||||
dma_channel_configure(_channelDMA, &c, &_i2c->hw->data_cmd, _dmaBuffer, bytes, false);
|
||||
|
||||
_i2c->hw->enable = 0;
|
||||
_i2c->hw->tar = address;
|
||||
_i2c->hw->dma_cr = 1 << 1; // TDMAE
|
||||
_i2c->hw->enable = 1;
|
||||
_i2c->restart_on_next = !sendStop;
|
||||
dma_channel_start(_channelDMA);
|
||||
_txBegun = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TwoWire::readAsync(uint8_t address, void *buffer, size_t bytes, bool sendStop) {
|
||||
if (!_running || _txBegun || _rxBegun) {
|
||||
return false;
|
||||
}
|
||||
_channelDMA = dma_claim_unused_channel(false);
|
||||
if (_channelDMA == -1) {
|
||||
return false;
|
||||
}
|
||||
_channelSendDMA = dma_claim_unused_channel(false);
|
||||
if (_channelSendDMA == -1) {
|
||||
dma_channel_unclaim(_channelDMA);
|
||||
return false;
|
||||
}
|
||||
|
||||
// We need to expand the data to include side-channel start/stop bits for the I2C FIFO
|
||||
_dmaBuffer = (uint16_t*)malloc(bytes * 2);
|
||||
if (!_dmaBuffer) {
|
||||
return false;
|
||||
}
|
||||
// We need to write one entry for every byte we want to read for the sideband info
|
||||
_dmaSendBuffer = (uint16_t *)malloc(bytes * 2);
|
||||
if (!_dmaSendBuffer) {
|
||||
free(_dmaBuffer);
|
||||
_dmaBuffer = nullptr;
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < bytes; i++) {
|
||||
bool first = i == 0;
|
||||
bool last = i == bytes - 1;
|
||||
_dmaSendBuffer[i] =
|
||||
bool_to_bit(first && _i2c->restart_on_next) << I2C_IC_DATA_CMD_RESTART_LSB |
|
||||
bool_to_bit(last && sendStop) << I2C_IC_DATA_CMD_STOP_LSB |
|
||||
I2C_IC_DATA_CMD_CMD_BITS; // -> 1 for read
|
||||
}
|
||||
|
||||
_dmaBytes = bytes;
|
||||
_rxFinalBuffer = (uint8_t *)buffer;
|
||||
|
||||
dma_channel_config c = dma_channel_get_default_config(_channelSendDMA);
|
||||
channel_config_set_transfer_data_size(&c, DMA_SIZE_16); // 16b transfers into I2C FIFO
|
||||
channel_config_set_read_increment(&c, true); // Reading incrementing addresses
|
||||
channel_config_set_write_increment(&c, false); // Writing to the same FIFO address
|
||||
channel_config_set_dreq(&c, i2c_get_dreq(_i2c, true)); // Wait for the TX FIFO specified
|
||||
channel_config_set_chain_to(&c, _channelSendDMA); // No chaining
|
||||
channel_config_set_irq_quiet(&c, true); // No need for IRQ
|
||||
dma_channel_configure(_channelSendDMA, &c, &_i2c->hw->data_cmd, _dmaSendBuffer, bytes, false);
|
||||
|
||||
c = dma_channel_get_default_config(_channelDMA);
|
||||
channel_config_set_transfer_data_size(&c, DMA_SIZE_16); // 16b transfers into I2C FIFO
|
||||
channel_config_set_read_increment(&c, false); // Reading same FIFO address
|
||||
channel_config_set_write_increment(&c, true); // Writing to the buffer
|
||||
channel_config_set_dreq(&c, i2c_get_dreq(_i2c, false)); // Wait for the RX FIFO specified
|
||||
channel_config_set_chain_to(&c, _channelDMA); // No chaining
|
||||
channel_config_set_irq_quiet(&c, true); // No need for IRQ
|
||||
dma_channel_configure(_channelDMA, &c, _dmaBuffer, &_i2c->hw->data_cmd, bytes, false);
|
||||
|
||||
_i2c->hw->enable = 0;
|
||||
_i2c->hw->tar = address;
|
||||
_i2c->hw->dma_cr = 1 | (1 << 1); // TDMAE | RDMAE
|
||||
_i2c->hw->enable = 1;
|
||||
_i2c->restart_on_next = !sendStop;
|
||||
dma_channel_start(_channelDMA);
|
||||
dma_channel_start(_channelSendDMA);
|
||||
_rxBegun = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TwoWire::finishedAsync() {
|
||||
if (!_running || !_dmaBuffer) {
|
||||
return true;
|
||||
}
|
||||
if (dma_channel_is_busy(_channelDMA)) {
|
||||
return false;
|
||||
}
|
||||
if (_txBegun) {
|
||||
if (_i2c->hw->txflr) {
|
||||
return false;
|
||||
}
|
||||
dma_channel_cleanup(_channelDMA);
|
||||
dma_channel_unclaim(_channelDMA);
|
||||
_i2c->hw->dma_cr = 0;
|
||||
free(_dmaBuffer);
|
||||
_dmaBuffer = nullptr;
|
||||
_txBegun = false;
|
||||
return true;
|
||||
} else if (_rxBegun) {
|
||||
// For RX, don't care if more data in FIFO. The DMA read is done for the size requested
|
||||
dma_channel_cleanup(_channelDMA);
|
||||
dma_channel_unclaim(_channelDMA);
|
||||
dma_channel_cleanup(_channelSendDMA);
|
||||
dma_channel_unclaim(_channelSendDMA);
|
||||
_i2c->hw->dma_cr = 0;
|
||||
// Need to convert from x16 to x8, strip off the status bits
|
||||
for (auto i = 0; i < _dmaBytes; i++) {
|
||||
_rxFinalBuffer[i] = _dmaBuffer[i] & 0xff;
|
||||
}
|
||||
free(_dmaBuffer);
|
||||
_dmaBuffer = nullptr;
|
||||
free(_dmaSendBuffer);
|
||||
_dmaSendBuffer = nullptr;
|
||||
_rxBegun = false;
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TwoWire::abortAsync() {
|
||||
if (!_running || !_dmaBuffer) {
|
||||
return;
|
||||
}
|
||||
dma_channel_cleanup(_channelDMA);
|
||||
dma_channel_unclaim(_channelDMA);
|
||||
if (_rxBegun) {
|
||||
dma_channel_cleanup(_channelSendDMA);
|
||||
dma_channel_unclaim(_channelSendDMA);
|
||||
}
|
||||
_i2c->hw->dma_cr = 0;
|
||||
free(_dmaBuffer);
|
||||
_dmaBuffer = nullptr;
|
||||
free(_dmaSendBuffer);
|
||||
_dmaSendBuffer = nullptr;
|
||||
_rxBegun = false;
|
||||
_txBegun = false;
|
||||
}
|
||||
|
||||
|
||||
void TwoWire::onReceive(void(*function)(int)) {
|
||||
_onReceiveCallback = function;
|
||||
}
|
||||
|
||||
@@ -82,6 +82,13 @@ public:
|
||||
}
|
||||
using Print::write;
|
||||
|
||||
// DMA/asynchronous transfers. Do not combime with synchronous runs or bad stuff will happen
|
||||
// All buffers must be valid for entire DMA and not touched until `finished()` returns true.
|
||||
bool writeAsync(uint8_t address, const void *buffer, size_t bytes, bool sendStop);
|
||||
bool readAsync(uint8_t address, void *buffer, size_t bytes, bool sendStop);
|
||||
bool finishedAsync(); // Call to check if the async operations is completed and the buffer can be reused/read
|
||||
void abortAsync(); // Cancel an outstanding async I2C operation
|
||||
|
||||
void setTimeout(uint32_t timeout = 25, bool reset_with_timeout = false); // sets the maximum number of milliseconds to wait
|
||||
bool getTimeoutFlag(void);
|
||||
void clearTimeoutFlag(void);
|
||||
@@ -116,6 +123,15 @@ private:
|
||||
|
||||
// TWI clock frequency
|
||||
static const uint32_t TWI_CLOCK = 100000;
|
||||
|
||||
// DMA
|
||||
bool _rxBegun = false;
|
||||
int _channelDMA;
|
||||
int _channelSendDMA;
|
||||
uint16_t *_dmaBuffer = nullptr;
|
||||
uint16_t *_dmaSendBuffer = nullptr;
|
||||
int _dmaBytes;
|
||||
uint8_t *_rxFinalBuffer;
|
||||
};
|
||||
|
||||
extern TwoWire Wire;
|
||||
|
||||
@@ -104,11 +104,14 @@ void CYW43::end() {
|
||||
_netif = nullptr;
|
||||
cyw43_deinit(&cyw43_state);
|
||||
}
|
||||
|
||||
int fails = 0;
|
||||
int calls = 0;
|
||||
uint16_t CYW43::sendFrame(const uint8_t* data, uint16_t datalen) {
|
||||
calls++;
|
||||
if (0 == cyw43_send_ethernet(_self, _itf, datalen, data, false)) {
|
||||
return datalen;
|
||||
}
|
||||
fails++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -205,8 +205,8 @@ u8_t LwipIntfDev<RawDev>::_pingCB(void *arg, struct raw_pcb *pcb, struct pbuf *p
|
||||
(void) addr;
|
||||
LwipIntfDev<RawDev> *w = (LwipIntfDev<RawDev> *)arg;
|
||||
struct icmp_echo_hdr *iecho;
|
||||
if (pbuf_header(p, -20) == 0) {
|
||||
iecho = (struct icmp_echo_hdr *)p->payload;
|
||||
if (p->len > 20) {
|
||||
iecho = (struct icmp_echo_hdr *)((uint8_t*)p->payload + 20);
|
||||
if ((iecho->id == w->_ping_id) && (iecho->seqno == htons(w->_ping_seq_num))) {
|
||||
w->_ping_ttl = pcb->ttl;
|
||||
pbuf_free(p);
|
||||
@@ -310,6 +310,11 @@ bool LwipIntfDev<RawDev>::config(IPAddress local_ip, IPAddress dns) {
|
||||
extern char wifi_station_hostname[];
|
||||
template<class RawDev>
|
||||
bool LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu) {
|
||||
if (_started) {
|
||||
// ERROR - Need to ::end before calling ::begin again
|
||||
return false;
|
||||
}
|
||||
|
||||
lwip_init();
|
||||
__startEthernetContext();
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "framework-arduinopico",
|
||||
"version": "1.30801.0",
|
||||
"version": "1.30900.0",
|
||||
"description": "Arduino Wiring-based Framework (RPi Pico RP2040)",
|
||||
"keywords": [
|
||||
"framework",
|
||||
|
||||
@@ -81,7 +81,7 @@ rm exclude.txt
|
||||
|
||||
# Get previous release name
|
||||
curl --silent https://api.github.com/repos/earlephilhower/arduino-pico/releases > releases.json
|
||||
# Previous final release (prerelase == false)
|
||||
# Previous final release (prerelease == false)
|
||||
prev_release=$(jq -r '. | map(select(.draft == false and .prerelease == false)) | sort_by(.created_at | - fromdateiso8601) | .[0].tag_name' releases.json)
|
||||
# Previous release (possibly a pre-release)
|
||||
prev_any_release=$(jq -r '. | map(select(.draft == false)) | sort_by(.created_at | - fromdateiso8601) | .[0].tag_name' releases.json)
|
||||
|
||||
@@ -155,6 +155,9 @@
|
||||
{
|
||||
"name": "Neko Systems BL2040 Mini"
|
||||
},
|
||||
{
|
||||
"name": "Newsan Archi"
|
||||
},
|
||||
{
|
||||
"name": "nullbits Bit-C PRO"
|
||||
},
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
# https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5---3rd-party-Hardware-specification
|
||||
|
||||
name=Raspberry Pi RP2040 Boards
|
||||
version=3.8.1
|
||||
version=3.9.0
|
||||
|
||||
# Required discoveries and monitors
|
||||
# ---------------------------------
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S ./libraries/SingleF
|
||||
./libraries/JoystickBLE ./libraries/KeyboardBLE ./libraries/MouseBLE \
|
||||
./libraries/lwIP_w5500 ./libraries/lwIP_w5100 ./libraries/lwIP_enc28j60 \
|
||||
./libraries/SPISlave ./libraries/lwIP_ESPHost ./libraries/FatFS\
|
||||
./libraries/FatFSUSB; do
|
||||
./libraries/FatFSUSB ./libraries/BluetoothAudio; do
|
||||
find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" \) -a \! -path '*api*' -exec astyle --suffix=none --options=./tests/astyle_core.conf \{\} \;
|
||||
find $dir -type f -name "*.ino" -exec astyle --suffix=none --options=./tests/astyle_examples.conf \{\} \;
|
||||
done
|
||||
|
||||
+2
-2
@@ -65,9 +65,9 @@ def compile(tmp_dir, sketch, cache, tools_dir, hardware_dir, ide_path, f, args):
|
||||
'dbgport={dbgport},' \
|
||||
'dbglvl={dbglvl},' \
|
||||
'usbstack={usbstack}'.format(**vars(args))
|
||||
if ("libraries/WiFi" in sketch) or ("/ArduinoOTA" in sketch) or ("/HTTPClient" in sketch) or ('/HTTPUpdate' in sketch) or ('/WebServer' in sketch) or ('/DNSServer' in sketch) or ('/BT' in sketch) or ('/BLE' in sketch):
|
||||
if ("libraries/WiFi" in sketch) or ("/ArduinoOTA" in sketch) or ("/HTTPClient" in sketch) or ('/HTTPUpdate' in sketch) or ('/WebServer' in sketch) or ('/DNSServer' in sketch) or ('/BT' in sketch) or ('/BLE' in sketch) or ('/Bluetooth' in sketch):
|
||||
fqbn = fqbn.replace("rpipico", "rpipicow")
|
||||
if ('/BT' in sketch) or ('/BLE' in sketch):
|
||||
if ('/BT' in sketch) or ('/BLE' in sketch) or ('/Bluetooth' in sketch):
|
||||
fqbn = fqbn + ",ipbtstack=ipv4btcble"
|
||||
cmd += [fqbn]
|
||||
cmd += ['-built-in-libraries', ide_path + '/libraries']
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"earlephilhower": {
|
||||
"boot2_source": "boot2_generic_03h_4_padded_checksum.S",
|
||||
"usb_vid": "0x2E8A",
|
||||
"usb_pid": "0x1043"
|
||||
}
|
||||
},
|
||||
"core": "earlephilhower",
|
||||
"cpu": "cortex-m0plus",
|
||||
"extra_flags": "-D ARDUINO_NEWSAN_ARCHI -DARDUINO_ARCH_RP2040 -DUSBD_MAX_POWER_MA=250",
|
||||
"f_cpu": "133000000L",
|
||||
"hwids": [
|
||||
[
|
||||
"0x2E8A",
|
||||
"0x00C0"
|
||||
],
|
||||
[
|
||||
"0x2E8A",
|
||||
"0x1043"
|
||||
]
|
||||
],
|
||||
"mcu": "rp2040",
|
||||
"variant": "newsan_archi"
|
||||
},
|
||||
"debug": {
|
||||
"jlink_device": "RP2040_M0_0",
|
||||
"openocd_target": "rp2040.cfg",
|
||||
"svd_path": "rp2040.svd"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "Archi",
|
||||
"upload": {
|
||||
"maximum_ram_size": 270336,
|
||||
"maximum_size": 4194304,
|
||||
"require_upload_port": true,
|
||||
"native_usb": true,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": false,
|
||||
"protocol": "picotool",
|
||||
"protocols": [
|
||||
"blackmagic",
|
||||
"cmsis-dap",
|
||||
"jlink",
|
||||
"raspberrypi-swd",
|
||||
"picotool",
|
||||
"picoprobe",
|
||||
"pico-debug"
|
||||
]
|
||||
},
|
||||
"url": "https://archikids.com.ar/",
|
||||
"vendor": "Newsan"
|
||||
}
|
||||
@@ -139,6 +139,8 @@ set(picow_bt_link_libraries
|
||||
pico_btstack_cyw43
|
||||
pico_btstack_ble
|
||||
pico_btstack_classic
|
||||
pico_btstack_sbc_encoder
|
||||
pico_btstack_sbc_decoder
|
||||
)
|
||||
|
||||
target_link_libraries(picow-noipv6-btc-ble
|
||||
|
||||
@@ -38,12 +38,12 @@ extern unsigned long __lwip_rand(void);
|
||||
#define LWIP_NETIF_LINK_CALLBACK 1
|
||||
#define LWIP_NETIF_HOSTNAME 1
|
||||
#define LWIP_NETCONN 0
|
||||
#define MEM_STATS 0
|
||||
#define SYS_STATS 0
|
||||
#define MEMP_STATS 0
|
||||
#define MEM_STATS 1
|
||||
#define SYS_STATS 1
|
||||
#define MEMP_STATS 1
|
||||
#define LINK_STATS 0
|
||||
// #define ETH_PAD_SIZE 2
|
||||
#define LWIP_CHKSUM_ALGORITHM 3
|
||||
#define LWIP_CHKSUM_ALGORITHM 0
|
||||
#define LWIP_DHCP 1
|
||||
#define LWIP_IPV4 1
|
||||
#define LWIP_TCP 1
|
||||
@@ -72,6 +72,8 @@ extern void __setSystemTime(unsigned long long sec, unsigned long us);
|
||||
#define LWIP_DEBUG 1
|
||||
#define LWIP_STATS 1
|
||||
#define LWIP_STATS_DISPLAY 1
|
||||
#define MEMP_STATS 1
|
||||
#define MEM_STATS 1
|
||||
#endif
|
||||
|
||||
#define ETHARP_DEBUG LWIP_DBG_OFF
|
||||
|
||||
@@ -435,6 +435,9 @@ MakeBoard("melopero_shake_rp2040", "Melopero", "Shake RP2040", "0x2e8a", "0x1005
|
||||
# Neko Systems
|
||||
MakeBoard("nekosystems_bl2040_mini", "Neko Systems", "BL2040 Mini", "0x2e8a", "0x000a", 500, "NEKOSYSTEMS_BL2040_MINI", 4, "boot2_generic_03h_2_padded_checksum")
|
||||
|
||||
# Newsan
|
||||
MakeBoard("newsan_archi", "Newsan", "Archi", "0x2E8A", "0x1043", 250, "NEWSAN_ARCHI", 4, "boot2_generic_03h_4_padded_checksum", None, "https://archikids.com.ar/")
|
||||
|
||||
# nullbits
|
||||
MakeBoard("nullbits_bit_c_pro", "nullbits", "Bit-C PRO", "0x2e8a", "0x6e61", 500, "NULLBITS_BIT_C_PRO", 4, "boot2_w25x10cl_4_padded_checksum")
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
// Pin definitions taken from:
|
||||
// https://archikids.com.ar/documentacion
|
||||
|
||||
//Buttons
|
||||
#define PIN_BUTTON_A (23u)
|
||||
#define PIN_BUTTON_B (10u)
|
||||
#define PIN_BUTTON_C (25u)
|
||||
#define PIN_BUTTON_D (19u)
|
||||
|
||||
// NeoPixel
|
||||
#define PIN_NEOPIXEL (24u)
|
||||
#define NUM_NEOPIXEL 64
|
||||
#define PIN_RGB PIN_NEOPIXEL
|
||||
|
||||
// Buzzer
|
||||
#define PIN_BUZZER (22u)
|
||||
|
||||
// Serial
|
||||
#define PIN_SERIAL1_TX (16u)
|
||||
#define PIN_SERIAL1_RX (17u)
|
||||
|
||||
#define PIN_SERIAL2_TX (8u)
|
||||
#define PIN_SERIAL2_RX (9u)
|
||||
|
||||
// SPI
|
||||
#define PIN_SPI0_MISO (4u)
|
||||
#define PIN_SPI0_MOSI (3u)
|
||||
#define PIN_SPI0_SCK (2u)
|
||||
#define PIN_SPI0_SS (5u)
|
||||
|
||||
#define PIN_SPI1_MISO (12u)
|
||||
#define PIN_SPI1_MOSI (11u)
|
||||
#define PIN_SPI1_SCK (10u)
|
||||
#define PIN_SPI1_SS (13u)
|
||||
|
||||
// Wire
|
||||
#define PIN_WIRE0_SDA (0u)
|
||||
#define PIN_WIRE0_SCL (1u)
|
||||
|
||||
#define PIN_WIRE1_SDA (6u)
|
||||
#define PIN_WIRE1_SCL (7u)
|
||||
|
||||
#define SERIAL_HOWMANY (3u)
|
||||
#define SPI_HOWMANY (2u)
|
||||
#define WIRE_HOWMANY (2u)
|
||||
|
||||
#include "../generic/common.h"
|
||||
@@ -65,3 +65,11 @@ extern "C" void initVariant() {
|
||||
cyw43_arch_init_with_country(WIFICC);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void __lockBluetooth() {
|
||||
async_context_acquire_lock_blocking(cyw43_arch_async_context());
|
||||
}
|
||||
|
||||
extern "C" void __unlockBluetooth() {
|
||||
async_context_release_lock(cyw43_arch_async_context());
|
||||
}
|
||||
|
||||
@@ -3,7 +3,16 @@
|
||||
// Pin definitions taken from:
|
||||
// https://datasheets.raspberrypi.org/pico/pico-datasheet.pdf
|
||||
|
||||
extern bool __isPicoW;
|
||||
extern bool __isPicoW;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
extern void __lockBluetooth();
|
||||
extern void __unlockBluetooth();
|
||||
#ifdef __cplusplus
|
||||
}; // extern "C"
|
||||
#endif // __cplusplus
|
||||
|
||||
// LEDs
|
||||
#define PIN_LED (32u)
|
||||
|
||||
Reference in New Issue
Block a user