Compare commits
@@ -6,6 +6,10 @@ name: Arduino-Pico CI
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||
TRAVIS_TAG: ${{ github.ref }}
|
||||
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -20,7 +24,7 @@ jobs:
|
||||
- name: Run codespell
|
||||
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
|
||||
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
|
||||
ignore_words_list: ser,dout
|
||||
|
||||
# Consistent style
|
||||
@@ -46,10 +50,6 @@ jobs:
|
||||
./tests/restyle.sh
|
||||
# If anything changed, GIT should return an error and fail the test
|
||||
git diff --exit-code
|
||||
# - name: Check Arduino API copy is clean
|
||||
# run: |
|
||||
# git submodule update --init ./ArduinoCore-API
|
||||
# diff -r ./cores/rp2040/api ./ArduinoCore-API/api
|
||||
|
||||
# Build all examples on linux (core and Arduino IDE)
|
||||
build-linux:
|
||||
@@ -62,19 +62,17 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Cache Linux toolchain
|
||||
id: cache-linux
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./tools/dist
|
||||
key: ${{ runner.os }}-${{ hashFiles('package/package_pico_index.template.json', 'tests/common.sh') }}
|
||||
- name: Build Sketches
|
||||
env:
|
||||
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||
TRAVIS_TAG: ${{ github.ref }}
|
||||
BUILD_PARITY: custom
|
||||
mod: 6
|
||||
rem: ${{ matrix.chunk }}
|
||||
@@ -92,19 +90,17 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Cache Linux toolchain
|
||||
id: cache-linux
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./tools/dist
|
||||
key: ${{ runner.os }}-${{ hashFiles('package/package_pico_index.template.json', 'tests/common.sh') }}
|
||||
- name: Build Sketches
|
||||
env:
|
||||
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||
TRAVIS_TAG: ${{ github.ref }}
|
||||
BUILD_PARITY: custom
|
||||
run: |
|
||||
cd pico-sdk
|
||||
@@ -120,19 +116,17 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Cache Windows toolchain
|
||||
id: cache-windows
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./tools/dist
|
||||
key: ${{ runner.os }}-${{ hashFiles('package/package_pico_index.template.json', 'tests/common.sh') }}
|
||||
- name: Build Sketch
|
||||
env:
|
||||
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||
TRAVIS_TAG: ${{ github.ref }}
|
||||
WINDOWS: 1
|
||||
BUILD_PARITY: custom
|
||||
mod: 500
|
||||
@@ -150,24 +144,22 @@ jobs:
|
||||
# Single build under macOS to ensure the Mac toolchain is good.
|
||||
build-mac:
|
||||
name: Mac
|
||||
runs-on: macOS-latest
|
||||
runs-on: macOS-12
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Cache Mac toolchain
|
||||
id: cache-mac
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ./tools/dist
|
||||
key: ${{ runner.os }}-${{ hashFiles('package/package_pico_index.template.json', 'tests/common.sh') }}
|
||||
- name: Build Sketch
|
||||
env:
|
||||
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||
TRAVIS_TAG: ${{ github.ref }}
|
||||
MACOSX: 1
|
||||
BUILD_PARITY: custom
|
||||
mod: 500
|
||||
@@ -194,19 +186,21 @@ jobs:
|
||||
git submodule update --init
|
||||
cd ../..
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Cache PlatformIO
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install PlatformIO
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
|
||||
@@ -9,22 +9,22 @@ jobs:
|
||||
name: Update master JSON file
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
- name: Cache PlatformIO
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.platformio
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install PlatformIO
|
||||
|
||||
@@ -15,11 +15,11 @@ jobs:
|
||||
name: Package
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-python@v2
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Build package JSON
|
||||
|
||||
+1
-1
Submodule ArduinoCore-API updated: d955d75747...e37df85425
@@ -12,7 +12,9 @@ See https://arduino-pico.readthedocs.io/en/latest/ along with the examples for m
|
||||
# Supported Boards
|
||||
* Raspberry Pi Pico
|
||||
* Raspberry Pi Pico W
|
||||
* 0xCB Helios
|
||||
* Adafruit Feather RP2040
|
||||
* Adafruit Feather RP2040 SCORPIO
|
||||
* Adafruit ItsyBitsy RP2040
|
||||
* Adafruit KB2040
|
||||
* Adafruit Macropad RP2040
|
||||
@@ -23,7 +25,7 @@ See https://arduino-pico.readthedocs.io/en/latest/ along with the examples for m
|
||||
* BridgeTek IDM2040-7A
|
||||
* Cytron Maker Pi RP2040
|
||||
* Cytron Maker Nano RP2040
|
||||
* Degz Mizu
|
||||
* DatanoiseTV PicoADK+
|
||||
* DeRuiLab FlyBoard2040 Core
|
||||
* DFRobot Beetle RP2040
|
||||
* ElectronicCats Hunter Cat NFC
|
||||
@@ -38,12 +40,20 @@ See https://arduino-pico.readthedocs.io/en/latest/ along with the examples for m
|
||||
* Invector Labs RPICO32
|
||||
* Melopero Cookie RP2040
|
||||
* Melopero Shake RP2040
|
||||
* nullbits Bit-C PRO
|
||||
* Pimoroni PGA2040
|
||||
* Seeed XIAO RP2040
|
||||
* Solder Party RP2040 Stamp
|
||||
* SparkFun ProMicro RP2040
|
||||
* SparkFun Thing Plus RP2040
|
||||
* uPesy RP2040 DevKit
|
||||
* VCC-GND YD-RP2040
|
||||
* Viyalab Mizu RP2040
|
||||
* Waveshare RP2040 Zero
|
||||
* Waveshare RP2040 One
|
||||
* Waveshare RP2040 Plus
|
||||
* Waveshare RP2040 LCD 0.96
|
||||
* Waveshare RP2040 LCD 1.28
|
||||
* WIZnet W5100S-EVB-Pico
|
||||
* WIZnet W5500-EVB-Pico
|
||||
* WIZnet WizFi360-EVB-Pico
|
||||
@@ -160,6 +170,9 @@ The installed tools include a version of OpenOCD (in the pqt-openocd directory)
|
||||
* FreeRTOS SMP support
|
||||
* Overclocking and underclocking from the menus
|
||||
* 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)
|
||||
* Peripherals: SPI master, Wire(I2C) master/slave, dual UART, emulated EEPROM, I2S audio input, I2S audio output, Servo
|
||||
* printf (i.e. debug) output over USB serial
|
||||
|
||||
@@ -178,6 +191,9 @@ Here are some links to coverage and additional tutorials for using `arduino-pico
|
||||
* Adafruit Feather RP2040 running LCD + TMP117 - https://www.youtube.com/watch?v=fKDeqZiIwHg
|
||||
* Demonstration of Servos and I2C in Korean - https://cafe.naver.com/arduinoshield/1201
|
||||
* Home Assistant Pico W integration starter project using Arduino - https://github.com/daniloc/PicoW_HomeAssistant_Starter
|
||||
* Tutorials for the Raspberry Pi Pico / uPesy RP2040 DevKit board
|
||||
- English version: https://www.upesy.com/blogs/tutorials/best-tutorials-for-rpi-pi-pico-with-arduino-code
|
||||
- French version: https://www.upesy.fr/blogs/tutorials/best-tutorials-for-rpi-pi-pico-with-arduino-code
|
||||
|
||||
# Contributing
|
||||
If you want to contribute or have bugfixes, drop me a note at <earlephilhower@yahoo.com> or open an issue/PR here.
|
||||
|
||||
+3762
-15657
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
// Padded and checksummed version of: generated\Winbond\W25Q128JVxQ\boot2.bin by @maxgerhardt
|
||||
|
||||
.cpu cortex-m0plus
|
||||
.thumb
|
||||
|
||||
.section .boot2, "ax"
|
||||
|
||||
.byte 0xf7, 0xb5, 0x73, 0x46, 0x21, 0x22, 0x02, 0x26, 0x01, 0x93, 0x29, 0x4b, 0xc0, 0x24, 0x5a, 0x60
|
||||
.byte 0x9a, 0x68, 0x00, 0x27, 0xb2, 0x43, 0xda, 0x60, 0x9a, 0x60, 0x1a, 0x61, 0x5a, 0x61, 0x04, 0x23
|
||||
.byte 0x01, 0x25, 0x64, 0x05, 0xa7, 0x60, 0x63, 0x61, 0x22, 0x4b, 0x30, 0x00, 0x1d, 0x60, 0xe0, 0x23
|
||||
.byte 0xdb, 0x02, 0x23, 0x60, 0x35, 0x23, 0xa5, 0x60, 0x23, 0x66, 0x23, 0x66, 0x00, 0xf0, 0x4a, 0xf8
|
||||
.byte 0xc0, 0xb2, 0xb0, 0x42, 0x12, 0xd0, 0x06, 0x23, 0x28, 0x00, 0x23, 0x66, 0x00, 0xf0, 0x42, 0xf8
|
||||
.byte 0x25, 0x66, 0x03, 0x20, 0x27, 0x66, 0x26, 0x66, 0x00, 0xf0, 0x3c, 0xf8, 0x03, 0x36, 0x26, 0x66
|
||||
.byte 0x02, 0x20, 0x26, 0x66, 0x00, 0xf0, 0x36, 0xf8, 0x28, 0x42, 0xf8, 0xd1, 0x00, 0x25, 0x12, 0x4b
|
||||
.byte 0xa5, 0x60, 0x12, 0x4f, 0x23, 0x60, 0x12, 0x4b, 0x65, 0x60, 0x01, 0x26, 0x3b, 0x60, 0xeb, 0x23
|
||||
.byte 0xa6, 0x60, 0x23, 0x66, 0x4b, 0x3b, 0x23, 0x66, 0x02, 0x20, 0x00, 0xf0, 0x23, 0xf8, 0x0d, 0x4b
|
||||
.byte 0xa5, 0x60, 0x3b, 0x60, 0xa6, 0x60, 0x01, 0x9b, 0xab, 0x42, 0x08, 0xd1, 0x0a, 0x4b, 0x0b, 0x4a
|
||||
.byte 0x13, 0x60, 0x1b, 0x68, 0x83, 0xf3, 0x08, 0x88, 0x09, 0x4b, 0x1b, 0x68, 0x18, 0x47, 0xf7, 0xbd
|
||||
.byte 0x00, 0x00, 0x02, 0x40, 0xf0, 0x00, 0x00, 0x18, 0x00, 0x03, 0x5f, 0x00, 0xf4, 0x00, 0x00, 0x18
|
||||
.byte 0x21, 0x22, 0x00, 0x00, 0x22, 0x20, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x10, 0x08, 0xed, 0x00, 0xe0
|
||||
.byte 0x04, 0x01, 0x00, 0x10, 0xc0, 0x22, 0x03, 0x00, 0x04, 0x21, 0x52, 0x05, 0x90, 0x6a, 0x08, 0x42
|
||||
.byte 0xfc, 0xd0, 0x01, 0x21, 0x90, 0x6a, 0x08, 0x42, 0xfc, 0xd1, 0x01, 0x3b, 0xdb, 0xb2, 0x10, 0x6e
|
||||
.byte 0x00, 0x2b, 0xfa, 0xd1, 0x70, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xa0, 0xf0, 0x2f
|
||||
@@ -0,0 +1,23 @@
|
||||
// Padded and checksummed version of: generated\Winbond\W25Q16JVxQ\boot2.bin
|
||||
|
||||
.cpu cortex-m0plus
|
||||
.thumb
|
||||
|
||||
.section .boot2, "ax"
|
||||
|
||||
.byte 0xf7, 0xb5, 0x73, 0x46, 0x21, 0x22, 0x02, 0x26, 0x01, 0x93, 0x29, 0x4b, 0xc0, 0x24, 0x5a, 0x60
|
||||
.byte 0x9a, 0x68, 0x00, 0x27, 0xb2, 0x43, 0xda, 0x60, 0x9a, 0x60, 0x1a, 0x61, 0x5a, 0x61, 0x04, 0x23
|
||||
.byte 0x01, 0x25, 0x64, 0x05, 0xa7, 0x60, 0x63, 0x61, 0x22, 0x4b, 0x30, 0x00, 0x1d, 0x60, 0xe0, 0x23
|
||||
.byte 0xdb, 0x02, 0x23, 0x60, 0x35, 0x23, 0xa5, 0x60, 0x23, 0x66, 0x23, 0x66, 0x00, 0xf0, 0x4a, 0xf8
|
||||
.byte 0xc0, 0xb2, 0xb0, 0x42, 0x12, 0xd0, 0x06, 0x23, 0x28, 0x00, 0x23, 0x66, 0x00, 0xf0, 0x42, 0xf8
|
||||
.byte 0x25, 0x66, 0x03, 0x20, 0x27, 0x66, 0x26, 0x66, 0x00, 0xf0, 0x3c, 0xf8, 0x03, 0x36, 0x26, 0x66
|
||||
.byte 0x02, 0x20, 0x26, 0x66, 0x00, 0xf0, 0x36, 0xf8, 0x28, 0x42, 0xf8, 0xd1, 0x00, 0x25, 0x12, 0x4b
|
||||
.byte 0xa5, 0x60, 0x12, 0x4f, 0x23, 0x60, 0x12, 0x4b, 0x65, 0x60, 0x01, 0x26, 0x3b, 0x60, 0xeb, 0x23
|
||||
.byte 0xa6, 0x60, 0x23, 0x66, 0x4b, 0x3b, 0x23, 0x66, 0x02, 0x20, 0x00, 0xf0, 0x23, 0xf8, 0x0d, 0x4b
|
||||
.byte 0xa5, 0x60, 0x3b, 0x60, 0xa6, 0x60, 0x01, 0x9b, 0xab, 0x42, 0x08, 0xd1, 0x0a, 0x4b, 0x0b, 0x4a
|
||||
.byte 0x13, 0x60, 0x1b, 0x68, 0x83, 0xf3, 0x08, 0x88, 0x09, 0x4b, 0x1b, 0x68, 0x18, 0x47, 0xf7, 0xbd
|
||||
.byte 0x00, 0x00, 0x02, 0x40, 0xf0, 0x00, 0x00, 0x18, 0x00, 0x03, 0x5f, 0x00, 0xf4, 0x00, 0x00, 0x18
|
||||
.byte 0x21, 0x22, 0x00, 0x00, 0x22, 0x20, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x10, 0x08, 0xed, 0x00, 0xe0
|
||||
.byte 0x04, 0x01, 0x00, 0x10, 0xc0, 0x22, 0x03, 0x00, 0x04, 0x21, 0x52, 0x05, 0x90, 0x6a, 0x08, 0x42
|
||||
.byte 0xfc, 0xd0, 0x01, 0x21, 0x90, 0x6a, 0x08, 0x42, 0xfc, 0xd1, 0x01, 0x3b, 0xdb, 0xb2, 0x10, 0x6e
|
||||
.byte 0x00, 0x2b, 0xfa, 0xd1, 0x70, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xa0, 0xf0, 0x2f
|
||||
+10
-1
@@ -68,10 +68,16 @@ void noInterrupts();
|
||||
#define portOutputRegister(port) ((volatile uint32_t*) sio_hw->gpio_out)
|
||||
#define portInputRegister(port) ((volatile uint32_t*) sio_hw->gpio_in)
|
||||
#define portModeRegister(port) ((volatile uint32_t*) sio_hw->gpio_oe)
|
||||
#define digitalWriteFast(pin, val) (val ? sio_hw->gpio_set = (1 << pin) : sio_hw->gpio_clr = (1 << pin))
|
||||
#define digitalReadFast(pin) ((1 << pin) & sio_hw->gpio_in)
|
||||
#define sei() interrupts()
|
||||
#define cli() noInterrupts()
|
||||
|
||||
// ADC RP2040-specific calls
|
||||
void analogReadResolution(int bits);
|
||||
float analogReadTemp(); // Returns core temp in Centigrade
|
||||
#ifdef __cplusplus
|
||||
float analogReadTemp(float vref = 3.3); // Returns core temp in Centigrade
|
||||
#endif
|
||||
|
||||
// PWM RP2040-specific calls
|
||||
void analogWriteFreq(uint32_t freq);
|
||||
@@ -101,6 +107,9 @@ extern bool __isFreeRTOS;
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
// emptyString is an ESP-ism, a constant string with ""
|
||||
extern const String emptyString;
|
||||
|
||||
#ifdef USE_TINYUSB
|
||||
// Needed for declaring Serial
|
||||
#include "Adafruit_USBD_CDC.h"
|
||||
|
||||
@@ -24,17 +24,22 @@
|
||||
#include "Arduino.h"
|
||||
#include "CoreMutex.h"
|
||||
|
||||
CoreMutex::CoreMutex(mutex_t *mutex, bool debugEnable) {
|
||||
CoreMutex::CoreMutex(mutex_t *mutex, uint8_t option) {
|
||||
_mutex = mutex;
|
||||
_acquired = false;
|
||||
_option = option;
|
||||
if (__isFreeRTOS) {
|
||||
auto m = __get_freertos_mutex_for_ptr(mutex);
|
||||
__freertos_mutex_take(m);
|
||||
if (_option & FromISR) {
|
||||
__freertos_mutex_take_from_isr(m);
|
||||
} else {
|
||||
__freertos_mutex_take(m);
|
||||
}
|
||||
} else {
|
||||
uint32_t owner;
|
||||
if (!mutex_try_enter(_mutex, &owner)) {
|
||||
if (owner == get_core_num()) { // Deadlock!
|
||||
if (debugEnable) {
|
||||
if (_option & DebugEnable) {
|
||||
DEBUGCORE("CoreMutex - Deadlock detected!\n");
|
||||
}
|
||||
return;
|
||||
@@ -49,7 +54,11 @@ CoreMutex::~CoreMutex() {
|
||||
if (_acquired) {
|
||||
if (__isFreeRTOS) {
|
||||
auto m = __get_freertos_mutex_for_ptr(_mutex);
|
||||
__freertos_mutex_give(m);
|
||||
if (_option & FromISR) {
|
||||
__freertos_mutex_give_from_isr(m);
|
||||
} else {
|
||||
__freertos_mutex_give(m);
|
||||
}
|
||||
} else {
|
||||
mutex_exit(_mutex);
|
||||
}
|
||||
|
||||
@@ -26,9 +26,14 @@
|
||||
#include "pico/mutex.h"
|
||||
#include "_freertos.h"
|
||||
|
||||
enum {
|
||||
DebugEnable = 1,
|
||||
FromISR = 1 << 1,
|
||||
};
|
||||
|
||||
class CoreMutex {
|
||||
public:
|
||||
CoreMutex(mutex_t *mutex, bool debugEnable = true);
|
||||
CoreMutex(mutex_t *mutex, uint8_t option = DebugEnable);
|
||||
~CoreMutex();
|
||||
|
||||
operator bool() {
|
||||
@@ -38,4 +43,5 @@ public:
|
||||
private:
|
||||
mutex_t *_mutex;
|
||||
bool _acquired;
|
||||
u_int8_t _option;
|
||||
};
|
||||
|
||||
+6
-7
@@ -192,13 +192,12 @@ File File::openNextFile() {
|
||||
String File::readString() {
|
||||
String ret;
|
||||
ret.reserve(size() - position());
|
||||
char temp[256 + 1];
|
||||
int countRead = readBytes(temp, sizeof(temp) - 1);
|
||||
while (countRead > 0) {
|
||||
temp[countRead] = 0;
|
||||
ret += temp;
|
||||
countRead = readBytes(temp, sizeof(temp) - 1);
|
||||
}
|
||||
uint8_t temp[256];
|
||||
int countRead;
|
||||
do {
|
||||
countRead = read(temp, sizeof(temp));
|
||||
ret.concat(temp, countRead);
|
||||
} while (countRead > 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -325,7 +325,7 @@ public:
|
||||
}
|
||||
|
||||
const char *getChipID() {
|
||||
static char id[PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1] = { 0 };
|
||||
static char id[2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 1] = { 0 };
|
||||
if (!id[0]) {
|
||||
pico_get_unique_board_id_string(id, sizeof(id));
|
||||
}
|
||||
|
||||
+177
-8
@@ -33,6 +33,10 @@
|
||||
#include "hardware/irq.h"
|
||||
#include "pico/mutex.h"
|
||||
#include "pico/unique_id.h"
|
||||
#include "pico/usb_reset_interface.h"
|
||||
#include "hardware/watchdog.h"
|
||||
#include "pico/bootrom.h"
|
||||
#include <device/usbd_pvt.h>
|
||||
|
||||
// Big, global USB mutex, shared with all USB devices to make sure we don't
|
||||
// have multiple cores updating the TUSB state in parallel
|
||||
@@ -42,12 +46,11 @@ mutex_t __usb_mutex;
|
||||
#define USB_TASK_INTERVAL 1000
|
||||
static int __usb_task_irq;
|
||||
|
||||
// USB VID/PID (note that PID can change depending on the add'l interfaces)
|
||||
#ifndef USBD_VID
|
||||
#define USBD_VID (0x2E8A) // Raspberry Pi
|
||||
#endif
|
||||
|
||||
#ifdef SERIALUSB_PID
|
||||
#define USBD_PID (SERIALUSB_PID)
|
||||
#else
|
||||
#ifndef USBD_PID
|
||||
#define USBD_PID (0x000a) // Raspberry Pi Pico SDK CDC
|
||||
#endif
|
||||
|
||||
@@ -67,10 +70,18 @@ static int __usb_task_irq;
|
||||
#define USBD_STR_PRODUCT (0x02)
|
||||
#define USBD_STR_SERIAL (0x03)
|
||||
#define USBD_STR_CDC (0x04)
|
||||
|
||||
#define USBD_STR_RPI_RESET (0x05)
|
||||
|
||||
#define EPNUM_HID 0x83
|
||||
|
||||
#define USBD_MSC_EPOUT 0x03
|
||||
#define USBD_MSC_EPIN 0x84
|
||||
#define USBD_MSC_EPSIZE 64
|
||||
|
||||
#define TUD_RPI_RESET_DESCRIPTOR(_itfnum, _stridx) \
|
||||
/* Interface */\
|
||||
9, TUSB_DESC_INTERFACE, _itfnum, 0, 0, TUSB_CLASS_VENDOR_SPECIFIC, RESET_INTERFACE_SUBCLASS, RESET_INTERFACE_PROTOCOL, _stridx,
|
||||
|
||||
|
||||
const uint8_t *tud_descriptor_device_cb(void) {
|
||||
static tusb_desc_device_t usbd_desc_device = {
|
||||
@@ -89,7 +100,7 @@ const uint8_t *tud_descriptor_device_cb(void) {
|
||||
.iSerialNumber = USBD_STR_SERIAL,
|
||||
.bNumConfigurations = 1
|
||||
};
|
||||
if (__USBInstallSerial && !__USBInstallKeyboard && !__USBInstallMouse && !__USBInstallJoystick) {
|
||||
if (__USBInstallSerial && !__USBInstallKeyboard && !__USBInstallMouse && !__USBInstallJoystick && !__USBInstallMassStorage) {
|
||||
// Can use as-is, this is the default USB case
|
||||
return (const uint8_t *)&usbd_desc_device;
|
||||
}
|
||||
@@ -103,6 +114,9 @@ const uint8_t *tud_descriptor_device_cb(void) {
|
||||
if (__USBInstallJoystick) {
|
||||
usbd_desc_device.idProduct |= 0x0100;
|
||||
}
|
||||
if (__USBInstallMassStorage) {
|
||||
usbd_desc_device.idProduct ^= 0x2000;
|
||||
}
|
||||
// Set the device class to 0 to indicate multiple device classes
|
||||
usbd_desc_device.bDeviceClass = 0;
|
||||
usbd_desc_device.bDeviceSubClass = 0;
|
||||
@@ -223,7 +237,7 @@ void __SetupUSBDescriptor() {
|
||||
if (!usbd_desc_cfg) {
|
||||
bool hasHID = __USBInstallKeyboard || __USBInstallMouse || __USBInstallJoystick;
|
||||
|
||||
uint8_t interface_count = (__USBInstallSerial ? 2 : 0) + (hasHID ? 1 : 0);
|
||||
uint8_t interface_count = (__USBInstallSerial ? 2 : 0) + (hasHID ? 1 : 0) + (__USBInstallMassStorage ? 1 : 0);
|
||||
|
||||
uint8_t cdc_desc[TUD_CDC_DESC_LEN] = {
|
||||
// Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval
|
||||
@@ -238,7 +252,20 @@ void __SetupUSBDescriptor() {
|
||||
TUD_HID_DESCRIPTOR(hid_itf, 0, HID_ITF_PROTOCOL_NONE, hid_report_len, EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 10)
|
||||
};
|
||||
|
||||
int usbd_desc_len = TUD_CONFIG_DESC_LEN + (__USBInstallSerial ? sizeof(cdc_desc) : 0) + (hasHID ? sizeof(hid_desc) : 0);
|
||||
uint8_t msd_itf = interface_count - 1;
|
||||
uint8_t msd_desc[TUD_MSC_DESC_LEN] = {
|
||||
TUD_MSC_DESCRIPTOR(msd_itf, 0, USBD_MSC_EPOUT, USBD_MSC_EPIN, USBD_MSC_EPSIZE)
|
||||
};
|
||||
|
||||
int usbd_desc_len = TUD_CONFIG_DESC_LEN + (__USBInstallSerial ? sizeof(cdc_desc) : 0) + (hasHID ? sizeof(hid_desc) : 0) + (__USBInstallMassStorage ? sizeof(msd_desc) : 0);
|
||||
|
||||
#ifdef ENABLE_PICOTOOL_USB
|
||||
uint8_t picotool_itf = interface_count++;
|
||||
uint8_t picotool_desc[] = {
|
||||
TUD_RPI_RESET_DESCRIPTOR(picotool_itf, USBD_STR_RPI_RESET)
|
||||
};
|
||||
usbd_desc_len += sizeof(picotool_desc);
|
||||
#endif
|
||||
|
||||
uint8_t tud_cfg_desc[TUD_CONFIG_DESC_LEN] = {
|
||||
// Config number, interface count, string index, total length, attribute, power in mA
|
||||
@@ -260,6 +287,14 @@ void __SetupUSBDescriptor() {
|
||||
memcpy(ptr, hid_desc, sizeof(hid_desc));
|
||||
ptr += sizeof(hid_desc);
|
||||
}
|
||||
if (__USBInstallMassStorage) {
|
||||
memcpy(ptr, msd_desc, sizeof(msd_desc));
|
||||
ptr += sizeof(msd_desc);
|
||||
}
|
||||
#ifdef ENABLE_PICOTOOL_USB
|
||||
memcpy(ptr, picotool_desc, sizeof(picotool_desc));
|
||||
ptr += sizeof(picotool_desc);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,6 +312,9 @@ const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
|
||||
[USBD_STR_PRODUCT] = "PicoArduino",
|
||||
[USBD_STR_SERIAL] = idString,
|
||||
[USBD_STR_CDC] = "Board CDC",
|
||||
#ifdef ENABLE_PICOTOOL_USB
|
||||
[USBD_STR_RPI_RESET] = "Reset",
|
||||
#endif
|
||||
};
|
||||
|
||||
if (!idString[0]) {
|
||||
@@ -367,4 +405,135 @@ extern "C" void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_r
|
||||
(void) bufsize;
|
||||
}
|
||||
|
||||
extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) __attribute__((weak));
|
||||
extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {
|
||||
(void) lun;
|
||||
(void) lba;
|
||||
(void) offset;
|
||||
(void) buffer;
|
||||
(void) bufsize;
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) __attribute__((weak));
|
||||
extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) {
|
||||
(void) lun;
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) __attribute__((weak));
|
||||
extern "C" int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) {
|
||||
(void) lun;
|
||||
(void) lba;
|
||||
(void) offset;
|
||||
(void) buffer;
|
||||
(void) bufsize;
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern "C" int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) __attribute__((weak));
|
||||
extern "C" int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) {
|
||||
(void) lun;
|
||||
(void) scsi_cmd;
|
||||
(void) buffer;
|
||||
(void) bufsize;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) __attribute__((weak));
|
||||
extern "C" void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) {
|
||||
(void) lun;
|
||||
*block_count = 0;
|
||||
*block_size = 0;
|
||||
}
|
||||
|
||||
extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) __attribute__((weak));
|
||||
extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) {
|
||||
(void) lun;
|
||||
vendor_id[0] = 0;
|
||||
product_id[0] = 0;
|
||||
product_rev[0] = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef ENABLE_PICOTOOL_USB
|
||||
|
||||
static uint8_t _picotool_itf_num;
|
||||
|
||||
static void resetd_init() {
|
||||
}
|
||||
|
||||
static void resetd_reset(uint8_t rhport) {
|
||||
(void) rhport;
|
||||
_picotool_itf_num = 0;
|
||||
}
|
||||
|
||||
static uint16_t resetd_open(uint8_t rhport,
|
||||
tusb_desc_interface_t const *itf_desc, uint16_t max_len) {
|
||||
(void) rhport;
|
||||
TU_VERIFY(TUSB_CLASS_VENDOR_SPECIFIC == itf_desc->bInterfaceClass &&
|
||||
RESET_INTERFACE_SUBCLASS == itf_desc->bInterfaceSubClass &&
|
||||
RESET_INTERFACE_PROTOCOL == itf_desc->bInterfaceProtocol, 0);
|
||||
|
||||
uint16_t const drv_len = sizeof(tusb_desc_interface_t);
|
||||
TU_VERIFY(max_len >= drv_len, 0);
|
||||
|
||||
_picotool_itf_num = itf_desc->bInterfaceNumber;
|
||||
return drv_len;
|
||||
}
|
||||
|
||||
// Support for parameterized reset via vendor interface control request
|
||||
static bool resetd_control_xfer_cb(uint8_t rhport, uint8_t stage,
|
||||
tusb_control_request_t const *request) {
|
||||
(void) rhport;
|
||||
// nothing to do with DATA & ACK stage
|
||||
if (stage != CONTROL_STAGE_SETUP) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (request->wIndex == _picotool_itf_num) {
|
||||
if (request->bRequest == RESET_REQUEST_BOOTSEL) {
|
||||
reset_usb_boot(0, (request->wValue & 0x7f));
|
||||
// does not return, otherwise we'd return true
|
||||
}
|
||||
|
||||
if (request->bRequest == RESET_REQUEST_FLASH) {
|
||||
watchdog_reboot(0, 0, 100);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool resetd_xfer_cb(uint8_t rhport, uint8_t ep_addr,
|
||||
xfer_result_t result, uint32_t xferred_bytes) {
|
||||
(void) rhport;
|
||||
(void) ep_addr;
|
||||
(void) result;
|
||||
(void) xferred_bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
static usbd_class_driver_t const _resetd_driver = {
|
||||
#if CFG_TUSB_DEBUG >= 2
|
||||
.name = "RESET",
|
||||
#endif
|
||||
.init = resetd_init,
|
||||
.reset = resetd_reset,
|
||||
.open = resetd_open,
|
||||
.control_xfer_cb = resetd_control_xfer_cb,
|
||||
.xfer_cb = resetd_xfer_cb,
|
||||
.sof = NULL
|
||||
};
|
||||
|
||||
// Implement callback to add our custom driver
|
||||
usbd_class_driver_t const *usbd_app_driver_get_cb(uint8_t *driver_count) {
|
||||
*driver_count = 1;
|
||||
return &_resetd_driver;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
@@ -26,6 +26,7 @@ extern void __USBInstallSerial() __attribute__((weak));
|
||||
extern void __USBInstallKeyboard() __attribute__((weak));
|
||||
extern void __USBInstallJoystick() __attribute__((weak));
|
||||
extern void __USBInstallMouse() __attribute__((weak));
|
||||
extern void __USBInstallMassStorage() __attribute__((weak));
|
||||
|
||||
// Big, global USB mutex, shared with all USB devices to make sure we don't
|
||||
// have multiple cores updating the TUSB state in parallel
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
#define ARDUINO_PICO_MAJOR 2
|
||||
#define ARDUINO_PICO_MINOR 6
|
||||
#define ARDUINO_PICO_MINOR 7
|
||||
#define ARDUINO_PICO_REVISION 2
|
||||
#define ARDUINO_PICO_VERSION_STR "2.6.2"
|
||||
#define ARDUINO_PICO_VERSION_STR "2.7.2"
|
||||
|
||||
@@ -230,7 +230,7 @@ void SerialPIO::begin(unsigned long baud, uint16_t config) {
|
||||
pio_sm_clear_fifos(_rxPIO, _rxSM); // Remove any existing data
|
||||
|
||||
// Put phase divider into OSR w/o using add'l program memory
|
||||
pio_sm_put_blocking(_rxPIO, _rxSM, clock_get_hz(clk_sys) / (_baud * 2) - 5 /* insns in PIO halfbit loop */);
|
||||
pio_sm_put_blocking(_rxPIO, _rxSM, clock_get_hz(clk_sys) / (_baud * 2) - 7 /* insns in PIO halfbit loop */);
|
||||
pio_sm_exec(_rxPIO, _rxSM, pio_encode_pull(false, false));
|
||||
|
||||
// Join the TX FIFO to the RX one now that we don't need it
|
||||
@@ -259,9 +259,11 @@ void SerialPIO::end() {
|
||||
}
|
||||
if (_tx != NOPIN) {
|
||||
pio_sm_set_enabled(_txPIO, _txSM, false);
|
||||
pio_sm_unclaim(_txPIO, _txSM);
|
||||
}
|
||||
if (_rx != NOPIN) {
|
||||
pio_sm_set_enabled(_rxPIO, _rxSM, false);
|
||||
pio_sm_unclaim(_rxPIO, _rxSM);
|
||||
_pioSP[pio_get_index(_rxPIO)][_rxSM] = nullptr;
|
||||
// If no more active, disable the IRQ
|
||||
auto pioNum = pio_get_index(_rxPIO);
|
||||
|
||||
+57
-17
@@ -134,6 +134,20 @@ void SerialUART::begin(unsigned long baud, uint16_t config) {
|
||||
_overflow = false;
|
||||
_queue = new uint8_t[_fifoSize];
|
||||
_baud = baud;
|
||||
|
||||
_fcnTx = gpio_get_function(_tx);
|
||||
_fcnRx = gpio_get_function(_rx);
|
||||
gpio_set_function(_tx, GPIO_FUNC_UART);
|
||||
gpio_set_function(_rx, GPIO_FUNC_UART);
|
||||
if (_rts != UART_PIN_NOT_DEFINED) {
|
||||
_fcnRts = gpio_get_function(_rts);
|
||||
gpio_set_function(_rts, GPIO_FUNC_UART);
|
||||
}
|
||||
if (_cts != UART_PIN_NOT_DEFINED) {
|
||||
_fcnCts = gpio_get_function(_cts);
|
||||
gpio_set_function(_cts, GPIO_FUNC_UART);
|
||||
}
|
||||
|
||||
uart_init(_uart, baud);
|
||||
int bits, stop;
|
||||
uart_parity_t parity;
|
||||
@@ -171,18 +185,6 @@ void SerialUART::begin(unsigned long baud, uint16_t config) {
|
||||
break;
|
||||
}
|
||||
uart_set_format(_uart, bits, stop, parity);
|
||||
_fcnTx = gpio_get_function(_tx);
|
||||
_fcnRx = gpio_get_function(_rx);
|
||||
gpio_set_function(_tx, GPIO_FUNC_UART);
|
||||
gpio_set_function(_rx, GPIO_FUNC_UART);
|
||||
if (_rts != UART_PIN_NOT_DEFINED) {
|
||||
_fcnRts = gpio_get_function(_rts);
|
||||
gpio_set_function(_rts, GPIO_FUNC_UART);
|
||||
}
|
||||
if (_cts != UART_PIN_NOT_DEFINED) {
|
||||
_fcnCts = gpio_get_function(_cts);
|
||||
gpio_set_function(_cts, GPIO_FUNC_UART);
|
||||
}
|
||||
uart_set_hw_flow(_uart, _rts != UART_PIN_NOT_DEFINED, _cts != UART_PIN_NOT_DEFINED);
|
||||
_writer = 0;
|
||||
_reader = 0;
|
||||
@@ -200,6 +202,7 @@ void SerialUART::begin(unsigned long baud, uint16_t config) {
|
||||
} else {
|
||||
// Polling mode has no IRQs used
|
||||
}
|
||||
_break = false;
|
||||
_running = true;
|
||||
}
|
||||
|
||||
@@ -288,13 +291,22 @@ int SerialUART::read() {
|
||||
}
|
||||
|
||||
bool SerialUART::overflow() {
|
||||
CoreMutex m(&_mutex);
|
||||
if (!_running || !m) {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
bool hold = _overflow;
|
||||
|
||||
if (_polling) {
|
||||
_handleIRQ(false);
|
||||
} else {
|
||||
_pumpFIFO();
|
||||
}
|
||||
|
||||
mutex_enter_blocking(&_fifoMutex);
|
||||
bool ovf = _overflow;
|
||||
_overflow = false;
|
||||
return hold;
|
||||
mutex_exit(&_fifoMutex);
|
||||
|
||||
return ovf;
|
||||
}
|
||||
|
||||
int SerialUART::available() {
|
||||
@@ -365,6 +377,25 @@ SerialUART::operator bool() {
|
||||
return _running;
|
||||
}
|
||||
|
||||
bool SerialUART::getBreakReceived() {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_polling) {
|
||||
_handleIRQ(false);
|
||||
} else {
|
||||
_pumpFIFO();
|
||||
}
|
||||
|
||||
mutex_enter_blocking(&_fifoMutex);
|
||||
bool break_received = _break;
|
||||
_break = false;
|
||||
mutex_exit(&_fifoMutex);
|
||||
|
||||
return break_received;
|
||||
}
|
||||
|
||||
void arduino::serialEvent1Run(void) {
|
||||
if (serialEvent1 && Serial1.available()) {
|
||||
serialEvent1();
|
||||
@@ -390,7 +421,16 @@ void __not_in_flash_func(SerialUART::_handleIRQ)(bool inIRQ) {
|
||||
// ICR is write-to-clear
|
||||
uart_get_hw(_uart)->icr = UART_UARTICR_RTIC_BITS | UART_UARTICR_RXIC_BITS;
|
||||
while (uart_is_readable(_uart)) {
|
||||
auto val = uart_getc(_uart);
|
||||
uint32_t raw = uart_get_hw(_uart)->dr;
|
||||
if (raw & 0x400) {
|
||||
// break!
|
||||
_break = true;
|
||||
continue;
|
||||
} else if (raw & 0x300) {
|
||||
// Framing, Parity Error. Ignore this bad char
|
||||
continue;
|
||||
}
|
||||
uint8_t val = raw & 0xff;
|
||||
auto next_writer = _writer + 1;
|
||||
if (next_writer == _fifoSize) {
|
||||
next_writer = 0;
|
||||
|
||||
@@ -63,9 +63,18 @@ public:
|
||||
bool overflow();
|
||||
operator bool() override;
|
||||
|
||||
// ESP8266 compat
|
||||
void setDebugOutput(bool unused) {
|
||||
(void) unused;
|
||||
}
|
||||
|
||||
// Not to be called by users, only from the IRQ handler. In public so that the C-language IQR callback can access it
|
||||
void _handleIRQ(bool inIRQ = true);
|
||||
|
||||
// Allows the user to sleep until a break is received (self-clears the flag
|
||||
// on read)
|
||||
bool getBreakReceived();
|
||||
|
||||
private:
|
||||
bool _running = false;
|
||||
uart_inst_t *_uart;
|
||||
@@ -76,6 +85,7 @@ private:
|
||||
mutex_t _mutex;
|
||||
bool _polling = false;
|
||||
bool _overflow;
|
||||
bool _break;
|
||||
|
||||
// Lockless, IRQ-handled circular queue
|
||||
uint32_t _writer;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "pico/mutex.h"
|
||||
#include "hardware/watchdog.h"
|
||||
#include "pico/unique_id.h"
|
||||
#include "hardware/resets.h"
|
||||
|
||||
#ifndef DISABLE_USB_SERIAL
|
||||
// Ensure we are installed in the USB chain
|
||||
@@ -174,8 +175,18 @@ SerialUSB::operator bool() {
|
||||
static bool _dtr = false;
|
||||
static bool _rts = false;
|
||||
static int _bps = 115200;
|
||||
static bool _rebooting = false;
|
||||
static void CheckSerialReset() {
|
||||
if ((_bps == 1200) && (!_dtr)) {
|
||||
__holdUpPendSV = 1; // Ensure we don't get swapped out by FreeRTOS
|
||||
if (!_rebooting && (_bps == 1200) && (!_dtr)) {
|
||||
_rebooting = true;
|
||||
// Disable NVIC IRQ, so that we don't get bothered anymore
|
||||
irq_set_enabled(USBCTRL_IRQ, false);
|
||||
// Reset the whole USB hardware block
|
||||
reset_block(RESETS_RESET_USBCTRL_BITS);
|
||||
unreset_block(RESETS_RESET_USBCTRL_BITS);
|
||||
// Delay a bit, so the PC can figure out that we have disconnected.
|
||||
sleep_ms(3);
|
||||
reset_usb_boot(0, 0);
|
||||
while (1); // WDT will fire here
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@ public:
|
||||
using Print::write;
|
||||
operator bool() override;
|
||||
|
||||
// ESP8266 compat
|
||||
void setDebugOutput(bool unused) {
|
||||
(void) unused;
|
||||
}
|
||||
|
||||
private:
|
||||
bool _running = false;
|
||||
};
|
||||
|
||||
@@ -109,7 +109,7 @@ void tone(uint8_t pin, unsigned int frequency, unsigned long duration) {
|
||||
if (ret > 0) {
|
||||
newTone->alarm = ret;
|
||||
} else {
|
||||
DEBUGCORE("ERROR: Unable to allocate timer for tone(%d, %d, %d)\n",
|
||||
DEBUGCORE("ERROR: Unable to allocate timer for tone(%d, %d, %lu)\n",
|
||||
pin, frequency, duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#include "api/String.h"
|
||||
@@ -38,10 +38,15 @@ extern "C" SemaphoreHandle_t __get_freertos_mutex_for_ptr(mutex_t *m) {
|
||||
return _map[i].dst;
|
||||
}
|
||||
}
|
||||
// Make a new mutex
|
||||
auto fm = __freertos_mutex_create();
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (_map[i].src == nullptr) {
|
||||
// Make a new mutex
|
||||
SemaphoreHandle_t fm = __freertos_mutex_create();
|
||||
if (fm == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
_map[i].src = m;
|
||||
_map[i].dst = fm;
|
||||
return fm;
|
||||
|
||||
@@ -41,8 +41,10 @@ extern "C" {
|
||||
extern SemaphoreHandle_t _freertos_recursive_mutex_create() __attribute__((weak));
|
||||
|
||||
extern void __freertos_mutex_take(SemaphoreHandle_t mtx) __attribute__((weak));
|
||||
extern void __freertos_mutex_take_from_isr(SemaphoreHandle_t mtx) __attribute__((weak));
|
||||
extern int __freertos_mutex_try_take(SemaphoreHandle_t mtx) __attribute__((weak));
|
||||
extern void __freertos_mutex_give(SemaphoreHandle_t mtx) __attribute__((weak));
|
||||
extern void __freertos_mutex_give_from_isr(SemaphoreHandle_t mtx) __attribute__((weak));
|
||||
|
||||
extern void __freertos_recursive_mutex_take(SemaphoreHandle_t mtx) __attribute__((weak));
|
||||
extern int __freertos_recursive_mutex_try_take(SemaphoreHandle_t mtx) __attribute__((weak));
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(DEBUG_RP2040_PORT)
|
||||
#define DEBUGV(...) do { } while(0)
|
||||
#define DEBUGCORE(...) do { } while(0)
|
||||
@@ -44,3 +46,7 @@
|
||||
#define DEBUGSPI(...) do { } while(0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern void hexdump(const void* mem, uint32_t len, uint8_t cols = 16);
|
||||
#endif
|
||||
|
||||
+27
-2
@@ -125,7 +125,6 @@ extern "C" int main() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef NO_USB
|
||||
if (!__isFreeRTOS) {
|
||||
if (setup1 || loop1) {
|
||||
rp2040.fifo.begin(2);
|
||||
@@ -134,7 +133,6 @@ extern "C" int main() {
|
||||
}
|
||||
rp2040.fifo.registerCore();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!__isFreeRTOS) {
|
||||
if (setup1 || loop1) {
|
||||
@@ -172,3 +170,30 @@ extern "C" struct _reent *__wrap___getreent() {
|
||||
return _impure_ptr1;
|
||||
}
|
||||
}
|
||||
|
||||
// ESP8266 internal debug routine
|
||||
extern void hexdump(const void* mem, uint32_t len, uint8_t cols) __attribute__((weak));
|
||||
void hexdump(const void* mem, uint32_t len, uint8_t cols) {
|
||||
const char* src = (const char*)mem;
|
||||
printf("\n[HEXDUMP] Address: %p len: 0x%lX (%ld)", src, len, len);
|
||||
while (len > 0) {
|
||||
uint32_t linesize = cols > len ? len : cols;
|
||||
printf("\n[%p] 0x%04x: ", src, (int)(src - (const char*)mem));
|
||||
for (uint32_t i = 0; i < linesize; i++) {
|
||||
printf("%02x ", *(src + i));
|
||||
}
|
||||
printf(" ");
|
||||
for (uint32_t i = linesize; i < cols; i++) {
|
||||
printf(" ");
|
||||
}
|
||||
for (uint32_t i = 0; i < linesize; i++) {
|
||||
unsigned char c = *(src + i);
|
||||
putc(isprint(c) ? c : '.', stdout);
|
||||
}
|
||||
src += linesize;
|
||||
len -= linesize;
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
const String emptyString = "";
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
// Taken from the Pico-SDK v1.4.0 and hacked by EFP3 to allow overriding the LWIP setup
|
||||
|
||||
#include <stdio.h>
|
||||
//#include <stdio.h>
|
||||
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include "pico/mutex.h"
|
||||
@@ -145,7 +145,7 @@ static void gpio_irq_handler(void) {
|
||||
static void low_priority_irq_handler(void) {
|
||||
assert(cyw43_core_num == get_core_num());
|
||||
if (recursive_mutex_try_enter(&cyw43_mutex, NULL)) {
|
||||
if (recursive_mutex_enter_count(&cyw43_mutex) != 1) {
|
||||
if (__inLWIP || (recursive_mutex_enter_count(&cyw43_mutex) != 1)) {
|
||||
low_priority_irq_missed = true;
|
||||
CYW43_STAT_INC(PENDSV_DISABLED_COUNT);
|
||||
} else {
|
||||
|
||||
@@ -26,9 +26,12 @@
|
||||
#include <hardware/pll.h>
|
||||
#include <hardware/adc.h>
|
||||
|
||||
void __clearADCPin(pin_size_t p);
|
||||
|
||||
static uint32_t analogScale = 255;
|
||||
static uint32_t analogFreq = 1000;
|
||||
static uint32_t pwmInitted = 0;
|
||||
static bool scaleInitted = false;
|
||||
static bool adcInitted = false;
|
||||
static uint16_t analogWritePseudoScale = 1;
|
||||
static uint16_t analogWriteSlowScale = 1;
|
||||
@@ -40,15 +43,16 @@ extern "C" void analogWriteFreq(uint32_t freq) {
|
||||
return;
|
||||
}
|
||||
if (freq < 100) {
|
||||
DEBUGCORE("ERROR: analogWriteFreq too low (%d)\n", freq);
|
||||
DEBUGCORE("ERROR: analogWriteFreq too low (%lu)\n", freq);
|
||||
analogFreq = 100;
|
||||
} else if (freq > 10'000'000) {
|
||||
DEBUGCORE("ERROR: analogWriteFreq too high (%d)\n", freq);
|
||||
DEBUGCORE("ERROR: analogWriteFreq too high (%lu)\n", freq);
|
||||
analogFreq = 10'000'000;
|
||||
} else {
|
||||
analogFreq = freq;
|
||||
}
|
||||
pwmInitted = 0;
|
||||
scaleInitted = false;
|
||||
}
|
||||
|
||||
extern "C" void analogWriteRange(uint32_t range) {
|
||||
@@ -58,8 +62,9 @@ extern "C" void analogWriteRange(uint32_t range) {
|
||||
if ((range >= 3) && (range <= 65535)) {
|
||||
analogScale = range;
|
||||
pwmInitted = 0;
|
||||
scaleInitted = false;
|
||||
} else {
|
||||
DEBUGCORE("ERROR: analogWriteRange out of range (%d)\n", range);
|
||||
DEBUGCORE("ERROR: analogWriteRange out of range (%lu)\n", range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,26 +83,30 @@ extern "C" void analogWrite(pin_size_t pin, int val) {
|
||||
DEBUGCORE("ERROR: Illegal analogWrite pin (%d)\n", pin);
|
||||
return;
|
||||
}
|
||||
if (!(pwmInitted & (1 << pin))) {
|
||||
__clearADCPin(pin);
|
||||
if (!scaleInitted) {
|
||||
// For low frequencies, we need to scale the output max value up to achieve lower periods
|
||||
analogWritePseudoScale = 1;
|
||||
while (((clock_get_hz(clk_sys) / ((float)analogScale * analogFreq)) > 255.0) && (analogScale < 32678)) {
|
||||
analogWritePseudoScale++;
|
||||
analogScale *= 2;
|
||||
DEBUGCORE("Adjusting analogWrite values PS=%d, scale=%d\n", analogWritePseudoScale, analogScale);
|
||||
DEBUGCORE("Adjusting analogWrite values PS=%d, scale=%lu\n", analogWritePseudoScale, analogScale);
|
||||
}
|
||||
// For high frequencies, we need to scale the output max value down to actually hit the frequency target
|
||||
analogWriteSlowScale = 1;
|
||||
while (((clock_get_hz(clk_sys) / ((float)analogScale * analogFreq)) < 1.0) && (analogScale >= 6)) {
|
||||
analogWriteSlowScale++;
|
||||
analogScale /= 2;
|
||||
DEBUGCORE("Adjusting analogWrite values SS=%d, scale=%d\n", analogWriteSlowScale, analogScale);
|
||||
DEBUGCORE("Adjusting analogWrite values SS=%d, scale=%lu\n", analogWriteSlowScale, analogScale);
|
||||
}
|
||||
scaleInitted = true;
|
||||
}
|
||||
if (!(pwmInitted & (1 << pwm_gpio_to_slice_num(pin)))) {
|
||||
pwm_config c = pwm_get_default_config();
|
||||
pwm_config_set_clkdiv(&c, clock_get_hz(clk_sys) / ((float)analogScale * analogFreq));
|
||||
pwm_config_set_wrap(&c, analogScale - 1);
|
||||
pwm_init(pwm_gpio_to_slice_num(pin), &c, true);
|
||||
pwmInitted |= 1 << pin;
|
||||
pwmInitted |= 1 << pwm_gpio_to_slice_num(pin);
|
||||
}
|
||||
|
||||
val <<= analogWritePseudoScale;
|
||||
@@ -114,7 +123,13 @@ extern "C" void analogWrite(pin_size_t pin, int val) {
|
||||
}
|
||||
|
||||
auto_init_mutex(_adcMutex);
|
||||
static int _readBits = 10;
|
||||
static uint8_t _readBits = 10;
|
||||
static uint8_t _lastADCMux = 0;
|
||||
static uint32_t _adcGPIOInit = 0;
|
||||
|
||||
void __clearADCPin(pin_size_t p) {
|
||||
_adcGPIOInit &= ~(1 << p);
|
||||
}
|
||||
|
||||
extern "C" int analogRead(pin_size_t pin) {
|
||||
CoreMutex m(&_adcMutex);
|
||||
@@ -130,12 +145,18 @@ extern "C" int analogRead(pin_size_t pin) {
|
||||
adc_init();
|
||||
adcInitted = true;
|
||||
}
|
||||
adc_gpio_init(pin);
|
||||
adc_select_input(pin - minPin);
|
||||
if (!(_adcGPIOInit & (1 << pin))) {
|
||||
adc_gpio_init(pin);
|
||||
_adcGPIOInit |= 1 << pin;
|
||||
}
|
||||
if (_lastADCMux != pin) {
|
||||
adc_select_input(pin - minPin);
|
||||
_lastADCMux = pin;
|
||||
}
|
||||
return (_readBits < 12) ? adc_read() >> (12 - _readBits) : adc_read() << (_readBits - 12);
|
||||
}
|
||||
|
||||
extern "C" float analogReadTemp() {
|
||||
extern "C" float analogReadTemp(float vref) {
|
||||
CoreMutex m(&_adcMutex);
|
||||
|
||||
if (!m) {
|
||||
@@ -145,12 +166,13 @@ extern "C" float analogReadTemp() {
|
||||
adc_init();
|
||||
adcInitted = true;
|
||||
}
|
||||
_lastADCMux = 0;
|
||||
adc_set_temp_sensor_enabled(true);
|
||||
delay(1); // Allow things to settle. Without this, readings can be erratic
|
||||
adc_select_input(4); // Temperature sensor
|
||||
int v = adc_read();
|
||||
adc_set_temp_sensor_enabled(false);
|
||||
float t = 27.0f - ((v * 3.3f / 4096.0f) - 0.706f) / 0.001721f; // From the datasheet
|
||||
float t = 27.0f - ((v * vref / 4096.0f) - 0.706f) / 0.001721f; // From the datasheet
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "Arduino.h"
|
||||
#include <hardware/gpio.h>
|
||||
|
||||
extern void __clearADCPin(pin_size_t p);
|
||||
|
||||
static PinMode _pm[30];
|
||||
|
||||
extern "C" void pinMode(pin_size_t ulPin, PinMode ulMode) __attribute__((weak, alias("__pinMode")));
|
||||
@@ -75,6 +77,10 @@ extern "C" void __pinMode(pin_size_t ulPin, PinMode ulMode) {
|
||||
return;
|
||||
}
|
||||
_pm[ulPin] = ulMode;
|
||||
|
||||
if ((ulPin >= std::min(A0, A3)) && (ulPin <= std::max(A0, A3))) {
|
||||
__clearADCPin(ulPin);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void digitalWrite(pin_size_t ulPin, PinStatus ulVal) __attribute__((weak, alias("__digitalWrite")));
|
||||
|
||||
@@ -77,7 +77,7 @@ static std::map<pin_size_t, CBInfo> _map;
|
||||
void _gpioInterruptDispatcher(uint gpio, uint32_t events) {
|
||||
(void) events;
|
||||
// Only need to lock around the std::map check, not the whole IRQ callback
|
||||
CoreMutex m(&_irqMutex);
|
||||
CoreMutex m(&_irqMutex, (FromISR | DebugEnable));
|
||||
if (m) {
|
||||
auto irq = _map.find(gpio);
|
||||
if (irq != _map.end()) {
|
||||
@@ -135,7 +135,7 @@ void attachInterruptParam(pin_size_t pin, voidFuncPtrParam callback, PinStatus m
|
||||
default: return; // ERROR
|
||||
}
|
||||
noInterrupts();
|
||||
detachInterrupt(pin);
|
||||
_detachInterruptInternal(pin);
|
||||
CBInfo cb(callback, param);
|
||||
_map.insert({pin, cb});
|
||||
gpio_set_irq_enabled_with_callback(pin, events, true, _gpioInterruptDispatcher);
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
ADC Input Library
|
||||
=================
|
||||
|
||||
The ADC pins can be sampled and recorded by an application using the same
|
||||
interface as the I2S or PWM Audio libraries. This allows analog devices which
|
||||
need to be periodically sampled to be read by applications, easily, such as:
|
||||
|
||||
* Analog electret microphones
|
||||
|
||||
* Potentiometers
|
||||
|
||||
* Light dependent resistors (LDR), etc.
|
||||
|
||||
|
||||
Up to 4 analog samples can be recorded by the hardware (``A0`` ... ``A4``), and all
|
||||
recording is done at 16-bit levels (but be aware that the ADC in the Pico will only
|
||||
ever return values between 0...4095).
|
||||
|
||||
The interface for the ``ADCInput`` device is very similar to the ``I2S`` input
|
||||
device, and most code can be ported simply by instantiating a ``ADCInput``
|
||||
object in lieu of an ``I2S`` input object and choosing the pins to record.
|
||||
|
||||
Since this uses the ADC hardware, no ``analogRead`` or ``analogReadTemp`` calls are
|
||||
allowed while in use.
|
||||
|
||||
ADC Input API
|
||||
-------------
|
||||
|
||||
ADCInput(pin0 [, pin1, pin2, pin3])
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Creates an ADC input object which will record the pins specified in the code.
|
||||
Only pins ``A0`` ... ``A4`` can be used, and they must be specified in increasing
|
||||
order (i.e. ``ADCInput(A0, A1);`` is valid, but ``ADCInput(A1, A0)`` is not.
|
||||
|
||||
bool setBuffers(size_t buffers, size_t bufferWords)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Set the number of DMA buffers and their size in 32-bit words. Call before
|
||||
``ADCInput::begin()``.
|
||||
|
||||
When running at high sample rates, it is recommended to increase the
|
||||
``bufferWords`` to 32 or higher (i.e. ``adcinput.setBuffers(4, 32);`` ).
|
||||
|
||||
bool setPins(pin_size_t pin [, pin1, pin2, pin3])
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Adjusts the pin to record. Only legal before ``ADCInput::begin()``.
|
||||
|
||||
bool setFrequency(long sampleRate)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the ADC sampling frequency, but does not start recording (however if the
|
||||
device was already running, it will wontinue to run at the new frequency). Note
|
||||
that every pin requested will be sampled at this frequency, one after the other.
|
||||
That is, if you have code like this:
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
ADCInput adc(A0, A1);
|
||||
adc.setFrequency(1000);
|
||||
|
||||
``A0`` will be sampled at 0ms, 1ms, 2ms, etc. and ``A1`` will be sampled at 0.5ms
|
||||
1.5ms, 2.5ms, etc. Each input is sampled at the proper frequency but offset in time
|
||||
since there is only one active ADC at a time.
|
||||
|
||||
bool begin()/begin(long sampleRate)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Start the ADC input up with the given sample rate, or with the value set
|
||||
using the prior ``setFrequency`` call.
|
||||
|
||||
void end()
|
||||
~~~~~~~~~~
|
||||
Stops the ADC Input device.
|
||||
|
||||
int read()
|
||||
~~~~~~~~~~
|
||||
Reads a single sample of recorded ADC data, as a 16-bit value. When multiple pins are
|
||||
recorded the first read will be pin 0, the second will be pin 1, etc. Applications need
|
||||
to keep track of which pin is being returned (normally by always reading out all pins
|
||||
at once). Will not return until data is available.
|
||||
|
||||
int available()
|
||||
~~~~~~~~~~~~~~~
|
||||
Returns the number of samples that can be read without potentially blocking.
|
||||
|
||||
void onReceive(void (\*fn)(void))
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets a callback to be called when a ADC input DMA buffer is fully filled.
|
||||
Will be in an interrupt context so the specified function must operate
|
||||
quickly and not use blocking calls like delay().
|
||||
+2
-1
@@ -18,9 +18,10 @@ void analogReadResolution(int bits)
|
||||
Determines the resolution (in bits) of the value returned by the analogRead() function.
|
||||
Default resolution is 10bit.
|
||||
|
||||
float analogReadTemp()
|
||||
float analogReadTemp(float vref = 3.3f)
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
Returns the temperature, in Celsius, of the onboard thermal sensor.
|
||||
If you have a custom Vref for the ADC on your RP2040 board, you can pass it in as a parameter. Calling with no parameters assumes the normal, 3.3V Vref.
|
||||
This reading is not exceedingly accurate and of relatively low
|
||||
resolution, so it is not a replacement for an external temperature
|
||||
sensor in many cases.
|
||||
|
||||
+2
-2
@@ -54,9 +54,9 @@ author = u'Earle F. Philhower, III'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'2.6.2'
|
||||
version = u'2.7.2'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'2.6.2'
|
||||
release = u'2.7.2'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -58,6 +58,12 @@ Sets the word clock frequency, but does not start the I2S device if not
|
||||
already running. May be called after ``I2S::begin()`` to change the
|
||||
sample rate on-the-fly.
|
||||
|
||||
bool setLSBJFormat()
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Enables LSB-J format for I2S output. In this mode the MSB comes out at the
|
||||
same time as the LRCLK changes, and not the normal 1-cycle delay. Useful for
|
||||
DAC chips like the PT8211.
|
||||
|
||||
bool begin()/begin(long sampleRate)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Start the I2S device up with the given sample rate, or with the value set
|
||||
|
||||
@@ -30,6 +30,8 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p
|
||||
Digital I/O <digital>
|
||||
EEPROM <eeprom>
|
||||
I2S Audio <i2s>
|
||||
PWM Audio <pwm>
|
||||
Microphone (and Analog Sensor) Input <adc>
|
||||
Serial USB and UARTs <serial>
|
||||
"Software Serial" PIO UART <piouart>
|
||||
Servo <servo>
|
||||
@@ -39,6 +41,8 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p
|
||||
USB (Arduino and Adafruit_TinyUSB) <usb>
|
||||
Multicore Processing <multicore>
|
||||
|
||||
Single File USB Drive <singlefile>
|
||||
|
||||
FreeRTOS SMP (multicore) <freertos>
|
||||
|
||||
WiFi (Pico-W Support) <wifi>
|
||||
|
||||
+34
-8
@@ -9,14 +9,6 @@ installation.
|
||||
|
||||
Installing via Arduino Boards Manager
|
||||
-------------------------------------
|
||||
**Note for Windows Users**: Please do not use the Windows Store version of
|
||||
the actual Arduino application because it has issues detecting attached Pico
|
||||
boards. Use the "Windows ZIP" or plain "Windows" executable (EXE) download
|
||||
direct from https://arduino.cc. and allow it to install any device drivers
|
||||
it suggests. Otherwise the Pico board may not be detected. Also, if trying
|
||||
out the 2.0 beta Arduino please install the release 1.8 version beforehand
|
||||
to ensure needed device drivers are present.
|
||||
|
||||
1. Open up the Arduino IDE and go to File->Preferences.
|
||||
2. In the dialog that pops up, enter the following URL in the "Additional Boards Manager URLs" field: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
|
||||
|
||||
@@ -28,6 +20,24 @@ to ensure needed device drivers are present.
|
||||
|
||||
.. image:: images/install2.png
|
||||
|
||||
Arduino IDE Installation Warning
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
**Note for Windows Users**: Please do not use the Windows Store version of
|
||||
the actual Arduino application because it has issues detecting attached Pico
|
||||
boards. Use the "Windows ZIP" or plain "Windows" executable (EXE) download
|
||||
direct from https://arduino.cc. and allow it to install any device drivers
|
||||
it suggests. Otherwise the Pico board may not be detected. Also, if trying
|
||||
out the 2.0 beta Arduino please install the release 1.8 version beforehand
|
||||
to ensure needed device drivers are present.
|
||||
|
||||
**Note for Linux Users**: If you installed the Arduino IDE using Flatpak, which
|
||||
is common in Pop!_OS, Fedora, and Mint, among others, you may need to configure
|
||||
Flatpak to allow the IDE access to files outside your home folder. The RP2040
|
||||
device is sometimes mounted as a folder in /opt or /media, which Flatpak will
|
||||
prevent the Arduino IDE from accessing. For Arduino IDE V2, override the filesystem
|
||||
restriction using ``flatpak override --user --filesystem=host cc.arduino.IDE2`` . For
|
||||
For Arduino IDE < V2, use ``flatpak override --user --filesystem=host cc.arduino.arduinoide``.
|
||||
|
||||
Installing via GIT
|
||||
------------------
|
||||
To install via GIT (for latest and greatest versions):
|
||||
@@ -125,6 +135,22 @@ For detailed usage information, please check the repo documentation available at
|
||||
|
||||
* https://arduino-pico.readthedocs.io/en/latest/fs.html
|
||||
|
||||
Uploading Sketches with Picotool
|
||||
--------------------------------
|
||||
Because the Picotool uses a custom device driver in the Pico to handle upload, when using the ``Upload Method->Picotool`` mode custom code needs to be run on the Pico which is not included by default for compatibility and code savings.
|
||||
|
||||
So for the first sketch you will need to rebuild (with the ``Upload Method->Picotool`` selected in them menus) and then manually hold down BOOTSEL and insert the Pico USB cable to enter the ROM bootloader.
|
||||
|
||||
After the initial upload, as long as the running binary was built using the ``Picotool`` upload method, then the normal upload process should work.
|
||||
|
||||
Note that for Ubuntu and other Linux operating systems you may need to add the following lines to a new `udev` config file(``99-picotool.rules``) to allow normal users to access the special USB device the Pico exports:
|
||||
|
||||
.. code::
|
||||
|
||||
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", MODE="660", GROUP="plugdev"' | sudo tee -a /etc/udev/rules.d/98-Picotool.rules
|
||||
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="000a", MODE="660", GROUP="plugdev"' | sudo tee -a /etc/udev/rules.d/98-Picotool.rules
|
||||
sudo udevadm control --reload
|
||||
|
||||
Uploading Sketches with Picoprobe
|
||||
---------------------------------
|
||||
If you have built a Raspberry Pi Picoprobe, you can use OpenOCD to handle your sketch uploads and for debugging with GDB.
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
PWM Audio Library
|
||||
=================
|
||||
|
||||
Relatively good quality analog audio out can be generated by using the
|
||||
RP2040 onboard PWM hardware. It can drive an amplifier for speaker output,
|
||||
or be decoupled using a capacitor to drive a headphone-level signal. Mono
|
||||
and stereo signals can be generated.
|
||||
|
||||
All samples are sent to the ``PWMAudio`` library as **signed 16 bits per sample**.
|
||||
Due to frequency limitations of the PWM hardware, at higher bit rates
|
||||
these 16-bits will automatically be reduced to the maximum the hardware
|
||||
can handle.
|
||||
|
||||
Multiple ``PWMAudio`` devices are supported, depending on availability of
|
||||
DMA channels.
|
||||
|
||||
The interface for the ``PWMAudio`` device is very similar to the ``I2S``
|
||||
device, and most code can be ported simply by instantiating a ``PWMAudio``
|
||||
object in lieu of an ``I2S`` object.
|
||||
|
||||
PWM Class API
|
||||
-------------
|
||||
|
||||
PWMAudio(pin)
|
||||
~~~~~~~~~~~~~
|
||||
Creates a mono PWM output port. Any pin can be used, but no ``analogWrite``
|
||||
calls are allowed to any other pins using that pin's PWM slice hardware.
|
||||
See the RP2040 datasheet for more details about PWM slices.
|
||||
|
||||
PWMAudio(pin, true)
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
Creates a stereo PWM output port. Only even pins (left signal) can be used, the next odd
|
||||
pin will automatically be assigned to the right channel (i.e. ``PWMAudio pwm(0, true);``
|
||||
will make GP0 as the left channel, GP1 as the right channel). The same restriction as
|
||||
in mono mode applies.
|
||||
|
||||
bool setBuffers(size_t buffers, size_t bufferWords)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Set the number of DMA buffers and their size in 32-bit words.
|
||||
Call before ``PWMAudio::begin()``.
|
||||
|
||||
When running at high sample rates, it is recommended to increase the
|
||||
``bufferWords`` to 32 or higher (i.e. ``pwm.setBuffers(4, 32);`` ).
|
||||
|
||||
bool setPin(pin_size_t pin)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Adjusts the pin to connect to the PWM audio output. Only legal before
|
||||
``PWMAudio::begin()``.
|
||||
|
||||
bool setStereo(bool stereo)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Adjusts the mono/stereo setting of the PWM audio output. Only legal before
|
||||
``PWMAudio::begin()``.
|
||||
|
||||
bool setFrequency(long sampleRate)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets the sample frequency, but does not start the PWM device (however if the
|
||||
device was already running, it will wontinue to run at the new frequency).
|
||||
|
||||
bool begin()/begin(long sampleRate)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Start the PWM Audio device up with the given sample rate, or with the value set
|
||||
using the prior ``setFrequency`` call.
|
||||
|
||||
void end()
|
||||
~~~~~~~~~~
|
||||
Stops the PWMAudio device.
|
||||
|
||||
void flush()
|
||||
~~~~~~~~~~~~
|
||||
Waits until all the PWM Audio buffers have been output.
|
||||
|
||||
size_t write(int16_t sample, bool sync = true)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Writes a single 16-bit sample to the buffer. It is up to the user to keep track
|
||||
of left/right channels when in stereo mode. In mono mode, one sample is written
|
||||
per timestep while in stereo mode two ``write()`` calls are required.
|
||||
|
||||
This call will block (wait) until space is available to actually write
|
||||
the data if ``sync`` is not specified or set to ``true``.
|
||||
|
||||
size_t write(const uint8_t \*buffer, size_t size)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Transfers number of bytes from an application buffer to the PWM Audio output buffer.
|
||||
Be aware that ``size`` is in *bytes** and not samples. Size must be a multiple
|
||||
of **4 bytes**. Will not block, so check the return value to find out how
|
||||
many bytes were actually written.
|
||||
|
||||
int availableForWrite()
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Returns the number of samples that can be written without potentially blocking.
|
||||
|
||||
void onTransmit(void (\*fn)(void))
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sets a callback to be called when a PWM Audio DMA buffer is fully transmitted.
|
||||
Will be in an interrupt context so the specified function must operate
|
||||
quickly and not use blocking calls like delay() or write to the PWM Audio.
|
||||
+2
-2
@@ -38,12 +38,12 @@ void rp2040.reboot()
|
||||
Forces a hardware reboot of the Pico.
|
||||
|
||||
Hardware Watchdog
|
||||
_________________
|
||||
-----------------
|
||||
|
||||
void rp2040.wdt_begin(uint32_t delay_ms)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Enables the hardware watchdog timer with a delay value of delay_ms
|
||||
microseconds. Note that on the RP2040, once this function has called, the
|
||||
milliseconds. Note that on the RP2040, once this function has called, the
|
||||
hardware watchdog can _not_ be disabled.
|
||||
|
||||
void rp2040.wdt_reset()
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
SingleFileDrive
|
||||
===============
|
||||
|
||||
USB drive mode is supported through the ``SingleFileDrive`` class which
|
||||
allows the Pico to emulate a FAT-formatted USB stick while preserving the
|
||||
onboard LittleFS filesystem. A single file can be exported this way without
|
||||
needing to use FAT as the onboard filesystem (FAT is not appropriate for
|
||||
flash-based devices without complicated wear leveling because of the update
|
||||
frequency of the FAT tables).
|
||||
|
||||
This emulation is very simple and only allows for the reading of the single
|
||||
file, and deleting it.
|
||||
|
||||
Callbacks, Interrupt Safety, and File Operations
|
||||
------------------------------------------------
|
||||
|
||||
The ``SingleFileDrive`` library allows your application to get a callback
|
||||
when a PC attempts to mount or unmount the Pico as a drive. Your app can
|
||||
also get a callback if the user attempts to delete the file (but your
|
||||
sketch does not actually need to delete the file, it's up to you).
|
||||
|
||||
Note that when the USB drive is mounted by a PC it is not safe for your
|
||||
main sketch to make changes to the LittleFS filesystem or the file being
|
||||
exported. So, normally, your ``onPlug`` callback will set a flag letting
|
||||
your application know not to touch the filesystem, with the ``onUnplug``
|
||||
callback clearing this flag.
|
||||
|
||||
Also, because the USB port can be connected at any time, it is important
|
||||
to disable interrupts using ``noInterrupts()`` before writing to a file
|
||||
you will be exporting (and restoring them with ``interrupts()`` afterwards).
|
||||
It is also important to ``close()`` the file after each update, or the
|
||||
on-flash version the ``SingleFileDrive`` will attempt to export may not be
|
||||
up to date causing issues later on.
|
||||
|
||||
See the included ``DataLoggerUSB`` sketch for an example of working with
|
||||
these limitations.
|
||||
|
||||
Using SingleFileDrive
|
||||
---------------------
|
||||
|
||||
Implementing the drive requires including the header file, starting LittleFS,
|
||||
defining your callbacks, and telling the library what file to export. No
|
||||
polling or other calls are required outside of your ``setup()``. (Note that
|
||||
the callback routines allow for a parameter to be passed to them, but in most
|
||||
cases this can be safely ignored.)
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
#include <LittleFS.h>
|
||||
#include <SingleFileDrive.h>
|
||||
|
||||
void myPlugCB(uint32_t data) {
|
||||
// Tell my app not to write to flash, we're connected
|
||||
}
|
||||
|
||||
void myUnplugCB(uint32_t data) {
|
||||
// I can start writing to flash again
|
||||
}
|
||||
|
||||
void myDeleteDB(uint32_t data) {
|
||||
// Maybe LittleFS.remove("myfile.txt")? or do nothing
|
||||
}
|
||||
|
||||
void setup() {
|
||||
LittleFS.begin();
|
||||
singleFileDrive.onPlug(myPlugCB);
|
||||
singleFileDrive.onUnplug(myUnplugCB);
|
||||
singleFileDrive.onDelete(myDeleteCB);
|
||||
singleFileDrive.begin("littlefsfile.csv", "Data Recorder.csv");
|
||||
// ... rest of setup ...
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Take some measurements, delay, etc.
|
||||
if (okay-to-write) {
|
||||
noInterrupts();
|
||||
File f = LittleFS.open("littlefsfile.csv", "a");
|
||||
f.printf("%d,%d,%d\n", data1, data2, data3);
|
||||
f.close();
|
||||
interrupts();
|
||||
}
|
||||
}
|
||||
@@ -40,8 +40,6 @@ Please note that WiFi on the Pico W is a work-in-progress and there are some imp
|
||||
|
||||
* Combined STA/AP mode is not supported
|
||||
|
||||
* Certain WiFi status values (RSSI, BSSID, etc.) are not available.
|
||||
|
||||
* Multicore is supported, but only one core may run ``WiFi`` code.
|
||||
|
||||
* FreeRTOS is not yet supported due to the requirement for a very different LWIP implementation. PRs always appreciated!
|
||||
|
||||
@@ -72,15 +72,14 @@
|
||||
//------------- CLASS -------------//
|
||||
#define CFG_TUD_HID (2)
|
||||
#define CFG_TUD_CDC (1)
|
||||
#define CFG_TUD_MSC (0)
|
||||
#define CFG_TUD_MSC (1)
|
||||
#define CFG_TUD_MIDI (0)
|
||||
#define CFG_TUD_VENDOR (0)
|
||||
|
||||
#define CFG_TUD_CDC_RX_BUFSIZE (256)
|
||||
#define CFG_TUD_CDC_TX_BUFSIZE (256)
|
||||
|
||||
#define CFG_TUD_MIDI_RX_BUFSIZE (64)
|
||||
#define CFG_TUD_MIDI_TX_BUFSIZE (64)
|
||||
#define CFG_TUD_MSC_EP_BUFSIZE (64)
|
||||
|
||||
// HID buffer size Should be sufficient to hold ID (if any) + Data
|
||||
#define CFG_TUD_HID_EP_BUFSIZE (64)
|
||||
|
||||
@@ -54,6 +54,9 @@ SerialPIO KEYWORD2
|
||||
setFIFOSize KEYWORD2
|
||||
setPollingMode KEYWORD2
|
||||
|
||||
digitalWriteFast KEYWORD2
|
||||
digitalReadFast KEYWORD2
|
||||
|
||||
enableDoubleResetBootloader KEYWORD2
|
||||
|
||||
OUTPUT_2MA LITERAL1
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -9,6 +9,7 @@
|
||||
-iwithprefixbefore/pico-sdk/src/common/pico_stdlib/include
|
||||
-iwithprefixbefore/pico-sdk/src/common/pico_sync/include
|
||||
-iwithprefixbefore/pico-sdk/src/common/pico_time/include
|
||||
-iwithprefixbefore/pico-sdk/src/common/pico_usb_reset_interface/include
|
||||
-iwithprefixbefore/pico-sdk/src/common/pico_util/include
|
||||
-iwithprefixbefore/pico-sdk/src/rp2040/hardware_regs/include
|
||||
-iwithprefixbefore/pico-sdk/src/rp2040/hardware_structs/include
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Mono analog microphone example using electret mike on A0
|
||||
Run using the Arduino Serial Plotter to see waveform.
|
||||
Released to the Public Domain by Earle F. Philhower, III
|
||||
|
||||
Wire the mike's VCC to 3.3V on the Pico, connect the mike's
|
||||
GND to a convenient Pico GND, and then connect mike OUT to A0
|
||||
*/
|
||||
|
||||
#include <ADCInput.h>
|
||||
|
||||
ADCInput mike(A0);
|
||||
// For stereo/dual mikes, could use this line instead
|
||||
// ADCInput(A0, A1);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
mike.begin(8000);
|
||||
|
||||
while (1) {
|
||||
Serial.printf("%d\n", mike.read());
|
||||
// For stereo/dual mikes, use this line instead
|
||||
// Serial.printf("%d %d\n", mike.read(), mike.read());
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
/* Nothing here */
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
ADCInput KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
begin KEYWORD2
|
||||
end KEYWORD2
|
||||
|
||||
setPins KEYWORD2
|
||||
setFrequency KEYWORD2
|
||||
setBuffers KEYWORD2
|
||||
|
||||
onReceive KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
@@ -0,0 +1,10 @@
|
||||
name=ADCInput
|
||||
version=1.0
|
||||
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
sentence=Records ADC values (i.e. microphone, sensors) and presents an I2S-like callback/immediate read interface
|
||||
paragraph=Records ADC values (i.e. microphone, sensors) and presents an I2S-like callback/immediate read interface
|
||||
category=Communication
|
||||
url=http://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
ADCInput
|
||||
Records ADC values (i.e. microphone, sensors) and presents an I2S-like
|
||||
callback/immediate read interface
|
||||
|
||||
Copyright (c) 2023 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 "ADCInput.h"
|
||||
#include <hardware/adc.h>
|
||||
|
||||
ADCInput::ADCInput(pin_size_t p0, pin_size_t p1, pin_size_t p2, pin_size_t p3) {
|
||||
_running = false;
|
||||
setPins(p0, p1, p2, p3);
|
||||
_freq = 48000;
|
||||
_arb = nullptr;
|
||||
_cb = nullptr;
|
||||
_buffers = 8;
|
||||
_bufferWords = 0;
|
||||
}
|
||||
|
||||
ADCInput::~ADCInput() {
|
||||
end();
|
||||
}
|
||||
|
||||
bool ADCInput::setBuffers(size_t buffers, size_t bufferWords) {
|
||||
if (_running || (buffers < 3) || (bufferWords < 8)) {
|
||||
return false;
|
||||
}
|
||||
_buffers = buffers;
|
||||
_bufferWords = bufferWords;
|
||||
return true;
|
||||
}
|
||||
|
||||
int ADCInput::_mask(pin_size_t p) {
|
||||
switch (p) {
|
||||
case 26: return 1;
|
||||
case 27: return 2;
|
||||
case 28: return 4;
|
||||
case 29: return 8;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool ADCInput::setPins(pin_size_t pin0, pin_size_t pin1, pin_size_t pin2, pin_size_t pin3) {
|
||||
if (_running) {
|
||||
return false;
|
||||
}
|
||||
_pinMask = _mask(pin0) | _mask(pin1) | _mask(pin2) | _mask(pin3);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ADCInput::setFrequency(int newFreq) {
|
||||
_freq = newFreq * __builtin_popcount(_pinMask); // Want to sample all channels at given frequency
|
||||
adc_set_clkdiv(48000000.0f / _freq - 1.0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ADCInput::onReceive(void(*fn)(void)) {
|
||||
_cb = fn;
|
||||
if (_running) {
|
||||
_arb->setCallback(_cb);
|
||||
}
|
||||
}
|
||||
|
||||
bool ADCInput::begin() {
|
||||
_running = true;
|
||||
|
||||
_isHolding = 0;
|
||||
|
||||
if (!_bufferWords) {
|
||||
_bufferWords = 16;
|
||||
}
|
||||
|
||||
// Set up the GPIOs to go to ADC
|
||||
adc_init();
|
||||
int cnt = 0;
|
||||
for (int mask = 1, pin = 26; pin <= 29; mask <<= 1, pin++) {
|
||||
if (_pinMask & mask) {
|
||||
if (!cnt) {
|
||||
adc_select_input(pin - 26);
|
||||
}
|
||||
cnt++;
|
||||
adc_gpio_init(pin);
|
||||
}
|
||||
}
|
||||
adc_set_round_robin(_pinMask);
|
||||
adc_fifo_setup(true, true, 1, false, false);
|
||||
|
||||
setFrequency(_freq);
|
||||
|
||||
_arb = new AudioBufferManager(_buffers, _bufferWords, 0, INPUT, DMA_SIZE_16);
|
||||
_arb->begin(DREQ_ADC, (volatile void*)&adc_hw->fifo);
|
||||
_arb->setCallback(_cb);
|
||||
|
||||
adc_fifo_drain();
|
||||
|
||||
adc_run(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ADCInput::end() {
|
||||
if (_running) {
|
||||
_running = false;
|
||||
delete _arb;
|
||||
_arb = nullptr;
|
||||
}
|
||||
adc_run(false);
|
||||
adc_fifo_drain();
|
||||
}
|
||||
|
||||
int ADCInput::available() {
|
||||
if (!_running) {
|
||||
return 0;
|
||||
} else {
|
||||
return _arb->available();
|
||||
}
|
||||
}
|
||||
|
||||
int ADCInput::read() {
|
||||
if (!_running) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (_hasPeeked) {
|
||||
_hasPeeked = false;
|
||||
return _peekSaved;
|
||||
}
|
||||
|
||||
if (_isHolding <= 0) {
|
||||
_arb->read(&_holdWord, true);
|
||||
_isHolding = 32;
|
||||
}
|
||||
|
||||
int ret = _holdWord & 0x0fff;
|
||||
_holdWord >>= 16;
|
||||
_isHolding -= 16;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ADCInput::peek() {
|
||||
if (!_running) {
|
||||
return -1;
|
||||
}
|
||||
if (!_hasPeeked) {
|
||||
_peekSaved = read();
|
||||
_hasPeeked = true;
|
||||
}
|
||||
return _peekSaved;
|
||||
}
|
||||
|
||||
void ADCInput::flush() {
|
||||
if (_running) {
|
||||
_arb->flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
ADCInput
|
||||
Records ADC values (i.e. microphone, sensors) and presents an I2S-like
|
||||
callback/immediate read interface
|
||||
|
||||
Copyright (c) 2023 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 "AudioBufferManager.h"
|
||||
|
||||
class ADCInput : public Stream {
|
||||
public:
|
||||
ADCInput(pin_size_t pin0, pin_size_t pin1 = 255, pin_size_t pin2 = 255, pin_size_t pin3 = 255);
|
||||
virtual ~ADCInput();
|
||||
|
||||
bool setBuffers(size_t buffers, size_t bufferWords);
|
||||
bool setFrequency(int newFreq);
|
||||
bool setPins(pin_size_t pin0, pin_size_t pin1 = 255, pin_size_t pin2 = 255, pin_size_t pin3 = 255);
|
||||
|
||||
bool begin(long sampleRate) {
|
||||
setFrequency(sampleRate);
|
||||
return begin();
|
||||
}
|
||||
|
||||
bool begin();
|
||||
void end();
|
||||
|
||||
// from Stream
|
||||
virtual int available() override;
|
||||
virtual int read() override;
|
||||
virtual int peek() override;
|
||||
virtual void flush() override;
|
||||
|
||||
// from Print, not supported
|
||||
virtual size_t write(const uint8_t *buffer, size_t size) override {
|
||||
(void) buffer;
|
||||
(void) size;
|
||||
return -1;
|
||||
}
|
||||
virtual size_t write(uint8_t x) override {
|
||||
(void) x;
|
||||
return -1;
|
||||
}
|
||||
virtual int availableForWrite() override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Note that these callback are called from **INTERRUPT CONTEXT** and hence
|
||||
// should be in RAM, not FLASH, and should be quick to execute.
|
||||
void onReceive(void(*)(void));
|
||||
|
||||
private:
|
||||
uint32_t _pinMask;
|
||||
|
||||
int _freq;
|
||||
|
||||
size_t _buffers;
|
||||
size_t _bufferWords;
|
||||
|
||||
bool _running;
|
||||
void (*_cb)();
|
||||
|
||||
bool _hasPeeked;
|
||||
uint32_t _peekSaved;
|
||||
uint32_t _holdWord = 0;
|
||||
int _isHolding = 0;
|
||||
|
||||
int _mask(pin_size_t pin);
|
||||
|
||||
AudioBufferManager *_arb;
|
||||
};
|
||||
Submodule libraries/Adafruit_TinyUSB_Arduino updated: 5be0b21c0a...e1e922d2ca
@@ -110,7 +110,7 @@ void ArduinoOTAClass::begin(bool useMDNS) {
|
||||
_useMDNS = useMDNS;
|
||||
|
||||
if (!_hostname.length()) {
|
||||
char tmp[15];
|
||||
char tmp[2 * PICO_UNIQUE_BOARD_ID_SIZE_BYTES + 6];
|
||||
sprintf(tmp, "pico-%s", rp2040.getChipID());
|
||||
_hostname = tmp;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
name=AudioBufferManager
|
||||
version=1.0.0
|
||||
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
sentence=Manages DMA buffers for audio output
|
||||
paragraph=Manages DMA buffers for audio output
|
||||
category=Device Control
|
||||
url=https://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
AudioBufferManager for Raspnerry Pi Pico RP2040
|
||||
Implements a DMA controlled linked-list series of buffers
|
||||
|
||||
Copyright (c) 2022 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 "hardware/dma.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "AudioBufferManager.h"
|
||||
|
||||
static int __channelCount = 0; // # of channels left. When we hit 0, then remove our handler
|
||||
static AudioBufferManager* __channelMap[12]; // Lets the IRQ handler figure out where to dispatch to
|
||||
|
||||
AudioBufferManager::AudioBufferManager(size_t bufferCount, size_t bufferWords, int32_t silenceSample, PinMode direction, enum dma_channel_transfer_size dmaSize) {
|
||||
_running = false;
|
||||
|
||||
// Need at least 2 DMA buffers and 1 user or this isn't going to work at all
|
||||
if (bufferCount < 3) {
|
||||
bufferCount = 3;
|
||||
}
|
||||
|
||||
_bufferCount = bufferCount;
|
||||
_wordsPerBuffer = bufferWords;
|
||||
_isOutput = direction == OUTPUT;
|
||||
_dmaSize = dmaSize;
|
||||
_overunderflow = false;
|
||||
_callback = nullptr;
|
||||
_userOff = 0;
|
||||
|
||||
// Create the silence buffer, fill with appropriate value
|
||||
_silence = new AudioBuffer;
|
||||
_silence->next = nullptr;
|
||||
_silence->buff = new uint32_t[_wordsPerBuffer];
|
||||
for (uint32_t x = 0; x < _wordsPerBuffer; x++) {
|
||||
_silence->buff[x] = silenceSample;
|
||||
}
|
||||
|
||||
// No filled buffers yet
|
||||
_filled = nullptr;
|
||||
|
||||
// Create all buffers on the empty chain
|
||||
_empty = nullptr;
|
||||
for (size_t i = 0; i < bufferCount; i++) {
|
||||
auto ab = new AudioBuffer;
|
||||
ab->buff = new uint32_t[_wordsPerBuffer];
|
||||
bzero(ab->buff, _wordsPerBuffer * 4);
|
||||
ab->next = nullptr;
|
||||
_addToList(&_empty, ab);
|
||||
}
|
||||
|
||||
_active[0] = _silence;
|
||||
_active[1] = _silence;
|
||||
}
|
||||
|
||||
AudioBufferManager::~AudioBufferManager() {
|
||||
noInterrupts();
|
||||
if (_running) {
|
||||
_running = false;
|
||||
for (auto i = 0; i < 2; i++) {
|
||||
dma_channel_set_irq0_enabled(_channelDMA[i], false);
|
||||
__channelMap[_channelDMA[i]] = nullptr;
|
||||
dma_channel_abort(_channelDMA[i]);
|
||||
dma_channel_unclaim(_channelDMA[i]);
|
||||
dma_channel_acknowledge_irq0(_channelDMA[i]);
|
||||
}
|
||||
__channelCount--;
|
||||
if (!__channelCount) {
|
||||
irq_set_enabled(DMA_IRQ_0, false);
|
||||
// TODO - how can we know if there are no other parts of the core using DMA0 IRQ??
|
||||
irq_remove_handler(DMA_IRQ_0, _irq);
|
||||
}
|
||||
}
|
||||
interrupts();
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (_active[i] != _silence) {
|
||||
_deleteAudioBuffer(_active[i]);
|
||||
}
|
||||
}
|
||||
while (_filled) {
|
||||
auto x = _filled->next;
|
||||
_deleteAudioBuffer(_filled);
|
||||
_filled = x;
|
||||
}
|
||||
while (_empty) {
|
||||
auto x = _empty->next;
|
||||
_deleteAudioBuffer(_empty);
|
||||
_empty = x;
|
||||
}
|
||||
_deleteAudioBuffer(_silence);
|
||||
}
|
||||
|
||||
void AudioBufferManager::setCallback(void (*fn)()) {
|
||||
_callback = fn;
|
||||
}
|
||||
|
||||
bool AudioBufferManager::begin(int dreq, volatile void *pioFIFOAddr) {
|
||||
_running = true;
|
||||
|
||||
// Get ping and pong DMA channels
|
||||
for (auto i = 0; i < 2; i++) {
|
||||
_channelDMA[i] = dma_claim_unused_channel(true);
|
||||
if (_channelDMA[i] == -1) {
|
||||
if (i == 1) {
|
||||
dma_channel_unclaim(_channelDMA[0]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool needSetIRQ = __channelCount == 0;
|
||||
// Need to know both channels to set up ping-pong, so do in 2 stages
|
||||
for (auto i = 0; i < 2; i++) {
|
||||
dma_channel_config c = dma_channel_get_default_config(_channelDMA[i]);
|
||||
channel_config_set_transfer_data_size(&c, _dmaSize); // 16b/32b transfers into PIO FIFO
|
||||
if (_isOutput) {
|
||||
channel_config_set_read_increment(&c, true); // Reading incrementing addresses
|
||||
channel_config_set_write_increment(&c, false); // Writing to the same FIFO address
|
||||
} else {
|
||||
channel_config_set_read_increment(&c, false); // Reading same FIFO address
|
||||
channel_config_set_write_increment(&c, true); // Writing to incrememting buffers
|
||||
}
|
||||
channel_config_set_dreq(&c, dreq); // Wait for the PIO TX FIFO specified
|
||||
channel_config_set_chain_to(&c, _channelDMA[i ^ 1]); // Start other channel when done
|
||||
channel_config_set_irq_quiet(&c, false); // Need IRQs
|
||||
|
||||
if (_isOutput) {
|
||||
dma_channel_configure(_channelDMA[i], &c, pioFIFOAddr, _silence->buff, _wordsPerBuffer * (_dmaSize == DMA_SIZE_16 ? 2 : 1), false);
|
||||
} else {
|
||||
_active[i] = _takeFromList(&_empty);
|
||||
dma_channel_configure(_channelDMA[i], &c, _active[i]->buff, pioFIFOAddr, _wordsPerBuffer * (_dmaSize == DMA_SIZE_16 ? 2 : 1), false);
|
||||
}
|
||||
dma_channel_set_irq0_enabled(_channelDMA[i], true);
|
||||
__channelMap[_channelDMA[i]] = this;
|
||||
__channelCount++;
|
||||
}
|
||||
if (needSetIRQ) {
|
||||
irq_add_shared_handler(DMA_IRQ_0, _irq, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
|
||||
irq_set_enabled(DMA_IRQ_0, true);
|
||||
}
|
||||
|
||||
dma_channel_start(_channelDMA[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Following 2 routines use volatile because the IRQ may update the "this"
|
||||
// pointer and change the list head while we are waiting. Volatile will
|
||||
// cause GCC to keep re-reading from memory and not use cached value read
|
||||
// on the first pass.
|
||||
|
||||
bool AudioBufferManager::write(uint32_t v, bool sync) {
|
||||
if (!_running || !_isOutput) {
|
||||
return false;
|
||||
}
|
||||
AudioBuffer ** volatile p = (AudioBuffer ** volatile)&_empty;
|
||||
if (!*p) {
|
||||
if (!sync) {
|
||||
return false;
|
||||
} else {
|
||||
while (!*p) {
|
||||
/* noop busy wait */
|
||||
}
|
||||
}
|
||||
}
|
||||
(*p)->buff[_userOff++] = v;
|
||||
if (_userOff == _wordsPerBuffer) {
|
||||
_addToList(&_filled, _takeFromList(p));
|
||||
_userOff = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioBufferManager::read(uint32_t *v, bool sync) {
|
||||
if (!_running || _isOutput) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioBuffer ** volatile p = (AudioBuffer ** volatile)&_filled;
|
||||
if (!*p) {
|
||||
if (!sync) {
|
||||
return false;
|
||||
} else {
|
||||
while (!*p) {
|
||||
/* noop busy wait */
|
||||
}
|
||||
}
|
||||
}
|
||||
auto ret = (*p)->buff[_userOff++];
|
||||
if (_userOff == _wordsPerBuffer) {
|
||||
_addToList(&_empty, _takeFromList(p));
|
||||
_userOff = 0;
|
||||
}
|
||||
*v = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioBufferManager::getOverUnderflow() {
|
||||
bool hold = _overunderflow;
|
||||
_overunderflow = false;
|
||||
return hold;
|
||||
}
|
||||
|
||||
int AudioBufferManager::available() {
|
||||
AudioBuffer *p = _isOutput ? _empty : _filled;
|
||||
|
||||
if (!_running || !p) {
|
||||
// No buffers available...
|
||||
return 0;
|
||||
}
|
||||
|
||||
int avail = _wordsPerBuffer - _userOff; // Currently available in this buffer
|
||||
|
||||
// Each add'l buffer has wpb spaces...
|
||||
auto x = p->next;
|
||||
while (x) {
|
||||
avail += _wordsPerBuffer;
|
||||
x = x->next;
|
||||
}
|
||||
return avail;
|
||||
}
|
||||
|
||||
void AudioBufferManager::flush() {
|
||||
AudioBuffer ** volatile a = (AudioBuffer ** volatile)&_active[0];
|
||||
AudioBuffer ** volatile b = (AudioBuffer ** volatile)&_active[1];
|
||||
AudioBuffer ** volatile c = (AudioBuffer ** volatile)&_filled;
|
||||
while (*c && (*b != (AudioBuffer * volatile)_silence) && (*a != (AudioBuffer * volatile)_silence)) {
|
||||
// busy wait until all user written data enroute
|
||||
}
|
||||
}
|
||||
|
||||
void __not_in_flash_func(AudioBufferManager::_dmaIRQ)(int channel) {
|
||||
if (!_running) {
|
||||
return;
|
||||
}
|
||||
if (_isOutput) {
|
||||
if (_active[0] != _silence) {
|
||||
_addToList(&_empty, _active[0]);
|
||||
}
|
||||
_active[0] = _active[1];
|
||||
if (!_filled) {
|
||||
_active[1] = _silence;
|
||||
} else {
|
||||
_active[1] = _takeFromList(&_filled);
|
||||
}
|
||||
_overunderflow = _overunderflow | (_active[1] == _silence);
|
||||
dma_channel_set_read_addr(channel, _active[0]->buff, false);
|
||||
} else {
|
||||
if (_empty) {
|
||||
_addToList(&_filled, _active[0]);
|
||||
_active[0] = _active[1];
|
||||
_active[1] = _takeFromList(&_empty);
|
||||
} else {
|
||||
_overunderflow = true;
|
||||
}
|
||||
dma_channel_set_write_addr(channel, _active[0]->buff, false);
|
||||
}
|
||||
dma_channel_set_trans_count(channel, _wordsPerBuffer * (_dmaSize == DMA_SIZE_16 ? 2 : 1), false);
|
||||
dma_channel_acknowledge_irq0(channel);
|
||||
if (_callback) {
|
||||
_callback();
|
||||
}
|
||||
}
|
||||
|
||||
void __not_in_flash_func(AudioBufferManager::_irq)() {
|
||||
for (size_t i = 0; i < sizeof(__channelMap); i++) {
|
||||
if (dma_channel_get_irq0_status(i) && __channelMap[i]) {
|
||||
__channelMap[i]->_dmaIRQ(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
AudioBufferManager for Rasperry Pi Pico
|
||||
Implements a DMA controlled linked-list series of buffers
|
||||
|
||||
Copyright (c) 2022 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 "hardware/dma.h"
|
||||
|
||||
class AudioBufferManager {
|
||||
public:
|
||||
AudioBufferManager(size_t bufferCount, size_t bufferWords, int32_t silenceSample, PinMode direction = OUTPUT, enum dma_channel_transfer_size dmaSize = DMA_SIZE_32);
|
||||
~AudioBufferManager();
|
||||
|
||||
void setCallback(void (*fn)());
|
||||
|
||||
bool begin(int dreq, volatile void *pioFIFOAddr);
|
||||
|
||||
bool write(uint32_t v, bool sync = true);
|
||||
bool read(uint32_t *v, bool sync = true);
|
||||
void flush();
|
||||
|
||||
bool getOverUnderflow();
|
||||
int available();
|
||||
|
||||
private:
|
||||
void _dmaIRQ(int channel);
|
||||
static void _irq();
|
||||
|
||||
typedef struct AudioBuffer {
|
||||
struct AudioBuffer *next;
|
||||
uint32_t *buff;
|
||||
} AudioBuffer;
|
||||
|
||||
bool _running = false;
|
||||
|
||||
AudioBuffer *_silence = nullptr; // A single silence buffer to be looped on underflow
|
||||
AudioBuffer *_filled = nullptr; // List of buffers ready to be played
|
||||
AudioBuffer *_empty = nullptr; // List of buffers waiting to be filled. *_empty = currently writing
|
||||
AudioBuffer *_active[2] = { nullptr, nullptr }; // The 2 buffers currently in use for DMA
|
||||
|
||||
// Can't use std::list because we need to put in RAM for IRQ use, so roll our own
|
||||
void __not_in_flash_func(_addToList)(AudioBuffer **list, AudioBuffer *element) {
|
||||
noInterrupts();
|
||||
// Find end of list, if any
|
||||
while ((*list) && ((*list)->next != nullptr)) {
|
||||
list = &(*list)->next;
|
||||
}
|
||||
if (*list) {
|
||||
(*list)->next = element;
|
||||
} else {
|
||||
*list = element;
|
||||
}
|
||||
element->next = nullptr; // Belt and braces
|
||||
interrupts();
|
||||
}
|
||||
|
||||
AudioBuffer *__not_in_flash_func(_takeFromList)(AudioBuffer **list) {
|
||||
noInterrupts();
|
||||
auto ret = *list;
|
||||
if (ret) {
|
||||
*list = ret->next;
|
||||
}
|
||||
interrupts();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void _deleteAudioBuffer(AudioBuffer *ab) {
|
||||
delete[] ab->buff;
|
||||
delete ab;
|
||||
}
|
||||
|
||||
int _bitsPerSample;
|
||||
size_t _wordsPerBuffer;
|
||||
size_t _bufferCount;
|
||||
enum dma_channel_transfer_size _dmaSize;
|
||||
bool _isOutput;
|
||||
|
||||
int _channelDMA[2];
|
||||
void (*_callback)();
|
||||
|
||||
bool _overunderflow;
|
||||
|
||||
// User buffer pointer
|
||||
size_t _userOff = 0;
|
||||
};
|
||||
@@ -45,7 +45,7 @@ void EEPROMClass::begin(size_t size) {
|
||||
size = 4096;
|
||||
}
|
||||
|
||||
_size = (size + 255) & (~255); // Flash writes limited to 256 byte boundaries
|
||||
size = (size + 255) & (~255); // Flash writes limited to 256 byte boundaries
|
||||
|
||||
// In case begin() is called a 2nd+ time, don't reallocate if size is the same
|
||||
if (_data && size != _size) {
|
||||
@@ -55,6 +55,8 @@ void EEPROMClass::begin(size_t size) {
|
||||
_data = new uint8_t[size];
|
||||
}
|
||||
|
||||
_size = size;
|
||||
|
||||
memcpy(_data, _sector, _size);
|
||||
|
||||
_dirty = false; //make sure dirty is cleared in case begin() is called 2nd+ time
|
||||
|
||||
@@ -30,7 +30,7 @@ void ps() {
|
||||
Serial.printf("# Tasks: %d\n", tasks);
|
||||
Serial.println("ID, NAME, STATE, PRIO, CYCLES");
|
||||
for (int i=0; i < tasks; i++) {
|
||||
Serial.printf("%d: %-16s %-10s %d %lu\n", i, pxTaskStatusArray[i].pcTaskName, eTaskStateName[pxTaskStatusArray[i].eCurrentState], pxTaskStatusArray[i].uxCurrentPriority, pxTaskStatusArray[i].ulRunTimeCounter);
|
||||
Serial.printf("%d: %-16s %-10s %d %lu\n", i, pxTaskStatusArray[i].pcTaskName, eTaskStateName[pxTaskStatusArray[i].eCurrentState], (int)pxTaskStatusArray[i].uxCurrentPriority, pxTaskStatusArray[i].ulRunTimeCounter);
|
||||
}
|
||||
delete[] pxTaskStatusArray;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ volatile bool __usbInitted = false;
|
||||
|
||||
static void __core0(void *params) {
|
||||
(void) params;
|
||||
#ifndef NO_USB
|
||||
#if !defined(NO_USB) && !defined(USE_TINYUSB)
|
||||
while (!__usbInitted) {
|
||||
delay(1);
|
||||
}
|
||||
@@ -81,7 +81,7 @@ static void __core0(void *params) {
|
||||
|
||||
static void __core1(void *params) {
|
||||
(void) params;
|
||||
#ifndef NO_USB
|
||||
#if !defined(NO_USB) && !defined(USE_TINYUSB)
|
||||
while (!__usbInitted) {
|
||||
delay(1);
|
||||
}
|
||||
@@ -414,6 +414,10 @@ extern "C" {
|
||||
xSemaphoreTake(mtx, portMAX_DELAY);
|
||||
}
|
||||
|
||||
void __freertos_mutex_take_from_isr(SemaphoreHandle_t mtx) {
|
||||
xSemaphoreTakeFromISR(mtx, NULL);
|
||||
}
|
||||
|
||||
int __freertos_mutex_try_take(SemaphoreHandle_t mtx) {
|
||||
return xSemaphoreTake(mtx, 0);
|
||||
}
|
||||
@@ -422,6 +426,10 @@ extern "C" {
|
||||
xSemaphoreGive(mtx);
|
||||
}
|
||||
|
||||
void __freertos_mutex_give_from_isr(SemaphoreHandle_t mtx) {
|
||||
xSemaphoreGiveFromISR(mtx, NULL);
|
||||
}
|
||||
|
||||
void __freertos_recursive_mutex_take(SemaphoreHandle_t mtx) {
|
||||
xSemaphoreTakeRecursive(mtx, portMAX_DELAY);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
simple demo to show sha1 calculation
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include <Hash.h>
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
// usage as String
|
||||
// SHA1:a9993e364706816aba3e25717850c26c9cd0d89d
|
||||
|
||||
Serial.print("SHA1:");
|
||||
Serial.println(sha1("abc"));
|
||||
|
||||
// usage as ptr
|
||||
// SHA1:a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
|
||||
uint8_t hash[20];
|
||||
sha1("test", &hash[0]);
|
||||
|
||||
Serial.print("SHA1:");
|
||||
for (uint16_t i = 0; i < 20; i++) { Serial.printf("%02x", hash[i]); }
|
||||
Serial.println();
|
||||
|
||||
delay(1000);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map For Hash
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Library (KEYWORD3)
|
||||
#######################################
|
||||
|
||||
Hash KEYWORD3 RESERVED_WORD
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
sha1 KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
@@ -0,0 +1,10 @@
|
||||
name=Hash
|
||||
version=1.0
|
||||
author=Markus Sattler
|
||||
maintainer=Markus Sattler
|
||||
sentence=Generate Hash from data
|
||||
paragraph=
|
||||
category=Data Processing
|
||||
url=
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* @file Hash.cpp
|
||||
* @date 20.05.2015
|
||||
* @author Markus Sattler
|
||||
*
|
||||
* Copyright (c) 2015 Markus Sattler. All rights reserved.
|
||||
* This file is part of the esp8266 core for Arduino environment.
|
||||
*
|
||||
* 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 <bearssl/bearssl_hash.h>
|
||||
|
||||
#include "Hash.h"
|
||||
|
||||
/**
|
||||
* create a sha1 hash from data
|
||||
* @param data uint8_t *
|
||||
* @param size uint32_t
|
||||
* @param hash uint8_t[20]
|
||||
*/
|
||||
void sha1(const uint8_t* data, uint32_t size, uint8_t hash[20]) {
|
||||
br_sha1_context ctx;
|
||||
|
||||
#ifdef DEBUG_SHA1
|
||||
os_printf("DATA:");
|
||||
for(uint16_t i = 0; i < size; i++) {
|
||||
os_printf("%02X", data[i]);
|
||||
}
|
||||
os_printf("\n");
|
||||
os_printf("DATA:");
|
||||
for(uint16_t i = 0; i < size; i++) {
|
||||
os_printf("%c", data[i]);
|
||||
}
|
||||
os_printf("\n");
|
||||
#endif
|
||||
|
||||
br_sha1_init(&ctx);
|
||||
br_sha1_update(&ctx, data, size);
|
||||
br_sha1_out(&ctx, hash);
|
||||
|
||||
#ifdef DEBUG_SHA1
|
||||
os_printf("SHA1:");
|
||||
for(uint16_t i = 0; i < 20; i++) {
|
||||
os_printf("%02X", hash[i]);
|
||||
}
|
||||
os_printf("\n\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void sha1(const char* data, uint32_t size, uint8_t hash[20]) {
|
||||
sha1((const uint8_t *) data, size, hash);
|
||||
}
|
||||
|
||||
void sha1(const String& data, uint8_t hash[20]) {
|
||||
sha1(data.c_str(), data.length(), hash);
|
||||
}
|
||||
|
||||
String sha1(const uint8_t* data, uint32_t size) {
|
||||
uint8_t hash[20];
|
||||
String hashStr((const char*)nullptr);
|
||||
hashStr.reserve(20 * 2 + 1);
|
||||
|
||||
sha1(&data[0], size, &hash[0]);
|
||||
|
||||
for(uint16_t i = 0; i < 20; i++) {
|
||||
char hex[3];
|
||||
snprintf(hex, sizeof(hex), "%02x", hash[i]);
|
||||
hashStr += hex;
|
||||
}
|
||||
|
||||
return hashStr;
|
||||
}
|
||||
|
||||
String sha1(const char* data, uint32_t size) {
|
||||
return sha1((const uint8_t*) data, size);
|
||||
}
|
||||
|
||||
String sha1(const String& data) {
|
||||
return sha1(data.c_str(), data.length());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @file Hash.h
|
||||
* @date 20.05.2015
|
||||
* @author Markus Sattler
|
||||
*
|
||||
* Copyright (c) 2015 Markus Sattler. All rights reserved.
|
||||
* This file is part of the esp8266 core for Arduino environment.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef HASH_H_
|
||||
#define HASH_H_
|
||||
|
||||
//#define DEBUG_SHA1
|
||||
|
||||
void sha1(const uint8_t* data, uint32_t size, uint8_t hash[20]);
|
||||
void sha1(const char* data, uint32_t size, uint8_t hash[20]);
|
||||
void sha1(const String& data, uint8_t hash[20]);
|
||||
|
||||
String sha1(const uint8_t* data, uint32_t size);
|
||||
String sha1(const char* data, uint32_t size);
|
||||
String sha1(const String& data);
|
||||
|
||||
#endif /* HASH_H_ */
|
||||
@@ -18,6 +18,8 @@ setBCLK KEYWORD2
|
||||
setDATA KEYWORD2
|
||||
setBitsPerSample KEYWORD2
|
||||
setFrequency KEYWORD2
|
||||
setBuffers KEYWORD2
|
||||
setLSBJFormat KEYWORD2
|
||||
|
||||
read8 KEYWORD2
|
||||
read16 KEYWORD2
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
AudioRingBuffer for Raspnerry Pi Pico RP2040
|
||||
Implements a ring buffer for PIO DMA for I2S read or write
|
||||
|
||||
Copyright (c) 2022 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 <vector>
|
||||
#include "hardware/dma.h"
|
||||
#include "hardware/irq.h"
|
||||
#include "hardware/pio.h"
|
||||
#include "pio_i2s.pio.h"
|
||||
#include "AudioRingBuffer.h"
|
||||
|
||||
static int __channelCount = 0; // # of channels left. When we hit 0, then remove our handler
|
||||
static AudioRingBuffer* __channelMap[12]; // Lets the IRQ handler figure out where to dispatch to
|
||||
|
||||
AudioRingBuffer::AudioRingBuffer(size_t bufferCount, size_t bufferWords, int32_t silenceSample, PinMode direction) {
|
||||
_running = false;
|
||||
_silenceSample = silenceSample;
|
||||
_bufferCount = bufferCount;
|
||||
_wordsPerBuffer = bufferWords;
|
||||
_isOutput = direction == OUTPUT;
|
||||
_overunderflow = false;
|
||||
_callback = nullptr;
|
||||
_userBuffer = -1;
|
||||
_userOff = 0;
|
||||
for (size_t i = 0; i < bufferCount; i++) {
|
||||
auto ab = new AudioBuffer;
|
||||
ab->buff = new uint32_t[_wordsPerBuffer];
|
||||
ab->empty = true;
|
||||
_buffers.push_back(ab);
|
||||
}
|
||||
}
|
||||
|
||||
AudioRingBuffer::~AudioRingBuffer() {
|
||||
if (_running) {
|
||||
for (auto i = 0; i < 2; i++) {
|
||||
dma_channel_set_irq0_enabled(_channelDMA[i], false);
|
||||
dma_channel_unclaim(_channelDMA[i]);
|
||||
__channelMap[_channelDMA[i]] = nullptr;
|
||||
}
|
||||
while (_buffers.size()) {
|
||||
auto ab = _buffers.back();
|
||||
_buffers.pop_back();
|
||||
delete[] ab->buff;
|
||||
delete ab;
|
||||
}
|
||||
__channelCount--;
|
||||
if (!__channelCount) {
|
||||
irq_set_enabled(DMA_IRQ_0, false);
|
||||
// TODO - how can we know if there are no other parts of the core using DMA0 IRQ??
|
||||
irq_remove_handler(DMA_IRQ_0, _irq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioRingBuffer::setCallback(void (*fn)()) {
|
||||
_callback = fn;
|
||||
}
|
||||
|
||||
bool AudioRingBuffer::begin(int dreq, volatile void *pioFIFOAddr) {
|
||||
_running = true;
|
||||
// Set all buffers to silence, empty
|
||||
for (auto buff : _buffers) {
|
||||
buff->empty = true;
|
||||
if (_isOutput) {
|
||||
for (uint32_t x = 0; x < _wordsPerBuffer; x++) {
|
||||
buff->buff[x] = _silenceSample;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Get ping and pong DMA channels
|
||||
for (auto i = 0; i < 2; i++) {
|
||||
_channelDMA[i] = dma_claim_unused_channel(true);
|
||||
if (_channelDMA[i] == -1) {
|
||||
if (i == 1) {
|
||||
dma_channel_unclaim(_channelDMA[0]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool needSetIRQ = __channelCount == 0;
|
||||
// Need to know both channels to set up ping-pong, so do in 2 stages
|
||||
for (auto i = 0; i < 2; i++) {
|
||||
dma_channel_config c = dma_channel_get_default_config(_channelDMA[i]);
|
||||
channel_config_set_transfer_data_size(&c, DMA_SIZE_32); // 32b transfers into PIO FIFO
|
||||
if (_isOutput) {
|
||||
channel_config_set_read_increment(&c, true); // Reading incrementing addresses
|
||||
channel_config_set_write_increment(&c, false); // Writing to the same FIFO address
|
||||
} else {
|
||||
channel_config_set_read_increment(&c, false); // Reading same FIFO address
|
||||
channel_config_set_write_increment(&c, true); // Writing to incrememting buffers
|
||||
}
|
||||
channel_config_set_dreq(&c, dreq); // Wait for the PIO TX FIFO specified
|
||||
channel_config_set_chain_to(&c, _channelDMA[i ^ 1]); // Start other channel when done
|
||||
channel_config_set_irq_quiet(&c, false); // Need IRQs
|
||||
|
||||
if (_isOutput) {
|
||||
dma_channel_configure(_channelDMA[i], &c, pioFIFOAddr, _buffers[i]->buff, _wordsPerBuffer, false);
|
||||
} else {
|
||||
dma_channel_configure(_channelDMA[i], &c, _buffers[i]->buff, pioFIFOAddr, _wordsPerBuffer, false);
|
||||
}
|
||||
dma_channel_set_irq0_enabled(_channelDMA[i], true);
|
||||
__channelMap[_channelDMA[i]] = this;
|
||||
__channelCount++;
|
||||
}
|
||||
if (needSetIRQ) {
|
||||
irq_add_shared_handler(DMA_IRQ_0, _irq, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
|
||||
irq_set_enabled(DMA_IRQ_0, true);
|
||||
}
|
||||
_curBuffer = 0;
|
||||
_nextBuffer = 2 % _bufferCount;
|
||||
dma_channel_start(_channelDMA[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioRingBuffer::write(uint32_t v, bool sync) {
|
||||
if (!_running || !_isOutput) {
|
||||
return false;
|
||||
}
|
||||
if (_userBuffer == -1) {
|
||||
// First write or overflow, pick spot 2 buffers out
|
||||
_userBuffer = (_nextBuffer + 2) % _bufferCount;
|
||||
_userOff = 0;
|
||||
}
|
||||
if (!_buffers[_userBuffer]->empty) {
|
||||
if (!sync) {
|
||||
return false;
|
||||
} else {
|
||||
while (!_buffers[_userBuffer]->empty) {
|
||||
/* noop busy wait */
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_userBuffer == _curBuffer) {
|
||||
if (!sync) {
|
||||
return false;
|
||||
} else {
|
||||
while (_userBuffer == _curBuffer) {
|
||||
/* noop busy wait */
|
||||
}
|
||||
}
|
||||
}
|
||||
_buffers[_userBuffer]->buff[_userOff++] = v;
|
||||
if (_userOff == _wordsPerBuffer) {
|
||||
_buffers[_userBuffer]->empty = false;
|
||||
_userBuffer = (_userBuffer + 1) % _bufferCount;
|
||||
_userOff = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioRingBuffer::read(uint32_t *v, bool sync) {
|
||||
if (!_running || _isOutput) {
|
||||
return false;
|
||||
}
|
||||
if (_userBuffer == -1) {
|
||||
// First write or overflow, pick last filled buffer
|
||||
_userBuffer = (_curBuffer - 1 + _bufferCount) % _bufferCount;
|
||||
_userOff = 0;
|
||||
}
|
||||
if (_buffers[_userBuffer]->empty) {
|
||||
if (!sync) {
|
||||
return false;
|
||||
} else {
|
||||
while (_buffers[_userBuffer]->empty) {
|
||||
/* noop busy wait */
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_userBuffer == _curBuffer) {
|
||||
if (!sync) {
|
||||
return false;
|
||||
} else {
|
||||
while (_userBuffer == _curBuffer) {
|
||||
/* noop busy wait */
|
||||
}
|
||||
}
|
||||
}
|
||||
auto ret = _buffers[_userBuffer]->buff[_userOff++];
|
||||
if (_userOff == _wordsPerBuffer) {
|
||||
_buffers[_userBuffer]->empty = true;
|
||||
_userBuffer = (_userBuffer + 1) % _bufferCount;
|
||||
_userOff = 0;
|
||||
}
|
||||
*v = ret;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioRingBuffer::getOverUnderflow() {
|
||||
bool hold = _overunderflow;
|
||||
_overunderflow = false;
|
||||
return hold;
|
||||
}
|
||||
|
||||
int AudioRingBuffer::available() {
|
||||
if (!_running) {
|
||||
return 0;
|
||||
}
|
||||
int avail;
|
||||
avail = _wordsPerBuffer - _userOff;
|
||||
avail += ((_bufferCount + _curBuffer - _userBuffer) % _bufferCount) * _wordsPerBuffer;
|
||||
return avail;
|
||||
}
|
||||
|
||||
void AudioRingBuffer::flush() {
|
||||
while (_curBuffer != _userBuffer) {
|
||||
// busy wait
|
||||
}
|
||||
}
|
||||
|
||||
void __not_in_flash_func(AudioRingBuffer::_dmaIRQ)(int channel) {
|
||||
if (_isOutput) {
|
||||
for (uint32_t x = 0; x < _wordsPerBuffer; x++) {
|
||||
_buffers[_curBuffer]->buff[x] = _silenceSample;
|
||||
}
|
||||
_buffers[_curBuffer]-> empty = true;
|
||||
_overunderflow = _overunderflow | _buffers[_nextBuffer]->empty;
|
||||
dma_channel_set_read_addr(channel, _buffers[_nextBuffer]->buff, false);
|
||||
} else {
|
||||
_buffers[_curBuffer]-> empty = false;
|
||||
_overunderflow = _overunderflow | !_buffers[_nextBuffer]->empty;
|
||||
dma_channel_set_write_addr(channel, _buffers[_nextBuffer]->buff, false);
|
||||
}
|
||||
dma_channel_set_trans_count(channel, _wordsPerBuffer, false);
|
||||
_curBuffer = (_curBuffer + 1) % _bufferCount;
|
||||
_nextBuffer = (_nextBuffer + 1) % _bufferCount;
|
||||
dma_channel_acknowledge_irq0(channel);
|
||||
if (_callback) {
|
||||
_callback();
|
||||
}
|
||||
}
|
||||
|
||||
void __not_in_flash_func(AudioRingBuffer::_irq)() {
|
||||
for (size_t i = 0; i < sizeof(__channelMap); i++) {
|
||||
if (dma_channel_get_irq0_status(i) && __channelMap[i]) {
|
||||
__channelMap[i]->_dmaIRQ(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
AudioRingBuffer for Rasperry Pi Pico
|
||||
Implements a ring buffer for PIO DMA for I2S read or write
|
||||
|
||||
Copyright (c) 2022 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 <vector>
|
||||
|
||||
class AudioRingBuffer {
|
||||
public:
|
||||
AudioRingBuffer(size_t bufferCount, size_t bufferWords, int32_t silenceSample, PinMode direction = OUTPUT);
|
||||
~AudioRingBuffer();
|
||||
|
||||
void setCallback(void (*fn)());
|
||||
|
||||
bool begin(int dreq, volatile void *pioFIFOAddr);
|
||||
|
||||
bool write(uint32_t v, bool sync = true);
|
||||
bool read(uint32_t *v, bool sync = true);
|
||||
void flush();
|
||||
|
||||
bool getOverUnderflow();
|
||||
int available();
|
||||
|
||||
private:
|
||||
void _dmaIRQ(int channel);
|
||||
static void _irq();
|
||||
|
||||
typedef struct {
|
||||
uint32_t *buff;
|
||||
volatile bool empty;
|
||||
} AudioBuffer;
|
||||
|
||||
bool _running = false;
|
||||
std::vector<AudioBuffer*> _buffers;
|
||||
volatile int _curBuffer;
|
||||
volatile int _nextBuffer;
|
||||
size_t _chunkSampleCount;
|
||||
int _bitsPerSample;
|
||||
size_t _wordsPerBuffer;
|
||||
size_t _bufferCount;
|
||||
bool _isOutput;
|
||||
int32_t _silenceSample;
|
||||
int _channelDMA[2];
|
||||
void (*_callback)();
|
||||
|
||||
bool _overunderflow;
|
||||
|
||||
// User buffer pointer
|
||||
int _userBuffer = -1;
|
||||
size_t _userOff = 0;
|
||||
};
|
||||
+63
-26
@@ -27,15 +27,31 @@ I2S::I2S(PinMode direction) {
|
||||
_running = false;
|
||||
_bps = 16;
|
||||
_writtenHalf = false;
|
||||
_isOutput = direction == OUTPUT;
|
||||
_pinBCLK = 26;
|
||||
_pinDOUT = 28;
|
||||
#ifdef PIN_I2S_BCLK
|
||||
_pinBCLK = PIN_I2S_BCLK;
|
||||
#endif
|
||||
|
||||
#ifdef PIN_I2S_DOUT
|
||||
if (_isOutput) {
|
||||
_pinDOUT = PIN_I2S_DOUT;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PIN_I2S_DIN
|
||||
if (!_isOutput) {
|
||||
_pinDOUT = PIN_I2S_DIN;
|
||||
}
|
||||
#endif
|
||||
_freq = 48000;
|
||||
_arb = nullptr;
|
||||
_isOutput = direction == OUTPUT;
|
||||
_cb = nullptr;
|
||||
_buffers = 8;
|
||||
_bufferWords = 16;
|
||||
_bufferWords = 0;
|
||||
_silenceSample = 0;
|
||||
_isLSBJ = false;
|
||||
}
|
||||
|
||||
I2S::~I2S() {
|
||||
@@ -84,6 +100,14 @@ bool I2S::setFrequency(int newFreq) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool I2S::setLSBJFormat() {
|
||||
if (_running || !_isOutput) {
|
||||
return false;
|
||||
}
|
||||
_isLSBJ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void I2S::onTransmit(void(*fn)(void)) {
|
||||
if (_isOutput) {
|
||||
_cb = fn;
|
||||
@@ -106,10 +130,14 @@ bool I2S::begin() {
|
||||
_running = true;
|
||||
_hasPeeked = false;
|
||||
int off = 0;
|
||||
_i2s = new PIOProgram(_isOutput ? &pio_i2s_out_program : &pio_i2s_in_program);
|
||||
_i2s = new PIOProgram(_isOutput ? (_isLSBJ ? &pio_lsbj_out_program : &pio_i2s_out_program) : &pio_i2s_in_program);
|
||||
_i2s->prepare(&_pio, &_sm, &off);
|
||||
if (_isOutput) {
|
||||
pio_i2s_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps);
|
||||
if (_isLSBJ) {
|
||||
pio_lsbj_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps);
|
||||
} else {
|
||||
pio_i2s_out_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps);
|
||||
}
|
||||
} else {
|
||||
pio_i2s_in_program_init(_pio, _sm, off, _pinDOUT, _pinBCLK, _bps);
|
||||
}
|
||||
@@ -121,7 +149,10 @@ bool I2S::begin() {
|
||||
uint16_t a = _silenceSample & 0xffff;
|
||||
_silenceSample = (a << 16) | a;
|
||||
}
|
||||
_arb = new AudioRingBuffer(_buffers, _bufferWords, _silenceSample, _isOutput ? OUTPUT : INPUT);
|
||||
if (!_bufferWords) {
|
||||
_bufferWords = 16 * (_bps == 32 ? 2 : 1);
|
||||
}
|
||||
_arb = new AudioBufferManager(_buffers, _bufferWords, _silenceSample, _isOutput ? OUTPUT : INPUT);
|
||||
_arb->begin(pio_get_dreq(_pio, _sm, _isOutput), _isOutput ? &_pio->txf[_sm] : (volatile void*)&_pio->rxf[_sm]);
|
||||
_arb->setCallback(_cb);
|
||||
pio_sm_set_enabled(_pio, _sm, true);
|
||||
@@ -130,18 +161,24 @@ bool I2S::begin() {
|
||||
}
|
||||
|
||||
void I2S::end() {
|
||||
_running = false;
|
||||
delete _arb;
|
||||
_arb = nullptr;
|
||||
delete _i2s;
|
||||
_i2s = nullptr;
|
||||
if (_running) {
|
||||
pio_sm_set_enabled(_pio, _sm, false);
|
||||
_running = false;
|
||||
delete _arb;
|
||||
_arb = nullptr;
|
||||
delete _i2s;
|
||||
_i2s = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int I2S::available() {
|
||||
if (!_running || _isOutput) {
|
||||
if (!_running) {
|
||||
return 0;
|
||||
} else if (_isOutput) {
|
||||
return availableForWrite(); // Do what I mean, not what I say
|
||||
} else {
|
||||
return _arb->available();
|
||||
}
|
||||
return _arb->available();
|
||||
}
|
||||
|
||||
int I2S::read() {
|
||||
@@ -154,9 +191,9 @@ int I2S::read() {
|
||||
return _peekSaved;
|
||||
}
|
||||
|
||||
if (_wasHolding <= 0) {
|
||||
if (_isHolding <= 0) {
|
||||
read(&_holdWord, true);
|
||||
_wasHolding = 32;
|
||||
_isHolding = 32;
|
||||
}
|
||||
|
||||
int ret;
|
||||
@@ -164,18 +201,18 @@ int I2S::read() {
|
||||
case 8:
|
||||
ret = _holdWord >> 24;
|
||||
_holdWord <<= 8;
|
||||
_wasHolding -= 8;
|
||||
_isHolding -= 8;
|
||||
return ret;
|
||||
case 16:
|
||||
ret = _holdWord >> 16;
|
||||
_holdWord <<= 16;
|
||||
_wasHolding -= 32;
|
||||
_isHolding -= 32;
|
||||
return ret;
|
||||
case 24:
|
||||
case 32:
|
||||
default:
|
||||
ret = _holdWord;
|
||||
_wasHolding = 0;
|
||||
_isHolding = 0;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -204,26 +241,26 @@ size_t I2S::_writeNatural(int32_t s) {
|
||||
switch (_bps) {
|
||||
case 8:
|
||||
_holdWord |= s & 0xff;
|
||||
if (_wasHolding >= 24) {
|
||||
if (_isHolding >= 24) {
|
||||
auto ret = write(_holdWord, true);
|
||||
_holdWord = 0;
|
||||
_wasHolding = 0;
|
||||
_isHolding = 0;
|
||||
return ret;
|
||||
} else {
|
||||
_holdWord <<= 8;
|
||||
_wasHolding += 8;
|
||||
_isHolding += 8;
|
||||
return 1;
|
||||
}
|
||||
case 16:
|
||||
_holdWord |= s & 0xffff;
|
||||
if (_wasHolding) {
|
||||
if (_isHolding) {
|
||||
auto ret = write(_holdWord, true);
|
||||
_holdWord = 0;
|
||||
_wasHolding = 0;
|
||||
_isHolding = 0;
|
||||
return ret;
|
||||
} else {
|
||||
_holdWord <<= 16;
|
||||
_wasHolding = 16;
|
||||
_isHolding = 16;
|
||||
return 1;
|
||||
}
|
||||
case 24:
|
||||
@@ -280,13 +317,13 @@ bool I2S::read8(int8_t *l, int8_t *r) {
|
||||
if (!_running || _isOutput) {
|
||||
return false;
|
||||
}
|
||||
if (_wasHolding) {
|
||||
if (_isHolding) {
|
||||
*l = (_holdWord >> 8) & 0xff;
|
||||
*r = (_holdWord >> 0) & 0xff;
|
||||
_wasHolding = 0;
|
||||
_isHolding = 0;
|
||||
} else {
|
||||
read(&_holdWord, true);
|
||||
_wasHolding = 16;
|
||||
_isHolding = 16;
|
||||
*l = (_holdWord >> 24) & 0xff;
|
||||
*r = (_holdWord >> 16) & 0xff;
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <vector>
|
||||
#include "AudioRingBuffer.h"
|
||||
#include "AudioBufferManager.h"
|
||||
|
||||
class I2S : public Stream {
|
||||
public:
|
||||
@@ -34,6 +33,7 @@ public:
|
||||
bool setBitsPerSample(int bps);
|
||||
bool setBuffers(size_t buffers, size_t bufferWords, int32_t silenceSample = 0);
|
||||
bool setFrequency(int newFreq);
|
||||
bool setLSBJFormat();
|
||||
|
||||
bool begin(long sampleRate) {
|
||||
setFrequency(sampleRate);
|
||||
@@ -105,6 +105,7 @@ private:
|
||||
size_t _buffers;
|
||||
size_t _bufferWords;
|
||||
int32_t _silenceSample;
|
||||
bool _isLSBJ;
|
||||
bool _isOutput;
|
||||
|
||||
bool _running;
|
||||
@@ -117,11 +118,11 @@ private:
|
||||
bool _writtenHalf;
|
||||
|
||||
int32_t _holdWord = 0;
|
||||
int _wasHolding = 0;
|
||||
int _isHolding = 0;
|
||||
|
||||
void (*_cb)();
|
||||
|
||||
AudioRingBuffer *_arb;
|
||||
AudioBufferManager *_arb;
|
||||
PIOProgram *_i2s;
|
||||
PIO _pio;
|
||||
int _sm;
|
||||
|
||||
@@ -41,6 +41,29 @@ right:
|
||||
out pins, 1 side 0b00 ; Last bit of right also has WCLK change
|
||||
; Loop back to beginning...
|
||||
|
||||
|
||||
.program pio_lsbj_out
|
||||
.side_set 2 ; 0 = bclk, 1=wclk
|
||||
|
||||
; The C code should place (number of bits/sample - 2) in Y and
|
||||
; also update the SHIFTCTRL to be 24 or 32 as appropriate
|
||||
|
||||
; +----- WCLK
|
||||
; |+---- BCLK
|
||||
mov x, y side 0b01
|
||||
left:
|
||||
out pins, 1 side 0b10
|
||||
jmp x--, left side 0b11
|
||||
out pins, 1 side 0b10
|
||||
|
||||
mov x, y side 0b11
|
||||
right:
|
||||
out pins, 1 side 0b00
|
||||
jmp x--, right side 0b01
|
||||
out pins, 1 side 0b00
|
||||
; Loop back to beginning...
|
||||
|
||||
|
||||
|
||||
.program pio_i2s_in ; Note this is the same as _out, just "in" and not "out"
|
||||
.side_set 2 ; 0 = bclk, 1=wclk
|
||||
@@ -73,7 +96,28 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint
|
||||
pio_gpio_init(pio, clock_pin_base + 1);
|
||||
|
||||
pio_sm_config sm_config = pio_i2s_out_program_get_default_config(offset);
|
||||
|
||||
|
||||
sm_config_set_out_pins(&sm_config, data_pin, 1);
|
||||
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
|
||||
sm_config_set_out_shift(&sm_config, false, true, (bits <= 16) ? 2 * bits : bits);
|
||||
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
|
||||
|
||||
pio_sm_init(pio, sm, offset, &sm_config);
|
||||
|
||||
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
|
||||
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
|
||||
pio_sm_set_pins(pio, sm, 0); // clear pins
|
||||
|
||||
pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
|
||||
}
|
||||
|
||||
static inline void pio_lsbj_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits) {
|
||||
pio_gpio_init(pio, data_pin);
|
||||
pio_gpio_init(pio, clock_pin_base);
|
||||
pio_gpio_init(pio, clock_pin_base + 1);
|
||||
|
||||
pio_sm_config sm_config = pio_lsbj_out_program_get_default_config(offset);
|
||||
|
||||
sm_config_set_out_pins(&sm_config, data_pin, 1);
|
||||
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
|
||||
sm_config_set_out_shift(&sm_config, false, true, (bits <= 16) ? 2 * bits : bits);
|
||||
|
||||
@@ -43,6 +43,41 @@ static inline pio_sm_config pio_i2s_out_program_get_default_config(uint offset)
|
||||
}
|
||||
#endif
|
||||
|
||||
// ------------ //
|
||||
// pio_lsbj_out //
|
||||
// ------------ //
|
||||
|
||||
#define pio_lsbj_out_wrap_target 0
|
||||
#define pio_lsbj_out_wrap 7
|
||||
|
||||
static const uint16_t pio_lsbj_out_program_instructions[] = {
|
||||
// .wrap_target
|
||||
0xa822, // 0: mov x, y side 1
|
||||
0x7001, // 1: out pins, 1 side 2
|
||||
0x1841, // 2: jmp x--, 1 side 3
|
||||
0x7001, // 3: out pins, 1 side 2
|
||||
0xb822, // 4: mov x, y side 3
|
||||
0x6001, // 5: out pins, 1 side 0
|
||||
0x0845, // 6: jmp x--, 5 side 1
|
||||
0x6001, // 7: out pins, 1 side 0
|
||||
// .wrap
|
||||
};
|
||||
|
||||
#if !PICO_NO_HARDWARE
|
||||
static const struct pio_program pio_lsbj_out_program = {
|
||||
.instructions = pio_lsbj_out_program_instructions,
|
||||
.length = 8,
|
||||
.origin = -1,
|
||||
};
|
||||
|
||||
static inline pio_sm_config pio_lsbj_out_program_get_default_config(uint offset) {
|
||||
pio_sm_config c = pio_get_default_sm_config();
|
||||
sm_config_set_wrap(&c, offset + pio_lsbj_out_wrap_target, offset + pio_lsbj_out_wrap);
|
||||
sm_config_set_sideset(&c, 2, false, false);
|
||||
return c;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ---------- //
|
||||
// pio_i2s_in //
|
||||
// ---------- //
|
||||
@@ -92,6 +127,21 @@ static inline void pio_i2s_out_program_init(PIO pio, uint sm, uint offset, uint
|
||||
pio_sm_set_pins(pio, sm, 0); // clear pins
|
||||
pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
|
||||
}
|
||||
static inline void pio_lsbj_out_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits) {
|
||||
pio_gpio_init(pio, data_pin);
|
||||
pio_gpio_init(pio, clock_pin_base);
|
||||
pio_gpio_init(pio, clock_pin_base + 1);
|
||||
pio_sm_config sm_config = pio_lsbj_out_program_get_default_config(offset);
|
||||
sm_config_set_out_pins(&sm_config, data_pin, 1);
|
||||
sm_config_set_sideset_pins(&sm_config, clock_pin_base);
|
||||
sm_config_set_out_shift(&sm_config, false, true, (bits <= 16) ? 2 * bits : bits);
|
||||
sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX);
|
||||
pio_sm_init(pio, sm, offset, &sm_config);
|
||||
uint pin_mask = (1u << data_pin) | (3u << clock_pin_base);
|
||||
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
|
||||
pio_sm_set_pins(pio, sm, 0); // clear pins
|
||||
pio_sm_exec(pio, sm, pio_encode_set(pio_y, bits - 2));
|
||||
}
|
||||
static inline void pio_i2s_in_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base, uint bits) {
|
||||
pio_gpio_init(pio, data_pin);
|
||||
pio_gpio_init(pio, clock_pin_base);
|
||||
@@ -109,4 +159,3 @@ static inline void pio_i2s_in_program_init(PIO pio, uint sm, uint offset, uint d
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
Submodule libraries/LittleFS/lib/littlefs updated: 40dba4a556...6a53d76e90
@@ -457,14 +457,14 @@ public:
|
||||
if (_creation) {
|
||||
int rc = lfs_setattr(_fs->getFS(), _name.get(), 'c', (const void *)&_creation, sizeof(_creation));
|
||||
if (rc < 0) {
|
||||
DEBUGV("Unable to set creation time on '%s' to %d\n", _name.get(), _creation);
|
||||
DEBUGV("Unable to set creation time on '%s' to %lld\n", _name.get(), _creation);
|
||||
}
|
||||
}
|
||||
// Add metadata with last write time
|
||||
time_t now = _timeCallback();
|
||||
int rc = lfs_setattr(_fs->getFS(), _name.get(), 't', (const void *)&now, sizeof(now));
|
||||
if (rc < 0) {
|
||||
DEBUGV("Unable to set last write time on '%s' to %d\n", _name.get(), now);
|
||||
DEBUGV("Unable to set last write time on '%s' to %lld\n", _name.get(), now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
|
||||
//PORTENTA_H7 min -12 max 51
|
||||
//NANO 33 BLE SENSe min 0 max 80
|
||||
//NICLA_VISION min 0 max 8
|
||||
void setGain(int gain);
|
||||
void setBufferSize(int bufferSize);
|
||||
size_t getBufferSize();
|
||||
@@ -56,6 +57,8 @@ private:
|
||||
int _gain;
|
||||
int _init;
|
||||
|
||||
int _cutSamples;
|
||||
|
||||
// Hardware peripherals used
|
||||
uint _dmaChannel;
|
||||
PIO _pio;
|
||||
|
||||
@@ -20,7 +20,7 @@ uint8_t rawBuffer1[RAW_BUFFER_SIZE];
|
||||
uint8_t* rawBuffer[2] = {rawBuffer0, rawBuffer1};
|
||||
volatile int rawBufferIndex = 0;
|
||||
|
||||
int decimation = 64;
|
||||
int decimation = 128;
|
||||
|
||||
// final buffer is the one to be filled with PCM data
|
||||
int16_t* volatile finalBuffer;
|
||||
@@ -45,6 +45,7 @@ PDMClass::PDMClass(int dinPin, int clkPin, int pwrPin) :
|
||||
_channels(-1),
|
||||
_samplerate(-1),
|
||||
_init(-1),
|
||||
_cutSamples(100),
|
||||
_dmaChannel(0),
|
||||
_pio(nullptr),
|
||||
_smIdx(-1),
|
||||
@@ -55,6 +56,12 @@ PDMClass::~PDMClass() {
|
||||
}
|
||||
|
||||
int PDMClass::begin(int channels, int sampleRate) {
|
||||
|
||||
if (_init == 1) {
|
||||
//ERROR: please call end first
|
||||
return 0;
|
||||
}
|
||||
|
||||
//_channels = channels; // only one channel available
|
||||
|
||||
// clear the final buffers
|
||||
@@ -63,6 +70,18 @@ int PDMClass::begin(int channels, int sampleRate) {
|
||||
int finalBufferLength = _doubleBuffer.availableForWrite() / sizeof(int16_t);
|
||||
_doubleBuffer.swap(0);
|
||||
|
||||
// The mic accepts an input clock from 1.2 to 3.25 Mhz
|
||||
// Setup the decimation factor accordingly
|
||||
if ((sampleRate * decimation * 2) > 3250000) {
|
||||
decimation = 64;
|
||||
}
|
||||
|
||||
// Sanity check, abort if still over 3.25Mhz
|
||||
if ((sampleRate * decimation * 2) > 3250000) {
|
||||
//ERROR: Sample rate too high, the mic would glitch
|
||||
return -1;
|
||||
}
|
||||
|
||||
int rawBufferLength = RAW_BUFFER_SIZE / (decimation / 8);
|
||||
// Saturate number of samples. Remaining bytes are dropped.
|
||||
if (rawBufferLength > finalBufferLength) {
|
||||
@@ -71,6 +90,7 @@ int PDMClass::begin(int channels, int sampleRate) {
|
||||
|
||||
/* Initialize Open PDM library */
|
||||
filter.Fs = sampleRate;
|
||||
filter.MaxVolume = 1;
|
||||
filter.nSamples = rawBufferLength;
|
||||
filter.LP_HZ = sampleRate / 2;
|
||||
filter.HP_HZ = 10;
|
||||
@@ -88,7 +108,7 @@ int PDMClass::begin(int channels, int sampleRate) {
|
||||
|
||||
if (!_pdmPgm.prepare(&_pio, &_smIdx, &_pgmOffset)) {
|
||||
// ERROR, no free slots
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
pdm_pio_program_init(_pio, _smIdx, _pgmOffset, _clkPin, _dinPin, clkDiv);
|
||||
|
||||
@@ -118,14 +138,29 @@ int PDMClass::begin(int channels, int sampleRate) {
|
||||
true // Start immediately
|
||||
);
|
||||
|
||||
_cutSamples = 100;
|
||||
|
||||
_init = 1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void PDMClass::end() {
|
||||
|
||||
if (_init != 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
dma_channel_set_irq0_enabled(_dmaChannel, false);
|
||||
dma_channel_abort(_dmaChannel);
|
||||
dma_channel_unclaim(_dmaChannel);
|
||||
irq_remove_handler(DMA_IRQ_0, dmaHandler);
|
||||
pio_sm_unclaim(_pio, _smIdx);
|
||||
pinMode(_clkPin, INPUT);
|
||||
rawBufferIndex = 0;
|
||||
_pgmOffset = -1;
|
||||
|
||||
_init = 0;
|
||||
}
|
||||
|
||||
int PDMClass::available() {
|
||||
@@ -163,7 +198,6 @@ void PDMClass::setBufferSize(int bufferSize) {
|
||||
}
|
||||
|
||||
void PDMClass::IrqHandler(bool halftranfer) {
|
||||
static int cutSamples = 100;
|
||||
|
||||
// Clear the interrupt request.
|
||||
dma_hw->ints0 = 1u << _dmaChannel;
|
||||
@@ -171,24 +205,25 @@ void PDMClass::IrqHandler(bool halftranfer) {
|
||||
int shadowIndex = rawBufferIndex ^ 1;
|
||||
dma_channel_set_write_addr(_dmaChannel, rawBuffer[shadowIndex], true);
|
||||
|
||||
if (_doubleBuffer.available()) {
|
||||
// buffer overflow, stop
|
||||
return end();
|
||||
if (!_doubleBuffer.available()) {
|
||||
// fill final buffer with PCM samples
|
||||
if (filter.Decimation == 128) {
|
||||
Open_PDM_Filter_128(rawBuffer[rawBufferIndex], finalBuffer, 1, &filter);
|
||||
} else {
|
||||
Open_PDM_Filter_64(rawBuffer[rawBufferIndex], finalBuffer, 1, &filter);
|
||||
}
|
||||
|
||||
if (_cutSamples) {
|
||||
memset(finalBuffer, 0, _cutSamples);
|
||||
_cutSamples = 0;
|
||||
}
|
||||
|
||||
// swap final buffer and raw buffers' indexes
|
||||
finalBuffer = (int16_t*)_doubleBuffer.data();
|
||||
_doubleBuffer.swap(filter.nSamples * sizeof(int16_t));
|
||||
rawBufferIndex = shadowIndex;
|
||||
}
|
||||
|
||||
// fill final buffer with PCM samples
|
||||
Open_PDM_Filter_64(rawBuffer[rawBufferIndex], finalBuffer, 1, &filter);
|
||||
|
||||
if (cutSamples) {
|
||||
memset(finalBuffer, 0, cutSamples);
|
||||
cutSamples = 0;
|
||||
}
|
||||
|
||||
// swap final buffer and raw buffers' indexes
|
||||
finalBuffer = (int16_t*)_doubleBuffer.data();
|
||||
_doubleBuffer.swap(filter.nSamples * sizeof(int16_t));
|
||||
rawBufferIndex = shadowIndex;
|
||||
|
||||
if (_onReceive) {
|
||||
_onReceive();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
This example plays a raw, headerless, mono 16b, 44.1 sample using the PWMAudio library on GPIO 1.
|
||||
|
||||
Released to the public domain by Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
*/
|
||||
|
||||
#include <PWMAudio.h>
|
||||
#include "wav.h"
|
||||
|
||||
// The sample pointers
|
||||
const int16_t *start = (const int16_t *)out_raw;
|
||||
const int16_t *p = start;
|
||||
|
||||
// Create the PWM audio device on GPIO 1. Hook amp/speaker between GPIO1 and convenient GND.
|
||||
PWMAudio pwm(1);
|
||||
|
||||
unsigned int count = 0;
|
||||
|
||||
void cb() {
|
||||
while (pwm.availableForWrite()) {
|
||||
pwm.write(*p++);
|
||||
count += 2;
|
||||
if (count >= sizeof(out_raw)) {
|
||||
count = 0;
|
||||
p = start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
pwm.onTransmit(cb);
|
||||
pwm.begin(44100);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
/* noop, everything is done in the CB */
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
This example plays a tune that alternates between left and right channels using a simple sine wave.
|
||||
|
||||
Released to the public domain by Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
*/
|
||||
|
||||
#include <PWMAudio.h>
|
||||
|
||||
PWMAudio pwm(0, true); // GP0 = left, GP1 = right
|
||||
|
||||
const int freq = 48000; // Output frequency for PWM
|
||||
|
||||
int16_t al = 0;
|
||||
int16_t ar = 0;
|
||||
|
||||
const int notes[] = { 784, 880, 698, 349, 523 };
|
||||
const int dly[] = { 400, 500, 700, 500, 1000 };
|
||||
const int noteCnt = sizeof(notes) / sizeof(notes[0]);
|
||||
|
||||
int freqL = 1;
|
||||
int freqR = 1;
|
||||
|
||||
double sineTable[128]; // Precompute sine wave in 128 steps
|
||||
|
||||
unsigned int cnt = 0;
|
||||
void cb() {
|
||||
while (pwm.availableForWrite()) {
|
||||
double now = ((double)cnt) / (double)freq;
|
||||
int fl = freqL << 7; // Prescale by 128 to avoid FP math later on
|
||||
int fr = freqR << 7; // Prescale by 128 to avoid FP math later on
|
||||
pwm.write((uint16_t)(al * sineTable[(int)(now * fl) & 127]));
|
||||
pwm.write((uint16_t)(ar * sineTable[(int)(now * fr) & 127]));
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
// Set up sine table for waveform generation
|
||||
for (int i = 0; i < 128; i++) {
|
||||
sineTable[i] = sin(i * 2.0 * 3.14159 / 128.0);
|
||||
}
|
||||
pwm.setBuffers(4, 32); // Give larger buffers since we're are 48khz sample rate
|
||||
pwm.onTransmit(cb);
|
||||
pwm.begin(freq);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
delay(1000);
|
||||
|
||||
// Send it out on the LHS
|
||||
ar = 0;
|
||||
for (int i = 0; i < noteCnt; i++) {
|
||||
freqL = notes[i];
|
||||
al = 5000;
|
||||
delay(dly[i]);
|
||||
}
|
||||
al = 0;
|
||||
delay(1000);
|
||||
|
||||
// Hear reply on RHS
|
||||
for (int i = 0; i < noteCnt; i++) {
|
||||
freqR = notes[i] / 4;
|
||||
ar = 15000;
|
||||
delay(dly[i]);
|
||||
}
|
||||
ar = 0;
|
||||
delay(3000);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
PWMAudio KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
begin KEYWORD2
|
||||
end KEYWORD2
|
||||
|
||||
setPin KEYWORD2
|
||||
setFrequency KEYWORD2
|
||||
setBuffers KEYWORD2
|
||||
setStereo KEYWORD2
|
||||
|
||||
onTransmit KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
@@ -0,0 +1,10 @@
|
||||
name=PWMAudio
|
||||
version=1.0
|
||||
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
sentence=Plays audio on a pin using the PWM hardware, no DAC required
|
||||
paragraph=Plays audio on a pin using the PWM hardware, no DAC required
|
||||
category=Communication
|
||||
url=http://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
PWMAudio
|
||||
Plays a 16b audio stream on a user defined pin using PWM
|
||||
|
||||
Copyright (c) 2022 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 "PWMAudio.h"
|
||||
#include <hardware/pwm.h>
|
||||
|
||||
|
||||
PWMAudio::PWMAudio(pin_size_t pin, bool stereo) {
|
||||
_running = false;
|
||||
_pin = pin;
|
||||
_freq = 48000;
|
||||
_arb = nullptr;
|
||||
_cb = nullptr;
|
||||
_buffers = 8;
|
||||
_bufferWords = 0;
|
||||
_stereo = stereo;
|
||||
}
|
||||
|
||||
PWMAudio::~PWMAudio() {
|
||||
end();
|
||||
}
|
||||
|
||||
bool PWMAudio::setBuffers(size_t buffers, size_t bufferWords) {
|
||||
if (_running || (buffers < 3) || (bufferWords < 8)) {
|
||||
return false;
|
||||
}
|
||||
_buffers = buffers;
|
||||
_bufferWords = bufferWords;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PWMAudio::setPin(pin_size_t pin) {
|
||||
if (_running) {
|
||||
return false;
|
||||
}
|
||||
_pin = pin;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PWMAudio::setStereo(bool stereo) {
|
||||
if (_running) {
|
||||
return false;
|
||||
}
|
||||
_stereo = stereo;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PWMAudio::setFrequency(int newFreq) {
|
||||
_freq = newFreq;
|
||||
|
||||
// Figure out the scale factor for PWM values
|
||||
float fPWM = 65535.0 * _freq; // ideal
|
||||
|
||||
if (fPWM > clock_get_hz(clk_sys)) {
|
||||
// Need to downscale the range to hit the frequency target
|
||||
float pwmMax = (float) clock_get_hz(clk_sys) / (float) _freq;
|
||||
_pwmScale = pwmMax;
|
||||
fPWM = clock_get_hz(clk_sys);
|
||||
} else {
|
||||
_pwmScale = 1 << 16;
|
||||
}
|
||||
|
||||
pwm_config c = pwm_get_default_config();
|
||||
pwm_config_set_clkdiv(&c, clock_get_hz(clk_sys) / fPWM);
|
||||
pwm_config_set_wrap(&c, _pwmScale);
|
||||
pwm_init(pwm_gpio_to_slice_num(_pin), &c, _running);
|
||||
gpio_set_function(_pin, GPIO_FUNC_PWM);
|
||||
pwm_set_gpio_level(_pin, (0x8000 * _pwmScale) >> 16);
|
||||
if (_stereo) {
|
||||
gpio_set_function(_pin + 1, GPIO_FUNC_PWM);
|
||||
pwm_set_gpio_level(_pin + 1, (0x8000 * _pwmScale) >> 16);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PWMAudio::onTransmit(void(*fn)(void)) {
|
||||
_cb = fn;
|
||||
if (_running) {
|
||||
_arb->setCallback(_cb);
|
||||
}
|
||||
}
|
||||
|
||||
bool PWMAudio::begin() {
|
||||
if (_stereo && (_pin & 1)) {
|
||||
// Illegal, need to have consecutive pins on the same PWM slice
|
||||
Serial.printf("ERROR: PWMAudio stereo mode requires pin be even\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
_running = true;
|
||||
_wasHolding = false;
|
||||
|
||||
if (!_bufferWords) {
|
||||
_bufferWords = 16;
|
||||
}
|
||||
|
||||
setFrequency(_freq);
|
||||
|
||||
uint32_t ccAddr = PWM_BASE + PWM_CH0_CC_OFFSET + pwm_gpio_to_slice_num(_pin) * 20;
|
||||
|
||||
_arb = new AudioBufferManager(_buffers, _bufferWords, 0x80008000, OUTPUT, DMA_SIZE_32);
|
||||
_arb->begin(pwm_get_dreq(pwm_gpio_to_slice_num(_pin)), (volatile void*)ccAddr);
|
||||
_arb->setCallback(_cb);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PWMAudio::end() {
|
||||
if (_running) {
|
||||
_running = false;
|
||||
pinMode(_pin, OUTPUT);
|
||||
if (_stereo) {
|
||||
pinMode(_pin + 1, OUTPUT);
|
||||
}
|
||||
delete _arb;
|
||||
_arb = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int PWMAudio::available() {
|
||||
return availableForWrite(); // Do what I mean, not what I say
|
||||
}
|
||||
|
||||
int PWMAudio::read() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int PWMAudio::peek() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
void PWMAudio::flush() {
|
||||
if (_running) {
|
||||
_arb->flush();
|
||||
}
|
||||
}
|
||||
|
||||
int PWMAudio::availableForWrite() {
|
||||
if (!_running) {
|
||||
return 0;
|
||||
}
|
||||
return _arb->available();
|
||||
}
|
||||
|
||||
size_t PWMAudio::write(int16_t val, bool sync) {
|
||||
if (!_running) {
|
||||
return 0;
|
||||
}
|
||||
// Go from signed -32K...32K to unsigned 0...64K
|
||||
uint32_t sample = (uint32_t)(val + 0x8000);
|
||||
// Adjust to the real range
|
||||
sample *= _pwmScale;
|
||||
sample >>= 16;
|
||||
if (!_stereo) {
|
||||
// Duplicate sample since we don't care which PWM channel
|
||||
sample = (sample & 0xffff) | (sample << 16);
|
||||
return _arb->write(sample, sync);
|
||||
} else {
|
||||
if (_wasHolding) {
|
||||
_holdWord = (_holdWord & 0xffff) | (sample << 16);
|
||||
auto ret = _arb->write(_holdWord, sync);
|
||||
if (ret) {
|
||||
_wasHolding = false;
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
_holdWord = sample;
|
||||
_wasHolding = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t PWMAudio::write(const uint8_t *buffer, size_t size) {
|
||||
// We can only write 16-bit chunks here
|
||||
if (size & 0x1) {
|
||||
return 0;
|
||||
}
|
||||
size_t writtenSize = 0;
|
||||
int16_t *p = (int16_t *)buffer;
|
||||
while (size) {
|
||||
if (!write((int16_t)*p)) {
|
||||
// Blocked, stop write here
|
||||
return writtenSize;
|
||||
} else {
|
||||
p++;
|
||||
size -= 4;
|
||||
writtenSize += 4;
|
||||
}
|
||||
}
|
||||
return writtenSize;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
PWMAudio
|
||||
Plays a signed 16b audio stream on a user defined pin using PWM
|
||||
|
||||
Copyright (c) 2022 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 "AudioBufferManager.h"
|
||||
|
||||
class PWMAudio : public Stream {
|
||||
public:
|
||||
PWMAudio(pin_size_t pin = 0, bool stereo = false);
|
||||
virtual ~PWMAudio();
|
||||
|
||||
bool setBuffers(size_t buffers, size_t bufferWords);
|
||||
bool setFrequency(int newFreq);
|
||||
bool setPin(pin_size_t pin);
|
||||
bool setStereo(bool stereo = true);
|
||||
|
||||
bool begin(long sampleRate) {
|
||||
setFrequency(sampleRate);
|
||||
return begin();
|
||||
}
|
||||
|
||||
bool begin();
|
||||
void end();
|
||||
|
||||
// from Stream
|
||||
virtual int available() override;
|
||||
virtual int read() override;
|
||||
virtual int peek() override;
|
||||
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 x) override {
|
||||
return write((int16_t) x, true);
|
||||
}
|
||||
|
||||
// Write 16 bit value to port, user responsible for packing/alignment, etc.
|
||||
size_t write(int16_t val, bool sync = true);
|
||||
size_t write(int val, bool sync = true) {
|
||||
return write((int16_t) val, sync);
|
||||
}
|
||||
|
||||
// Note that these callback are called from **INTERRUPT CONTEXT** and hence
|
||||
// should be in RAM, not FLASH, and should be quick to execute.
|
||||
void onTransmit(void(*)(void));
|
||||
|
||||
private:
|
||||
pin_size_t _pin;
|
||||
bool _stereo;
|
||||
|
||||
int _freq;
|
||||
|
||||
size_t _buffers;
|
||||
size_t _bufferWords;
|
||||
|
||||
uint32_t _pwmScale;
|
||||
|
||||
bool _running;
|
||||
|
||||
bool _wasHolding;
|
||||
uint32_t _holdWord;
|
||||
|
||||
void (*_cb)();
|
||||
|
||||
AudioBufferManager *_arb;
|
||||
};
|
||||
@@ -5,6 +5,6 @@ maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
sentence=Configures requests for OTA bootloader
|
||||
paragraph=Example repository for Ethernet drivers
|
||||
category=Device Control
|
||||
url=https://github.com/earlephilhower.arduino-pico
|
||||
url=https://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
|
||||
+17
-3
@@ -162,9 +162,23 @@ public:
|
||||
|
||||
private:
|
||||
const char *getMode(uint8_t mode) {
|
||||
bool read = (mode & O_READ) ? true : false;
|
||||
bool write = (mode & O_WRITE) ? true : false;
|
||||
bool append = (mode & O_APPEND) ? true : false;
|
||||
bool read = false;
|
||||
bool write = false;
|
||||
|
||||
switch (mode & O_ACCMODE) {
|
||||
case O_RDONLY:
|
||||
read = true;
|
||||
break;
|
||||
case O_WRONLY:
|
||||
write = true;
|
||||
break;
|
||||
case O_RDWR:
|
||||
read = true;
|
||||
write = true;
|
||||
break;
|
||||
}
|
||||
const bool append = (mode & O_APPEND) > 0;
|
||||
|
||||
if (read & !write) {
|
||||
return "r";
|
||||
} else if (!read & write & !append) {
|
||||
|
||||
@@ -125,7 +125,7 @@ uint16_t SPIClassRP2040::transfer16(uint16_t data) {
|
||||
spi_set_format(_spi, 16, cpol(), cpha(), SPI_MSB_FIRST);
|
||||
DEBUGSPI("SPI::transfer16(%04x), cpol=%d, cpha=%d\n", data, cpol(), cpha());
|
||||
spi_write16_read16_blocking(_spi, &data, &ret, 1);
|
||||
ret = (_spis.getBitOrder() == MSBFIRST) ? ret : reverseByte(ret);
|
||||
ret = (_spis.getBitOrder() == MSBFIRST) ? ret : reverse16Bit(ret);
|
||||
DEBUGSPI("SPI: read back %02x\n", ret);
|
||||
return ret;
|
||||
}
|
||||
@@ -178,7 +178,7 @@ void SPIClassRP2040::transfer(const void *txbuf, void *rxbuf, size_t count) {
|
||||
}
|
||||
|
||||
void SPIClassRP2040::beginTransaction(SPISettings settings) {
|
||||
DEBUGSPI("SPI::beginTransaction(clk=%d, bo=%s\n", _spis.getClockFreq(), (_spis.getBitOrder() == MSBFIRST) ? "MSB" : "LSB");
|
||||
DEBUGSPI("SPI::beginTransaction(clk=%lu, bo=%s\n", _spis.getClockFreq(), (_spis.getBitOrder() == MSBFIRST) ? "MSB" : "LSB");
|
||||
if (_initted && settings == _spis) {
|
||||
DEBUGSPI("SPI: Reusing existing initted SPI\n");
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// Simple logger with USB upload to PC
|
||||
// Uses SingleFileDrive to export an onboard LittleFS file to the computer
|
||||
// The PC can open/copy the file, and then the user can delete it to restart
|
||||
|
||||
// Released to the public domain, 2022 - Earle F. Philhower, III
|
||||
|
||||
#include <SingleFileDrive.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
uint32_t cnt = 0;
|
||||
bool okayToWrite = true;
|
||||
|
||||
// Make the CSV file and give it a simple header
|
||||
void headerCSV() {
|
||||
File f = LittleFS.open("data.csv", "w");
|
||||
f.printf("sample,millis,temp,rand\n");
|
||||
f.close();
|
||||
cnt = 0;
|
||||
}
|
||||
|
||||
// Called when the USB stick connected to a PC and the drive opened
|
||||
// Note this is from a USB IRQ so no printing to SerialUSB/etc.
|
||||
void plug(uint32_t i) {
|
||||
(void) i;
|
||||
okayToWrite = false;
|
||||
}
|
||||
|
||||
// Called when the USB is ejected or removed from a PC
|
||||
// Note this is from a USB IRQ so no printing to SerialUSB/etc.
|
||||
void unplug(uint32_t i) {
|
||||
(void) i;
|
||||
okayToWrite = true;
|
||||
}
|
||||
|
||||
// Called when the PC tries to delete the single file
|
||||
// Note this is from a USB IRQ so no printing to SerialUSB/etc.
|
||||
void deleteCSV(uint32_t i) {
|
||||
(void) i;
|
||||
LittleFS.remove("data.csv");
|
||||
headerCSV();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin();
|
||||
delay(5000);
|
||||
|
||||
LittleFS.begin();
|
||||
|
||||
// Set up the USB disk share
|
||||
singleFileDrive.onDelete(deleteCSV);
|
||||
singleFileDrive.onPlug(plug);
|
||||
singleFileDrive.onUnplug(unplug);
|
||||
singleFileDrive.begin("data.csv", "Recorded data from the Raspberry Pi Pico.csv");
|
||||
|
||||
// Find the last written data
|
||||
File f = LittleFS.open("data.csv", "r");
|
||||
if (!f || !f.size()) {
|
||||
cnt = 1;
|
||||
headerCSV();
|
||||
} else {
|
||||
if (f.size() > 2048) {
|
||||
f.seek(f.size() - 1024);
|
||||
}
|
||||
do {
|
||||
String s = f.readStringUntil('\n');
|
||||
sscanf(s.c_str(), "%lu,", &cnt);
|
||||
} while (f.available());
|
||||
f.close();
|
||||
cnt++;
|
||||
}
|
||||
|
||||
Serial.printf("Starting acquisition at %lu\n", cnt);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
float temp = analogReadTemp();
|
||||
uint32_t hwrand = rp2040.hwrand32();
|
||||
// Make sure the USB connect doesn't happen while we're writing!
|
||||
noInterrupts();
|
||||
if (okayToWrite) {
|
||||
Serial.printf("Sampling...%lu\n", cnt);
|
||||
// Don't want the USB to connect during an update!
|
||||
File f = LittleFS.open("data.csv", "a");
|
||||
if (f) {
|
||||
f.printf("%lu,%lu,%f,%lu\n", cnt++, millis(), temp, hwrand);
|
||||
f.close();
|
||||
}
|
||||
}
|
||||
interrupts();
|
||||
|
||||
delay(10000);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#######################################
|
||||
# Syntax Coloring Map
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
SingleFileDrive KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
|
||||
singleFileDrive KEYWORD1
|
||||
onDelete KEYWORD1
|
||||
onPlug KEYWORD1
|
||||
onUnplug KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
@@ -0,0 +1,10 @@
|
||||
name=SingleFileDrive
|
||||
version=1.0.0
|
||||
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
sentence=Allows using USB MSC (USB stick) to transfer an onboard flash file to a PC
|
||||
paragraph=Emulates a USB stick and presents a single file for users to copy over/erase
|
||||
category=Device Control
|
||||
url=https://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
SingleFileDrive - Emulates a USB stick for easy data transfer
|
||||
Copyright (c) 2022 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
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 <SingleFileDrive.h>
|
||||
#include <LittleFS.h>
|
||||
#include <class/msc/msc.h>
|
||||
|
||||
SingleFileDrive singleFileDrive;
|
||||
|
||||
static const uint32_t _hddsize = (256 * 1024 * 1024); // 256MB
|
||||
static const uint32_t _hddsects = _hddsize / 512;
|
||||
|
||||
// Ensure we are logged in to the USB framework
|
||||
void __USBInstallMassStorage() {
|
||||
/* dummy */
|
||||
}
|
||||
|
||||
SingleFileDrive::SingleFileDrive() {
|
||||
}
|
||||
|
||||
SingleFileDrive::~SingleFileDrive() {
|
||||
end();
|
||||
}
|
||||
|
||||
void SingleFileDrive::onDelete(void (*cb)(uint32_t), uint32_t cbData) {
|
||||
_cbDelete = cb;
|
||||
_cbDeleteData = cbData;
|
||||
}
|
||||
|
||||
void SingleFileDrive::onPlug(void (*cb)(uint32_t), uint32_t cbData) {
|
||||
_cbPlug = cb;
|
||||
_cbPlugData = cbData;
|
||||
}
|
||||
|
||||
void SingleFileDrive::onUnplug(void (*cb)(uint32_t), uint32_t cbData) {
|
||||
_cbUnplug = cb;
|
||||
_cbUnplugData = cbData;
|
||||
}
|
||||
|
||||
bool SingleFileDrive::begin(const char *localFile, const char *dosFile) {
|
||||
if (_started) {
|
||||
return false;
|
||||
}
|
||||
_localFile = strdup(localFile);
|
||||
_dosFile = strdup(dosFile);
|
||||
_started = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SingleFileDrive::end() {
|
||||
_started = false;
|
||||
free(_localFile);
|
||||
free(_dosFile);
|
||||
_localFile = nullptr;
|
||||
_dosFile = nullptr;
|
||||
}
|
||||
|
||||
void SingleFileDrive::bootSector(char buff[512]) {
|
||||
// 256MB FAT16 stolen from mkfs.fat
|
||||
// dd if=/dev/zero of=/tmp/fat.bin bs=1M seek=255 count=1
|
||||
// mkfs.fat -F 16 -r 16 -n PICODISK -i 12345678 -s 128 -m ':(' /tmp/fat.bin
|
||||
const uint8_t hdr[] = {
|
||||
0xeb, 0x3c, 0x90, 0x6d, 0x6b, 0x66, 0x73, 0x2e, 0x66, 0x61, 0x74, 0x00,
|
||||
0x02, 0x80, 0x80, 0x00, 0x02, 0x00, 0x08, 0x00, 0x00, 0xf8, 0x80, 0x00,
|
||||
0x20, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
|
||||
0x80, 0x00, 0x29, 0x78, 0x56, 0x34, 0x12, 0x50, 0x49, 0x43, 0x4f, 0x44,
|
||||
0x49, 0x53, 0x4b, 0x20, 0x20, 0x20, 0x46, 0x41, 0x54, 0x31, 0x36, 0x20,
|
||||
0x20, 0x20, 0x0e, 0x1f, 0xbe, 0x5b, 0x7c, 0xac, 0x22, 0xc0, 0x74, 0x0b,
|
||||
0x56, 0xb4, 0x0e, 0xbb, 0x07, 0x00, 0xcd, 0x10, 0x5e, 0xeb, 0xf0, 0x32,
|
||||
0xe4, 0xcd, 0x16, 0xcd, 0x19, 0xeb, 0xfe, 0x3a, 0x28, 0x0d, 0x0a, 0x00
|
||||
};
|
||||
memset(buff, 0, 512);
|
||||
memcpy(buff, hdr, sizeof(hdr));
|
||||
buff[0x1fe] = 0x55;
|
||||
buff[0x1ff] = 0xff;
|
||||
}
|
||||
|
||||
static char _toLegalFATChar(char c) {
|
||||
const char *odds = "!#$%&'()-@^_`{}~";
|
||||
c = toupper(c);
|
||||
if (((c >= '0') && (c <= '9')) || ((c >= 'A') && (c <= 'Z')) || strchr(odds, c)) {
|
||||
return c;
|
||||
} else {
|
||||
return '~';
|
||||
}
|
||||
}
|
||||
|
||||
void SingleFileDrive::directorySector(char buff[512]) {
|
||||
const uint8_t lbl[] = {
|
||||
0x50, 0x49, 0x43, 0x4f, 0x44, 0x49, 0x53, 0x4b, 0x20, 0x20, 0x20, 0x08, 0x00, 0x00, 0xac, 0x56,
|
||||
0x82, 0x55, 0x82, 0x55, 0x00, 0x00, 0xac, 0x56, 0x82, 0x55
|
||||
}; //, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
memset(buff, 0, 512);
|
||||
memcpy(buff, lbl, sizeof(lbl));
|
||||
buff += 32; // Skip the just-set label
|
||||
|
||||
// Create a legal 11-char UPPERCASE FILENAME WITH 0x20 PAD
|
||||
char SFN[11];
|
||||
memset(SFN, ' ', 11);
|
||||
for (int i = 0; (i < 8) && _dosFile[i] && (_dosFile[i] != '.'); i++) {
|
||||
SFN[i] = _toLegalFATChar(_dosFile[i]);
|
||||
}
|
||||
char *dot = _dosFile + strlen(_dosFile) - 1;
|
||||
while ((dot >= _dosFile) && (*dot != '.')) {
|
||||
dot--;
|
||||
}
|
||||
if (*dot == '.') {
|
||||
dot++;
|
||||
for (int i = 0; (i < 3) && dot[i]; i++) {
|
||||
SFN[8 + i] = _toLegalFATChar(dot[i]);
|
||||
}
|
||||
}
|
||||
uint8_t chksum = 0; // for LFN
|
||||
for (int i = 0; i < 11; i++) {
|
||||
chksum = (chksum >> 1) + (chksum << 7) + SFN[i];
|
||||
}
|
||||
|
||||
// Create LFN structure
|
||||
int entries = (strlen(_dosFile) + 12) / 13; // round up
|
||||
for (int i = 0; i < entries; i++) {
|
||||
*buff++ = (entries - i) | (i == 0 ? 0x40 : 0);
|
||||
const char *partname = _dosFile + 13 * (entries - i - 1);
|
||||
for (int j = 0; j < 13; j++) {
|
||||
uint16_t u;
|
||||
if (j > (int)strlen(partname)) {
|
||||
u = 0xffff;
|
||||
} else {
|
||||
u = partname[j] & 0xff;
|
||||
}
|
||||
*buff++ = u & 0xff;
|
||||
*buff++ = (u >> 8) & 0xff;
|
||||
if (j == 4) {
|
||||
*buff++ = 0x0f; // LFN ATTR
|
||||
*buff++ = 0;
|
||||
*buff++ = chksum;
|
||||
} else if (j == 10) {
|
||||
*buff++ = 0;
|
||||
*buff++ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create SFN
|
||||
memset(buff, 0, 32);
|
||||
for (int i = 0; i < 11; i++) {
|
||||
buff[i] = SFN[i];
|
||||
}
|
||||
buff[0x0b] = 0x20; // ATTR = Archive
|
||||
// Ignore creation data/time, etc.
|
||||
buff[0x1a] = 0x03; // Starting cluster 3
|
||||
File f = LittleFS.open(_localFile, "r");
|
||||
int size = f.size();
|
||||
f.close();
|
||||
buff[0x1c] = size & 255;
|
||||
buff[0x1d] = (size >> 8) & 255;
|
||||
buff[0x1e] = (size >> 16) & 255; // 16MB or smaller
|
||||
}
|
||||
|
||||
void SingleFileDrive::fatSector(char fat[512]) {
|
||||
memset(fat, 0, 512);
|
||||
fat[0x00] = 0xff;
|
||||
fat[0x01] = 0xf8;
|
||||
fat[0x02] = 0xff;
|
||||
fat[0x03] = 0xff;
|
||||
int cluster = 3;
|
||||
File f = LittleFS.open(_localFile, "r");
|
||||
int size = f.size();
|
||||
f.close();
|
||||
while (size > 65536) {
|
||||
fat[cluster * 2] = (cluster + 1) & 0xff;
|
||||
fat[cluster * 2 + 1] = ((cluster + 1) >> 8) & 0xff;
|
||||
cluster++;
|
||||
size -= 65536;
|
||||
}
|
||||
fat[cluster * 2] = 0xff;
|
||||
fat[cluster * 2 + 1] = 0xff;
|
||||
}
|
||||
|
||||
// Invoked to determine max LUN
|
||||
extern "C" uint8_t tud_msc_get_maxlun_cb(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Invoked when received SCSI_CMD_INQUIRY
|
||||
// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
|
||||
extern "C" void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) {
|
||||
(void) lun;
|
||||
|
||||
const char vid[] = "PicoDisk";
|
||||
const char pid[] = "Mass Storage";
|
||||
const char rev[] = "1.0";
|
||||
|
||||
memcpy(vendor_id, vid, strlen(vid));
|
||||
memcpy(product_id, pid, strlen(pid));
|
||||
memcpy(product_rev, rev, strlen(rev));
|
||||
}
|
||||
|
||||
bool SingleFileDrive::testUnitReady() {
|
||||
return _started;
|
||||
}
|
||||
|
||||
// Invoked when received Test Unit Ready command.
|
||||
// return true allowing host to read/write this LUN e.g SD card inserted
|
||||
extern "C" bool tud_msc_test_unit_ready_cb(uint8_t lun) {
|
||||
(void) lun;
|
||||
|
||||
return singleFileDrive.testUnitReady();
|
||||
}
|
||||
|
||||
// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
|
||||
// Application update block count and block size
|
||||
extern "C" void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) {
|
||||
(void) lun;
|
||||
*block_count = _hddsects;
|
||||
*block_size = 512;
|
||||
}
|
||||
|
||||
|
||||
// Callback invoked when received READ10 command.
|
||||
// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
|
||||
extern "C" int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {
|
||||
(void) lun;
|
||||
return singleFileDrive.read10(lba, offset, buffer, bufsize);
|
||||
}
|
||||
|
||||
int32_t SingleFileDrive::read10(uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) {
|
||||
if (!_started || (lba >= _hddsects)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t toread = bufsize;
|
||||
uint8_t *curbuff = (uint8_t *)buffer;
|
||||
|
||||
while (bufsize > 0) {
|
||||
if (lba == 0) {
|
||||
bootSector(_sectBuff);
|
||||
} else if ((lba == 128) || (lba == 256)) {
|
||||
fatSector(_sectBuff);
|
||||
} else if (lba == 384) {
|
||||
directorySector(_sectBuff);
|
||||
} else if (lba >= 640) {
|
||||
File f = LittleFS.open(_localFile, "r");
|
||||
f.seek((lba - 640) * 512);
|
||||
f.read((uint8_t*)_sectBuff, 512);
|
||||
f.close();
|
||||
} else {
|
||||
memset(_sectBuff, 0, sizeof(_sectBuff));
|
||||
}
|
||||
|
||||
uint32_t cplen = 512 - offset;
|
||||
if (bufsize < cplen) {
|
||||
cplen = bufsize;
|
||||
}
|
||||
memcpy(curbuff, _sectBuff + offset, cplen);
|
||||
curbuff += cplen;
|
||||
offset = 0;
|
||||
lba++;
|
||||
bufsize -= cplen;
|
||||
}
|
||||
|
||||
return toread;
|
||||
}
|
||||
|
||||
extern "C" bool tud_msc_is_writable_cb(uint8_t lun) {
|
||||
(void) lun;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Callback invoked when received WRITE10 command.
|
||||
// Process data in buffer to disk's storage and return number of written bytes
|
||||
extern "C" int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) {
|
||||
(void) lun;
|
||||
return singleFileDrive.write10(lba, offset, buffer, bufsize);
|
||||
}
|
||||
|
||||
int32_t SingleFileDrive::write10(uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) {
|
||||
if (!_started || (lba >= _hddsects)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t addr = lba * 512 + offset;
|
||||
uint32_t hotspot = 384 * 512 + 0x20;
|
||||
if ((addr > hotspot) || (addr + bufsize < hotspot)) {
|
||||
// Did not try and erase the file entry, ignore
|
||||
return bufsize;
|
||||
}
|
||||
int off = hotspot - addr;
|
||||
uint8_t *ptr = (uint8_t *)buffer;
|
||||
ptr += off;
|
||||
if (*ptr == 0xe5) {
|
||||
if (_cbDelete) {
|
||||
_cbDelete(_cbDeleteData);
|
||||
}
|
||||
}
|
||||
|
||||
return bufsize;
|
||||
}
|
||||
|
||||
extern "C" bool tud_msc_set_sense(uint8_t lun, uint8_t sense_key, uint8_t add_sense_code, uint8_t add_sense_qualifier);
|
||||
|
||||
// Callback invoked when received an SCSI command not in built-in list below
|
||||
// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE
|
||||
// - READ10 and WRITE10 has their own callbacks
|
||||
extern "C" int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) {
|
||||
const int SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E;
|
||||
const int SCSI_CMD_START_STOP_UNIT = 0x1B;
|
||||
const int SCSI_SENSE_ILLEGAL_REQUEST = 0x05;
|
||||
|
||||
void const* response = NULL;
|
||||
int32_t resplen = 0;
|
||||
|
||||
// most scsi handled is input
|
||||
bool in_xfer = true;
|
||||
scsi_start_stop_unit_t const * start_stop = (scsi_start_stop_unit_t const *) scsi_cmd;
|
||||
switch (scsi_cmd[0]) {
|
||||
case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
|
||||
// Host is about to read/write etc ... better not to disconnect disk
|
||||
if (scsi_cmd[4] & 1) {
|
||||
singleFileDrive.plug();
|
||||
}
|
||||
resplen = 0;
|
||||
break;
|
||||
case SCSI_CMD_START_STOP_UNIT:
|
||||
// Host try to eject/safe remove/poweroff us. We could safely disconnect with disk storage, or go into lower power
|
||||
if (!start_stop->start && start_stop->load_eject) {
|
||||
singleFileDrive.unplug();
|
||||
} else if (start_stop->start && start_stop->load_eject) {
|
||||
singleFileDrive.plug();
|
||||
}
|
||||
resplen = 0;
|
||||
break;
|
||||
default:
|
||||
// Set Sense = Invalid Command Operation
|
||||
tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
|
||||
// negative means error -> tinyusb could stall and/or response with failed status
|
||||
resplen = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
// return resplen must not larger than bufsize
|
||||
if (resplen > bufsize) {
|
||||
resplen = bufsize;
|
||||
}
|
||||
|
||||
if (response && (resplen > 0)) {
|
||||
if (in_xfer) {
|
||||
memcpy(buffer, response, resplen);
|
||||
} else {
|
||||
// SCSI output
|
||||
}
|
||||
}
|
||||
|
||||
return resplen;
|
||||
}
|
||||
|
||||
void SingleFileDrive::plug() {
|
||||
if (_started && _cbPlug) {
|
||||
_cbPlug(_cbPlugData);
|
||||
}
|
||||
}
|
||||
|
||||
void SingleFileDrive::unplug() {
|
||||
if (_started && _cbUnplug) {
|
||||
_cbUnplug(_cbUnplugData);
|
||||
}
|
||||
}
|
||||
|
||||
// Callback invoked on start/stop
|
||||
extern "C" bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) {
|
||||
(void) lun;
|
||||
(void) power_condition;
|
||||
if (start && load_eject) {
|
||||
singleFileDrive.plug();
|
||||
} else if (!start && load_eject) {
|
||||
singleFileDrive.unplug();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
SingleFileDrive - Emulates a USB stick for easy data transfer
|
||||
Copyright (c) 2022 Earle F. Philhower, III. All rights reserved.
|
||||
|
||||
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 SingleFileDrive {
|
||||
public:
|
||||
SingleFileDrive();
|
||||
~SingleFileDrive();
|
||||
|
||||
bool begin(const char *localFile, const char *dosFile);
|
||||
void end();
|
||||
|
||||
void onDelete(void (*cb)(uint32_t), uint32_t cbData = 0);
|
||||
void onPlug(void (*cb)(uint32_t), uint32_t cbData = 0);
|
||||
void onUnplug(void (*cb)(uint32_t), uint32_t cbData = 0);
|
||||
|
||||
// Only for internal TinyUSB callback use
|
||||
bool testUnitReady();
|
||||
int32_t read10(uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize);
|
||||
int32_t write10(uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize);
|
||||
void plug();;
|
||||
void unplug();
|
||||
|
||||
private:
|
||||
void bootSector(char buff[512]);
|
||||
void directorySector(char buff[512]);
|
||||
void fatSector(char buff[512]);
|
||||
|
||||
private:
|
||||
bool _started = false;
|
||||
char *_localFile = nullptr;
|
||||
char *_dosFile = nullptr;
|
||||
|
||||
char _sectBuff[512]; // Read sector region
|
||||
|
||||
void (*_cbDelete)(uint32_t) = nullptr;
|
||||
uint32_t _cbDeleteData = 0;
|
||||
|
||||
void (*_cbPlug)(uint32_t) = nullptr;
|
||||
uint32_t _cbPlugData = 0;
|
||||
|
||||
void (*_cbUnplug)(uint32_t) = nullptr;
|
||||
uint32_t _cbUnplugData = 0;
|
||||
};
|
||||
|
||||
extern SingleFileDrive singleFileDrive;
|
||||
@@ -129,7 +129,7 @@ bool UpdaterClass::begin(size_t size, int command) {
|
||||
_command = command;
|
||||
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("[begin] _startAddress: 0x%08X (%d)\n"), _startAddress, _startAddress);
|
||||
DEBUG_UPDATER.printf_P(PSTR("[begin] _startAddress: 0x%08lX (%lu)\n"), _startAddress, _startAddress);
|
||||
DEBUG_UPDATER.printf_P(PSTR("[begin] _size: 0x%08zX (%zd)\n"), _size, _size);
|
||||
#endif
|
||||
|
||||
@@ -188,7 +188,7 @@ bool UpdaterClass::end(bool evenIfRemaining) {
|
||||
_fp.read((uint8_t *)&sigLen, sizeof(uint32_t));
|
||||
}
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] sigLen: %d\n"), sigLen);
|
||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] sigLen: %lu\n"), sigLen);
|
||||
#endif
|
||||
if (sigLen != expectedSigLen) {
|
||||
_setError(UPDATE_ERROR_SIGN);
|
||||
@@ -276,7 +276,7 @@ bool UpdaterClass::end(bool evenIfRemaining) {
|
||||
picoOTA.addFile("firmware.bin");
|
||||
picoOTA.commit();
|
||||
#ifdef DEBUG_UPDATER
|
||||
DEBUG_UPDATER.printf_P(PSTR("Staged: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
|
||||
DEBUG_UPDATER.printf_P(PSTR("Staged: address:0x%08lX, size:0x%08zX\n"), _startAddress, _size);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <LEAmDNS.h>
|
||||
#include <StreamString.h>
|
||||
|
||||
#ifndef STASSID
|
||||
#define STASSID "your-ssid"
|
||||
@@ -48,14 +49,13 @@ const int led = LED_BUILTIN;
|
||||
void handleRoot() {
|
||||
static int cnt = 0;
|
||||
digitalWrite(led, 1);
|
||||
char temp[400];
|
||||
int sec = millis() / 1000;
|
||||
int min = sec / 60;
|
||||
int hr = min / 60;
|
||||
|
||||
snprintf(temp, 400,
|
||||
|
||||
"<html>\
|
||||
StreamString temp;
|
||||
temp.reserve(500); // Preallocate a large chunk to avoid memory fragmentation
|
||||
temp.printf("<html>\
|
||||
<head>\
|
||||
<meta http-equiv='refresh' content='5'/>\
|
||||
<title>Pico-W Demo</title>\
|
||||
@@ -70,9 +70,7 @@ void handleRoot() {
|
||||
<p>Page Count: %d</p>\
|
||||
<img src=\"/test.svg\" />\
|
||||
</body>\
|
||||
</html>",
|
||||
|
||||
hr, min % 60, sec % 60, rp2040.getFreeHeap(), ++cnt);
|
||||
</html>", hr, min % 60, sec % 60, rp2040.getFreeHeap(), ++cnt);
|
||||
server.send(200, "text/html", temp);
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WebServer.h>
|
||||
#include <LEAmDNS.h>
|
||||
|
||||
#ifndef STASSID
|
||||
#define STASSID "your-ssid"
|
||||
#define STAPSK "your-password"
|
||||
#endif
|
||||
|
||||
const char* ssid = STASSID;
|
||||
const char* password = STAPSK;
|
||||
|
||||
WebServer server(80);
|
||||
|
||||
const int led = LED_BUILTIN;
|
||||
|
||||
void handleRoot() {
|
||||
digitalWrite(led, 1);
|
||||
server.send(200, "text/plain", "hello from pico w!\r\n");
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void handleNotFound() {
|
||||
digitalWrite(led, 1);
|
||||
String message = "File Not Found\n\n";
|
||||
message += "URI: ";
|
||||
message += server.uri();
|
||||
message += "\nMethod: ";
|
||||
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||
message += "\nArguments: ";
|
||||
message += server.args();
|
||||
message += "\n";
|
||||
for (uint8_t i = 0; i < server.args(); i++) {
|
||||
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||
}
|
||||
server.send(404, "text/plain", message);
|
||||
digitalWrite(led, 0);
|
||||
}
|
||||
|
||||
void setup(void) {
|
||||
pinMode(led, OUTPUT);
|
||||
digitalWrite(led, 0);
|
||||
Serial.begin(115200);
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFi.begin(ssid, password);
|
||||
Serial.println("");
|
||||
|
||||
// Wait for connection
|
||||
while (WiFi.status() != WL_CONNECTED) {
|
||||
delay(500);
|
||||
Serial.print(".");
|
||||
}
|
||||
Serial.println("");
|
||||
Serial.print("Connected to ");
|
||||
Serial.println(ssid);
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
|
||||
if (MDNS.begin("picow")) {
|
||||
Serial.println("MDNS responder started");
|
||||
}
|
||||
|
||||
server.on("/", handleRoot);
|
||||
|
||||
server.on("/inline", []() {
|
||||
server.send(200, "text/plain", "this works as well");
|
||||
});
|
||||
|
||||
server.on("/gif", []() {
|
||||
static const uint8_t gif[] = {
|
||||
0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 0x10, 0x00, 0x10, 0x00, 0x80, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x2c, 0x00, 0x00, 0x00, 0x00,
|
||||
0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x19, 0x8c, 0x8f, 0xa9, 0xcb, 0x9d,
|
||||
0x00, 0x5f, 0x74, 0xb4, 0x56, 0xb0, 0xb0, 0xd2, 0xf2, 0x35, 0x1e, 0x4c,
|
||||
0x0c, 0x24, 0x5a, 0xe6, 0x89, 0xa6, 0x4d, 0x01, 0x00, 0x3b
|
||||
};
|
||||
char gif_colored[sizeof(gif)];
|
||||
memcpy_P(gif_colored, gif, sizeof(gif));
|
||||
// Set the background to a random set of colors
|
||||
gif_colored[16] = millis() % 256;
|
||||
gif_colored[17] = millis() % 256;
|
||||
gif_colored[18] = millis() % 256;
|
||||
server.send(200, "image/gif", gif_colored, sizeof(gif_colored));
|
||||
});
|
||||
|
||||
server.onNotFound(handleNotFound);
|
||||
|
||||
/////////////////////////////////////////////////////////
|
||||
// Hook examples
|
||||
|
||||
server.addHook([](const String & method, const String & url, WiFiClient * client, WebServer::ContentTypeFunction contentType) {
|
||||
(void)method; // GET, PUT, ...
|
||||
(void)url; // example: /root/myfile.html
|
||||
(void)client; // the webserver tcp client connection
|
||||
(void)contentType; // contentType(".html") => "text/html"
|
||||
Serial.printf("A useless web hook has passed\n");
|
||||
return WebServer::CLIENT_REQUEST_CAN_CONTINUE;
|
||||
});
|
||||
|
||||
server.addHook([](const String&, const String & url, WiFiClient*, WebServer::ContentTypeFunction) {
|
||||
if (url.startsWith("/fail")) {
|
||||
Serial.printf("An always failing web hook has been triggered\n");
|
||||
return WebServer::CLIENT_MUST_STOP;
|
||||
}
|
||||
return WebServer::CLIENT_REQUEST_CAN_CONTINUE;
|
||||
});
|
||||
|
||||
server.addHook([](const String&, const String & url, WiFiClient * client, WebServer::ContentTypeFunction) {
|
||||
if (url.startsWith("/dump")) {
|
||||
Serial.printf("The dumper web hook is on the run\n");
|
||||
|
||||
// Here the request is not interpreted, so we cannot for sure
|
||||
// swallow the exact amount matching the full request+content,
|
||||
// hence the tcp connection cannot be handled anymore by the
|
||||
auto last = millis();
|
||||
while ((millis() - last) < 500) {
|
||||
char buf[32];
|
||||
size_t len = client->read((uint8_t*)buf, sizeof(buf));
|
||||
if (len > 0) {
|
||||
Serial.printf("(<%d> chars)", (int)len);
|
||||
Serial.write(buf, len);
|
||||
last = millis();
|
||||
}
|
||||
}
|
||||
// Two choices: return MUST STOP and webserver will close it
|
||||
// (we already have the example with '/fail' hook)
|
||||
// or IS GIVEN and webserver will forget it
|
||||
// trying with IS GIVEN and storing it on a dumb WiFiClient.
|
||||
// check the client connection: it should not immediately be closed
|
||||
// (make another '/dump' one to close the first)
|
||||
Serial.printf("\nTelling server to forget this connection\n");
|
||||
static WiFiClient forgetme = *client; // stop previous one if present and transfer client refcounter
|
||||
return WebServer::CLIENT_IS_GIVEN;
|
||||
}
|
||||
return WebServer::CLIENT_REQUEST_CAN_CONTINUE;
|
||||
});
|
||||
|
||||
// Hook examples
|
||||
/////////////////////////////////////////////////////////
|
||||
|
||||
server.begin();
|
||||
Serial.println("HTTP server started");
|
||||
}
|
||||
|
||||
void loop(void) {
|
||||
server.handleClient();
|
||||
MDNS.update();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Since things may just work, we'll redirect for now
|
||||
#include "WebServer.h"
|
||||
|
||||
using ESP8266WebServer = WebServer;
|
||||
@@ -249,20 +249,32 @@ void HTTPServer::httpHandleClient() {
|
||||
case HC_WAIT_READ:
|
||||
// Wait for data from client to become available
|
||||
if (_currentClient->available()) {
|
||||
if (_parseRequest(_currentClient)) {
|
||||
// because HTTP_MAX_SEND_WAIT is expressed in milliseconds,
|
||||
// it must be divided by 1000
|
||||
switch (_parseRequest(_currentClient)) {
|
||||
case CLIENT_REQUEST_CAN_CONTINUE:
|
||||
// Because HTTP_MAX_SEND_WAIT is expressed in milliseconds, it must be divided by 1000
|
||||
_currentClient->setTimeout(HTTP_MAX_SEND_WAIT / 1000);
|
||||
_contentLength = CONTENT_LENGTH_NOT_SET;
|
||||
_handleRequest();
|
||||
|
||||
// Fix for issue with Chrome based browsers: https://github.com/espressif/arduino-esp32/issues/3652
|
||||
// if (_currentClient->connected()) {
|
||||
// _currentStatus = HC_WAIT_CLOSE;
|
||||
// _statusChange = millis();
|
||||
// keepCurrentClient = true;
|
||||
// }
|
||||
}
|
||||
/* fallthrough */
|
||||
case CLIENT_REQUEST_IS_HANDLED:
|
||||
if (_currentClient->connected() || _currentClient->available()) {
|
||||
_currentStatus = HC_WAIT_CLOSE;
|
||||
_statusChange = millis();
|
||||
keepCurrentClient = true;
|
||||
} else {
|
||||
log_v("webserver: peer has closed after served\n");
|
||||
}
|
||||
break;
|
||||
case CLIENT_MUST_STOP:
|
||||
log_v("Close client\n");
|
||||
_currentClient->stop();
|
||||
break;
|
||||
case CLIENT_IS_GIVEN:
|
||||
// client must not be stopped but must not be handled here anymore
|
||||
// (example: tcp connection given to websocket)
|
||||
log_v("Give client\n");
|
||||
break;
|
||||
} // switch _parseRequest()
|
||||
} else { // !_currentClient->available()
|
||||
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
|
||||
keepCurrentClient = true;
|
||||
|
||||
@@ -48,6 +48,8 @@ enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
|
||||
#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)
|
||||
#define CONTENT_LENGTH_NOT_SET ((size_t) -2)
|
||||
|
||||
#define WEBSERVER_HAS_HOOK 1
|
||||
|
||||
class HTTPServer;
|
||||
|
||||
typedef struct {
|
||||
@@ -183,6 +185,25 @@ public:
|
||||
return _currentClient->write(file);
|
||||
}
|
||||
|
||||
// Hook
|
||||
enum ClientFuture { CLIENT_REQUEST_CAN_CONTINUE, CLIENT_REQUEST_IS_HANDLED, CLIENT_MUST_STOP, CLIENT_IS_GIVEN };
|
||||
typedef String(*ContentTypeFunction)(const String&);
|
||||
using HookFunction = std::function<ClientFuture(const String& method, const String& url, WiFiClient* client, ContentTypeFunction contentType)>;
|
||||
void addHook(HookFunction hook) {
|
||||
if (_hook) {
|
||||
auto previousHook = _hook;
|
||||
_hook = [previousHook, hook](const String & method, const String & url, WiFiClient * client, ContentTypeFunction contentType) {
|
||||
auto whatNow = previousHook(method, url, client, contentType);
|
||||
if (whatNow == CLIENT_REQUEST_CAN_CONTINUE) {
|
||||
return hook(method, url, client, contentType);
|
||||
}
|
||||
return whatNow;
|
||||
};
|
||||
} else {
|
||||
_hook = hook;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual size_t _currentClientWrite(const char* b, size_t l) {
|
||||
return _currentClient->write(b, l);
|
||||
@@ -193,7 +214,7 @@ protected:
|
||||
void _addRequestHandler(RequestHandler* handler);
|
||||
void _handleRequest();
|
||||
void _finalizeResponse();
|
||||
bool _parseRequest(WiFiClient* client);
|
||||
ClientFuture _parseRequest(WiFiClient* client);
|
||||
void _parseArguments(String data);
|
||||
static String _responseCodeToString(int code);
|
||||
bool _parseForm(WiFiClient* client, String boundary, uint32_t len);
|
||||
@@ -249,4 +270,6 @@ protected:
|
||||
String _snonce; // Store noance and opaque for future comparison
|
||||
String _sopaque;
|
||||
String _srealm; // Store the Auth realm between Calls
|
||||
|
||||
HookFunction _hook;
|
||||
};
|
||||
|
||||
@@ -78,7 +78,7 @@ static char* readBytesWithTimeout(WiFiClient* client, size_t maxLength, size_t&
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool HTTPServer::_parseRequest(WiFiClient* client) {
|
||||
HTTPServer::ClientFuture HTTPServer::_parseRequest(WiFiClient* client) {
|
||||
// Read the first line of HTTP request
|
||||
String req = client->readStringUntil('\r');
|
||||
client->readStringUntil('\n');
|
||||
@@ -93,7 +93,7 @@ bool HTTPServer::_parseRequest(WiFiClient* client) {
|
||||
int addr_end = req.indexOf(' ', addr_start + 1);
|
||||
if (addr_start == -1 || addr_end == -1) {
|
||||
log_e("Invalid request: %s", req.c_str());
|
||||
return false;
|
||||
return CLIENT_MUST_STOP;
|
||||
}
|
||||
|
||||
String methodStr = req.substring(0, addr_start);
|
||||
@@ -110,6 +110,13 @@ bool HTTPServer::_parseRequest(WiFiClient* client) {
|
||||
_chunked = false;
|
||||
_clientContentLength = 0; // not known yet, or invalid
|
||||
|
||||
if (_hook) {
|
||||
auto whatNow = _hook(methodStr, url, client, mime::getContentType);
|
||||
if (whatNow != CLIENT_REQUEST_CAN_CONTINUE) {
|
||||
return whatNow;
|
||||
}
|
||||
}
|
||||
|
||||
HTTPMethod method = HTTP_ANY;
|
||||
size_t num_methods = sizeof(_http_method_str) / sizeof(const char *);
|
||||
for (size_t i = 0; i < num_methods; i++) {
|
||||
@@ -120,7 +127,7 @@ bool HTTPServer::_parseRequest(WiFiClient* client) {
|
||||
}
|
||||
if (method == HTTP_ANY) {
|
||||
log_e("Unknown HTTP Method: %s", methodStr.c_str());
|
||||
return false;
|
||||
return CLIENT_MUST_STOP;
|
||||
}
|
||||
_currentMethod = method;
|
||||
|
||||
@@ -186,7 +193,7 @@ bool HTTPServer::_parseRequest(WiFiClient* client) {
|
||||
char* plainBuf = readBytesWithTimeout(client, _clientContentLength, plainLength, HTTP_MAX_POST_WAIT);
|
||||
if ((int)plainLength < (int)_clientContentLength) {
|
||||
free(plainBuf);
|
||||
return false;
|
||||
return CLIENT_MUST_STOP;
|
||||
}
|
||||
if (_clientContentLength > 0) {
|
||||
if (isEncoded) {
|
||||
@@ -214,7 +221,7 @@ bool HTTPServer::_parseRequest(WiFiClient* client) {
|
||||
// it IS a form
|
||||
_parseArguments(searchStr);
|
||||
if (!_parseForm(client, boundaryStr, _clientContentLength)) {
|
||||
return false;
|
||||
return CLIENT_MUST_STOP;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -249,7 +256,7 @@ bool HTTPServer::_parseRequest(WiFiClient* client) {
|
||||
log_v("Request: %s", url.c_str());
|
||||
log_v(" Arguments: %s", searchStr.c_str());
|
||||
|
||||
return true;
|
||||
return CLIENT_REQUEST_CAN_CONTINUE;
|
||||
}
|
||||
|
||||
bool HTTPServer::_collectHeader(const char* headerName, const char* headerValue) {
|
||||
|
||||
@@ -113,7 +113,7 @@ void loop() {
|
||||
client.setTrustAnchors(&cert);
|
||||
fetchURL(&client, host, port, path);
|
||||
finish = millis();
|
||||
Serial.printf("Total time: %dms\n", finish - start);
|
||||
Serial.printf("Total time: %lums\n", finish - start);
|
||||
|
||||
BearSSL::Session session;
|
||||
client.setSession(&session);
|
||||
@@ -122,21 +122,21 @@ void loop() {
|
||||
client.setTrustAnchors(&cert);
|
||||
fetchURL(&client, host, port, path);
|
||||
finish = millis();
|
||||
Serial.printf("Total time: %dms\n", finish - start);
|
||||
Serial.printf("Total time: %lums\n", finish - start);
|
||||
|
||||
Serial.printf("Connecting with the just initialized session...");
|
||||
start = millis();
|
||||
client.setTrustAnchors(&cert);
|
||||
fetchURL(&client, host, port, path);
|
||||
finish = millis();
|
||||
Serial.printf("Total time: %dms\n", finish - start);
|
||||
Serial.printf("Total time: %lums\n", finish - start);
|
||||
|
||||
Serial.printf("Connecting again with the initialized session...");
|
||||
start = millis();
|
||||
client.setTrustAnchors(&cert);
|
||||
fetchURL(&client, host, port, path);
|
||||
finish = millis();
|
||||
Serial.printf("Total time: %dms\n", finish - start);
|
||||
Serial.printf("Total time: %lums\n", finish - start);
|
||||
|
||||
delay(10000); // Avoid DDOSing github
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ void fetchURL(BearSSL::WiFiClientSecure *client, const char *host, const uint16_
|
||||
} while (millis() < to);
|
||||
}
|
||||
client->stop();
|
||||
Serial.printf("BSSL stack used: %d\n-------\n\n", stack_thunk_get_max_usage());
|
||||
Serial.printf("BSSL stack used: %lu\n-------\n\n", stack_thunk_get_max_usage());
|
||||
}
|
||||
|
||||
void fetchNoConfig() {
|
||||
|
||||
@@ -24,7 +24,7 @@ const char *encToString(uint8_t enc) {
|
||||
|
||||
void loop() {
|
||||
delay(5000);
|
||||
Serial.printf("Beginning scan at %d\n", millis());
|
||||
Serial.printf("Beginning scan at %lu\n", millis());
|
||||
auto cnt = WiFi.scanNetworks();
|
||||
if (!cnt) {
|
||||
Serial.printf("No networks found\n");
|
||||
@@ -34,7 +34,7 @@ void loop() {
|
||||
for (auto i = 0; i < cnt; i++) {
|
||||
uint8_t bssid[6];
|
||||
WiFi.BSSID(i, bssid);
|
||||
Serial.printf("%32s %5s %17s %2d %4d\n", WiFi.SSID(i), encToString(WiFi.encryptionType(i)), macToString(bssid), WiFi.channel(i), WiFi.RSSI(i));
|
||||
Serial.printf("%32s %5s %17s %2d %4ld\n", WiFi.SSID(i), encToString(WiFi.encryptionType(i)), macToString(bssid), WiFi.channel(i), WiFi.RSSI(i));
|
||||
}
|
||||
}
|
||||
Serial.printf("\n--- Sleeping ---\n\n\n");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user