mirror of
https://github.com/esp8266/Arduino.git
synced 2025-06-06 05:21:22 +03:00
Merge branch 'master' into master
This commit is contained in:
commit
0593cbdc00
271
.github/workflows/pull-request.yml
vendored
Normal file
271
.github/workflows/pull-request.yml
vendored
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
# Run whenever a PR is generated or updated.
|
||||||
|
|
||||||
|
# Most jobs check out the code, ensure Python3 is installed, and for build
|
||||||
|
# tests the ESP8266 toolchain is cached when possible to speed up execution.
|
||||||
|
|
||||||
|
name: ESP8266 Arduino CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
# Run 8 parallel jobs for the default build of all examples.
|
||||||
|
build-linux:
|
||||||
|
name: Build ${{ matrix.chunk }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
chunk: [0, 1, 2, 3, 4, 5, 6, 7]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Cache Linux toolchain
|
||||||
|
id: cache-linux
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ./tools/dist
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||||
|
- name: Build Sketches
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
BUILD_PARITY: custom
|
||||||
|
mod: 8
|
||||||
|
rem: ${{ matrix.chunk }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/build.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Cover the debug and IPv6 cases by enabling both and running 8 parallel jobs
|
||||||
|
# over all example code.
|
||||||
|
build-debug-ipv6:
|
||||||
|
name: Debug IPv6 ${{ matrix.chunk }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
chunk: [0, 1, 2, 3, 4, 5, 6, 7]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Cache Linux toolchain
|
||||||
|
id: cache-linux
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ./tools/dist
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||||
|
- name: Build Sketches
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
BUILD_PARITY: custom
|
||||||
|
mod: 8
|
||||||
|
rem: ${{ matrix.chunk }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/debug6.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Single build under Windows to ensure the Win toolchain is good.
|
||||||
|
build-windows:
|
||||||
|
name: Windows
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Cache Windows toolchain
|
||||||
|
id: cache-windows
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ./tools/dist
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_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
|
||||||
|
rem: 1
|
||||||
|
run: |
|
||||||
|
# Windows has python3 already installed, but it's called "python".
|
||||||
|
# Copy python.exe to the proper name so scripts "just work".
|
||||||
|
try { Get-Command python3 } catch { copy (get-command python).source (get-command python).source.Replace("python.exe", "python3.exe") }
|
||||||
|
bash ./tests/build.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Single build under macOS to ensure the Mac toolchain is good.
|
||||||
|
build-mac:
|
||||||
|
name: Mac
|
||||||
|
runs-on: macOS-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Cache Mac toolchain
|
||||||
|
id: cache-mac
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ./tools/dist
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_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
|
||||||
|
rem: 1
|
||||||
|
run: |
|
||||||
|
bash ./tests/build.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Run a few Platform.IO jobs (not full suite) to check PIO integration.
|
||||||
|
build-pio:
|
||||||
|
name: Build Platform.IO
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Build subset on Platform.IO
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
BUILD_PARITY: custom
|
||||||
|
mod: 42 # Picked at random to give 4-5 builds and exit.
|
||||||
|
rem: 13
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install python3-pip python3-setuptools
|
||||||
|
PATH=/home/runner/.local/bin:$PATH bash ./tests/platformio.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Run host test suite under valgrind for runtime checking of code.
|
||||||
|
host-tests:
|
||||||
|
name: Host tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Run host tests
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install valgrind lcov
|
||||||
|
bash ./tests/ci/host_test.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Ensure Sphinx can build the documentation properly.
|
||||||
|
documentation:
|
||||||
|
name: Documentation
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Build documentation
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install python3-pip python3-setuptools
|
||||||
|
# GitHub CI installs pip3 and setuptools outside the path.
|
||||||
|
# Update the path to include them and run.
|
||||||
|
PATH=/home/runner/.local/bin:$PATH pip3 install --user -r doc/requirements.txt
|
||||||
|
PATH=/home/runner/.local/bin:$PATH bash ./tests/ci/build_docs.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Standard Arduino formatting in all the examples
|
||||||
|
style-check:
|
||||||
|
name: Style and formatting
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Style check
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install astyle
|
||||||
|
bash ./tests/ci/style_check.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Quick test that the mocking builds succeed
|
||||||
|
mock-check:
|
||||||
|
name: Mock trivial test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Mock build
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/buildm.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Ensure no manual edits to boards.txt
|
||||||
|
boards-check:
|
||||||
|
name: Boards.txt check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Cache Linux toolchain
|
||||||
|
id: cache-linux
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ./tools/dist
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||||
|
- name: Boards.txt diff
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/ci/build_boards.sh
|
||||||
|
bash ./tests/ci/eboot_test.sh
|
||||||
|
bash ./tests/ci/pkgrefs_test.sh
|
56
.github/workflows/release-to-publish.yml
vendored
Normal file
56
.github/workflows/release-to-publish.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Whenever a release is published from a draft, this will update the
|
||||||
|
# master Arduino JSON file to add its new entry.
|
||||||
|
|
||||||
|
# We keep the master JSON file in another repo, so we need to use a pre-set
|
||||||
|
# Deployment SSH key to be able to push a change to the repo.
|
||||||
|
|
||||||
|
#### Steps to follow when you need to make a new SSH key for upload (not
|
||||||
|
#### normally needed!)
|
||||||
|
|
||||||
|
# Generate a new SSH key private/public pair
|
||||||
|
|
||||||
|
# ssh-keygen -t rsa -b 4096 -C "your@email.com" -f ./deploy_rsa
|
||||||
|
|
||||||
|
# Upload deploy_rsa.pub to the *ESP8266.GITHUB.IO* repo as a deployment key
|
||||||
|
|
||||||
|
# Convert the private key to base64 (to remove line breaks and allow easier
|
||||||
|
# usage in the script as an environment variable)
|
||||||
|
|
||||||
|
# base64.exe -w 0 < deploy_rsa > deploy_rsa.b64
|
||||||
|
|
||||||
|
# Copy the contents of the .b64 file to the clipboard, make a new GitHub
|
||||||
|
# secret in the ESP8266/Arduino repo called "GHCI_DEPLOY_KEY" and paste
|
||||||
|
# the B64 code into the variable.
|
||||||
|
|
||||||
|
name: ESP8266 Arduino Release Publisher
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
package:
|
||||||
|
name: Update master JSON file
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Set GIT tag name
|
||||||
|
run: |
|
||||||
|
echo "::set-env name=TRAVIS_TAG::$(git describe --exact-match --tags)"
|
||||||
|
- name: Deploy updated JSON
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
BUILD_TYPE: package
|
||||||
|
CI_GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GHCI_DEPLOY_KEY: ${{ secrets.GHCI_DEPLOY_KEY }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/ci/build_package.sh
|
||||||
|
# Only the regenerated JSON file will be used, but it's simpler
|
||||||
|
# than looking for it in a GH release.
|
||||||
|
bash ./package/deploy_package_index.sh
|
||||||
|
|
40
.github/workflows/tag-to-draft-release.yml
vendored
Normal file
40
.github/workflows/tag-to-draft-release.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Whenever a tag of the form #.xxxx is pushed against master, generate a
|
||||||
|
# draft release and upload the ZIP and JSON file to it. Maintainers then
|
||||||
|
# will manually add the changelist and publish it.
|
||||||
|
|
||||||
|
name: ESP8266 Arduino Draft Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
# Run for tags of the x.x.x* form (i.e. 3.0.0, 3.0.0-beta, etc.).
|
||||||
|
- '[0-9]+.[0-9]+.[0-9]+*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
package:
|
||||||
|
name: Package
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Set GIT tag name
|
||||||
|
run: |
|
||||||
|
# Sets an environment variable used in the next steps
|
||||||
|
echo "::set-env name=TRAVIS_TAG::$(git describe --exact-match --tags)"
|
||||||
|
- name: Build package JSON
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
BUILD_TYPE: package
|
||||||
|
CI_GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/ci/build_package.sh
|
||||||
|
pip3 install PyGithub
|
||||||
|
# Create a draft release and upload the ZIP and JSON files.
|
||||||
|
# This draft is not visible to normal users and needs to be
|
||||||
|
# updated manually with release notes and published from the
|
||||||
|
# GitHub web interface.
|
||||||
|
python3 ./package/upload_release.py --user "$GITHUB_ACTOR" --repo "$GITHUB_REPOSITORY" --token "$CI_GITHUB_API_KEY" --tag "$TRAVIS_TAG" --name "Release $TRAVIS_TAG" --msg "Update the draft with release notes before publishing." package/versions/*/*.zip package/versions/*/package_esp8266com_index.json
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -2,7 +2,9 @@
|
|||||||
tools/dist/
|
tools/dist/
|
||||||
tools/xtensa-lx106-elf/
|
tools/xtensa-lx106-elf/
|
||||||
tools/mkspiffs/
|
tools/mkspiffs/
|
||||||
|
tools/mklittlefs/
|
||||||
tools/python/
|
tools/python/
|
||||||
|
tools/python3/
|
||||||
package/versions/
|
package/versions/
|
||||||
exclude.txt
|
exclude.txt
|
||||||
tools/sdk/lib/liblwip_src.a
|
tools/sdk/lib/liblwip_src.a
|
||||||
@ -23,3 +25,10 @@ boards.local.txt
|
|||||||
*.gcda
|
*.gcda
|
||||||
*.o
|
*.o
|
||||||
*.a
|
*.a
|
||||||
|
|
||||||
|
#Ignore files built by Visual Studio/Visual Micro
|
||||||
|
[Dd]ebug*/
|
||||||
|
[Rr]elease*/
|
||||||
|
.vs/
|
||||||
|
__vm/
|
||||||
|
*.vcxproj*
|
||||||
|
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -7,6 +7,9 @@
|
|||||||
[submodule "libraries/SoftwareSerial"]
|
[submodule "libraries/SoftwareSerial"]
|
||||||
path = libraries/SoftwareSerial
|
path = libraries/SoftwareSerial
|
||||||
url = https://github.com/plerup/espsoftwareserial.git
|
url = https://github.com/plerup/espsoftwareserial.git
|
||||||
|
[submodule "libraries/LittleFS/lib/littlefs"]
|
||||||
|
path = libraries/LittleFS/lib/littlefs
|
||||||
|
url = https://github.com/ARMmbed/littlefs.git
|
||||||
[submodule "libraries/ESP8266SdFat"]
|
[submodule "libraries/ESP8266SdFat"]
|
||||||
path = libraries/ESP8266SdFat
|
path = libraries/ESP8266SdFat
|
||||||
url = https://github.com/earlephilhower/ESP8266SdFat.git
|
url = https://github.com/earlephilhower/ESP8266SdFat.git
|
||||||
@ -16,3 +19,9 @@
|
|||||||
[submodule "tools/esptool"]
|
[submodule "tools/esptool"]
|
||||||
path = tools/esptool
|
path = tools/esptool
|
||||||
url = https://github.com/espressif/esptool.git
|
url = https://github.com/espressif/esptool.git
|
||||||
|
[submodule "libraries/Ethernet"]
|
||||||
|
path = libraries/Ethernet
|
||||||
|
url = https://github.com/arduino-libraries/Ethernet.git
|
||||||
|
[submodule "tools/sdk/uzlib"]
|
||||||
|
path = tools/sdk/uzlib
|
||||||
|
url = https://github.com/pfalcon/uzlib.git
|
||||||
|
141
.travis.yml
141
.travis.yml
@ -1,141 +0,0 @@
|
|||||||
language: bash
|
|
||||||
os: linux
|
|
||||||
dist: trusty
|
|
||||||
|
|
||||||
git:
|
|
||||||
depth: 1
|
|
||||||
submodules: false
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- git submodule update --init # no recursive update
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/astyle
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- build
|
|
||||||
- deploy
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
# Build stage. To save time, run all kinds of builds and tests in parallel.
|
|
||||||
|
|
||||||
- name: "Platformio (1)"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/platformio.sh
|
|
||||||
env:
|
|
||||||
- BUILD_PARITY=even
|
|
||||||
- name: "Platformio (2)"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/platformio.sh
|
|
||||||
env:
|
|
||||||
- BUILD_PARITY=odd
|
|
||||||
|
|
||||||
- name: "Build (1)"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/build.sh
|
|
||||||
env:
|
|
||||||
- BUILD_PARITY=even
|
|
||||||
- name: "Build (2)"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/build.sh
|
|
||||||
env:
|
|
||||||
- BUILD_PARITY=odd
|
|
||||||
|
|
||||||
- name: "Debug (1)"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/debug.sh
|
|
||||||
env:
|
|
||||||
- BUILD_PARITY=even
|
|
||||||
- name: "Debug (2)"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/debug.sh
|
|
||||||
env:
|
|
||||||
- BUILD_PARITY=odd
|
|
||||||
|
|
||||||
- name: "Build IPv6 (1)"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/build6.sh
|
|
||||||
env:
|
|
||||||
- BUILD_PARITY=even
|
|
||||||
- name: "Build IPv6 (2)"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/build6.sh
|
|
||||||
env:
|
|
||||||
- BUILD_PARITY=odd
|
|
||||||
|
|
||||||
- name: "Build lwIP-v1.4 (1)"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/build1.sh
|
|
||||||
env:
|
|
||||||
- BUILD_PARITY=even
|
|
||||||
- name: "Build lwIP-v1.4 (2)"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/build1.sh
|
|
||||||
env:
|
|
||||||
- BUILD_PARITY=odd
|
|
||||||
|
|
||||||
- name: "Host tests"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/ci/host_test.sh
|
|
||||||
install: sudo apt-get install valgrind lcov
|
|
||||||
|
|
||||||
- name: "Docs"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/ci/build_docs.sh
|
|
||||||
install:
|
|
||||||
- sudo apt-get install python3-pip
|
|
||||||
- pip3 install --user -r doc/requirements.txt;
|
|
||||||
|
|
||||||
- name: "Style check"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/ci/style_check.sh
|
|
||||||
install: tests/ci/install_astyle.sh
|
|
||||||
|
|
||||||
- name: "Boards"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/ci/build_boards.sh
|
|
||||||
|
|
||||||
# Deploy stage.
|
|
||||||
# Here we build the package JSON (always) and do the deployments
|
|
||||||
- name: "Package / deploy"
|
|
||||||
stage: deploy
|
|
||||||
script: tests/ci/build_package.sh
|
|
||||||
env: BUILD_TYPE=package
|
|
||||||
before_deploy: git submodule update --init
|
|
||||||
deploy:
|
|
||||||
# Create Github release, upload artifacts
|
|
||||||
- provider: releases
|
|
||||||
draft: true
|
|
||||||
skip_cleanup: true
|
|
||||||
api_key:
|
|
||||||
secure: kYsxX/N21fwLSTLpbb0c96PnQHn1CIMqZstm02hfUhCX83FygWSh4vs3gzW28DMpjQMZ6vC4g+jtfosYU2tUhht/bynurDH4edpEyGeMyK+fzCI9pAr4JT0RbKQI84EC18ScpgP/UP0jTc1LJ+xl8UMwSiDE0mzHx7xJ4mMNQbA=
|
|
||||||
file_glob: true
|
|
||||||
tag_name: $TRAVIS_TAG
|
|
||||||
target_commitish: $TRAVIS_COMMIT
|
|
||||||
file:
|
|
||||||
- package/versions/$TRAVIS_TAG/esp8266-$TRAVIS_TAG.zip
|
|
||||||
- package/versions/$TRAVIS_TAG/package_esp8266com_index.json
|
|
||||||
on:
|
|
||||||
repo: esp8266/Arduino
|
|
||||||
tags: true
|
|
||||||
|
|
||||||
# Update the package index URL to point to the new version
|
|
||||||
- provider: script
|
|
||||||
skip_cleanup: true
|
|
||||||
script: bash package/deploy_package_index.sh
|
|
||||||
on:
|
|
||||||
repo: esp8266/Arduino
|
|
||||||
tags: true
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email:
|
|
||||||
on_success: change
|
|
||||||
on_failure: change
|
|
||||||
webhooks:
|
|
||||||
urls:
|
|
||||||
- secure: "dnSY+KA7NK+KD+Z71copmANDUsyVePrZ0iXvXxmqMEQv+lp3j2Z87G5pHn7j0WNcNZrejJqOdbElJ9Q4QESRaAYxTR7cA6ameJeEKHiFJrQtN/4abvoXb9E1CxpL8aNON/xgnqCk+fycOK3nbWWXlJBodzBm7KN64vrcHO7et+M="
|
|
||||||
on_success: change # options: [always|never|change] default: always
|
|
||||||
on_failure: always # options: [always|never|change] default: always
|
|
||||||
on_start: false # default: false
|
|
106
README.md
106
README.md
@ -3,20 +3,20 @@ Arduino core for ESP8266 WiFi chip
|
|||||||
|
|
||||||
# Quick links
|
# Quick links
|
||||||
|
|
||||||
- [Latest release documentation](https://arduino-esp8266.readthedocs.io/en/2.5.2/)
|
- [Latest release documentation](https://arduino-esp8266.readthedocs.io/en/2.7.4_a/)
|
||||||
- [Current "git version" documentation](https://arduino-esp8266.readthedocs.io/en/latest/)
|
- [Current "git version" documentation](https://arduino-esp8266.readthedocs.io/en/latest/)
|
||||||
- [Install git version](https://arduino-esp8266.readthedocs.io/en/latest/installing.html#using-git-version) ([sources](doc/installing.rst#using-git-version))
|
- [Install git version](https://arduino-esp8266.readthedocs.io/en/latest/installing.html#using-git-version) ([sources](doc/installing.rst#using-git-version))
|
||||||
|
|
||||||
# Arduino on ESP8266
|
# Arduino on ESP8266
|
||||||
|
|
||||||
This project brings support for ESP8266 chip to the Arduino environment. It lets you write sketches using familiar Arduino functions and libraries, and run them directly on ESP8266, no external microcontroller required.
|
This project brings support for the ESP8266 chip to the Arduino environment. It lets you write sketches, using familiar Arduino functions and libraries, and run them directly on ESP8266, with no external microcontroller required.
|
||||||
|
|
||||||
ESP8266 Arduino core comes with libraries to communicate over WiFi using TCP and UDP, set up HTTP, mDNS, SSDP, and DNS servers, do OTA updates, use a file system in flash memory, work with SD cards, servos, SPI and I2C peripherals.
|
ESP8266 Arduino core comes with libraries to communicate over WiFi using TCP and UDP, set up HTTP, mDNS, SSDP, and DNS servers, do OTA updates, use a file system in flash memory, and work with SD cards, servos, SPI and I2C peripherals.
|
||||||
|
|
||||||
# Contents
|
# Contents
|
||||||
- Installing options:
|
- Installing options:
|
||||||
- [Using Boards Manager](#installing-with-boards-manager)
|
- [Using Boards Manager](#installing-with-boards-manager)
|
||||||
- [Using git version](#using-git-version-basic-instructions)
|
- [Using git version](#using-git-version)
|
||||||
- [Using PlatformIO](#using-platformio)
|
- [Using PlatformIO](#using-platformio)
|
||||||
- [Building with make](#building-with-make)
|
- [Building with make](#building-with-make)
|
||||||
- [Documentation](#documentation)
|
- [Documentation](#documentation)
|
||||||
@ -28,61 +28,39 @@ ESP8266 Arduino core comes with libraries to communicate over WiFi using TCP and
|
|||||||
|
|
||||||
Starting with 1.6.4, Arduino allows installation of third-party platform packages using Boards Manager. We have packages available for Windows, Mac OS, and Linux (32 and 64 bit).
|
Starting with 1.6.4, Arduino allows installation of third-party platform packages using Boards Manager. We have packages available for Windows, Mac OS, and Linux (32 and 64 bit).
|
||||||
|
|
||||||
- Install the current upstream Arduino IDE at the 1.8.7 level or later. The current version is at the [Arduino website](https://www.arduino.cc/en/main/software).
|
- Install the current upstream Arduino IDE at the 1.8.9 level or later. The current version is on the [Arduino website](https://www.arduino.cc/en/main/software).
|
||||||
- Start Arduino and open Preferences window.
|
- Start Arduino and open the Preferences window.
|
||||||
- Enter ```https://arduino.esp8266.com/stable/package_esp8266com_index.json``` into *Additional Board Manager URLs* field. You can add multiple URLs, separating them with commas.
|
- Enter ```https://arduino.esp8266.com/stable/package_esp8266com_index.json``` into the *File>Preferences>Additional Boards Manager URLs* field of the Arduino IDE. You can add multiple URLs, separating them with commas.
|
||||||
- Open Boards Manager from Tools > Board menu and install *esp8266* platform (and don't forget to select your ESP8266 board from Tools > Board menu after installation).
|
- Open Boards Manager from Tools > Board menu and install *esp8266* platform (and don't forget to select your ESP8266 board from Tools > Board menu after installation).
|
||||||
|
|
||||||
#### Latest release [](https://github.com/esp8266/Arduino/releases/latest/)
|
#### Latest release [](https://github.com/esp8266/Arduino/releases/latest/)
|
||||||
Boards manager link: `https://arduino.esp8266.com/stable/package_esp8266com_index.json`
|
Boards manager link: `https://arduino.esp8266.com/stable/package_esp8266com_index.json`
|
||||||
|
|
||||||
Documentation: [https://arduino-esp8266.readthedocs.io/en/2.5.2/](https://arduino-esp8266.readthedocs.io/en/2.5.2/)
|
Documentation: [https://arduino-esp8266.readthedocs.io/en/2.7.4_a/](https://arduino-esp8266.readthedocs.io/en/2.7.4_a/)
|
||||||
|
|
||||||
### Using git version (basic instructions)
|
### Using git version
|
||||||
[](https://travis-ci.org/esp8266/Arduino)
|
[](https://travis-ci.org/esp8266/Arduino)
|
||||||
|
|
||||||
- Install the current upstream Arduino IDE at the 1.8 level or later. The current version is at the [Arduino website](https://www.arduino.cc/en/main/software).
|
Also known as latest git or master branch.
|
||||||
- Go to Arduino directory
|
|
||||||
- For Mac OS X, it is `Arduino.app` showing as the Arduino icon.
|
- Install the current upstream Arduino IDE at the 1.8 level or later. The current version is on the [Arduino website](https://www.arduino.cc/en/main/software).
|
||||||
This location may be your `~/Downloads`, `~/Desktop` or even `/Applications`.
|
- Follow the [instructions in the documentation](https://arduino-esp8266.readthedocs.io/en/latest/installing.html#using-git-version).
|
||||||
```bash
|
|
||||||
cd <application-directory>/Arduino.app/Contents/Java
|
|
||||||
```
|
|
||||||
- For Linux, it is ~/Arduino by default.
|
|
||||||
```bash
|
|
||||||
cd ~/Arduino
|
|
||||||
```
|
|
||||||
- Clone this repository into hardware/esp8266com/esp8266 directory (or clone it elsewhere and create a symlink)
|
|
||||||
```bash
|
|
||||||
cd hardware
|
|
||||||
mkdir esp8266com
|
|
||||||
cd esp8266com
|
|
||||||
git clone https://github.com/esp8266/Arduino.git esp8266
|
|
||||||
cd esp8266
|
|
||||||
git submodule update --init
|
|
||||||
```
|
|
||||||
- Download binary tools (you need Python 2.7)
|
|
||||||
```bash
|
|
||||||
cd esp8266/tools
|
|
||||||
python get.py
|
|
||||||
```
|
|
||||||
- Restart Arduino
|
|
||||||
|
|
||||||
### Using PlatformIO
|
### Using PlatformIO
|
||||||
|
|
||||||
[PlatformIO](https://platformio.org?utm_source=github&utm_medium=arduino-esp8266) is an open source ecosystem for IoT
|
[PlatformIO](https://platformio.org?utm_source=arduino-esp8266) is an open source ecosystem for IoT
|
||||||
development with cross platform build system, library manager and full support
|
development with a cross-platform build system, a library manager, and full support
|
||||||
for Espressif (ESP8266) development. It works on the popular host OS: macOS, Windows,
|
for Espressif (ESP8266) development. It works on the following popular host operating systems: macOS, Windows,
|
||||||
Linux 32/64, Linux ARM (like Raspberry Pi, BeagleBone, CubieBoard).
|
Linux 32/64, and Linux ARM (like Raspberry Pi, BeagleBone, CubieBoard).
|
||||||
|
|
||||||
- [What is PlatformIO?](https://docs.platformio.org/en/latest/what-is-platformio.html?utm_source=github&utm_medium=arduino-esp8266)
|
- [What is PlatformIO?](https://docs.platformio.org/en/latest/what-is-platformio.html?utm_source=arduino-esp8266)
|
||||||
- [PlatformIO IDE](https://platformio.org/platformio-ide?utm_source=github&utm_medium=arduino-esp8266)
|
- [PlatformIO IDE](https://platformio.org/platformio-ide?utm_source=arduino-esp8266)
|
||||||
- [PlatformIO Core](https://docs.platformio.org/en/latest/core.html?utm_source=github&utm_medium=arduino-esp8266) (command line tool)
|
- [PlatformIO Core](https://docs.platformio.org/en/latest/core.html?utm_source=arduino-esp8266) (command line tool)
|
||||||
- [Advanced usage](https://docs.platformio.org/en/latest/platforms/espressif8266.html?utm_source=github&utm_medium=arduino-esp8266) -
|
- [Advanced usage](https://docs.platformio.org/en/latest/platforms/espressif8266.html?utm_source=arduino-esp8266) -
|
||||||
custom settings, uploading to SPIFFS, Over-the-Air (OTA), staging version
|
custom settings, uploading to SPIFFS, Over-the-Air (OTA), staging version
|
||||||
- [Integration with Cloud and Standalone IDEs](https://docs.platformio.org/en/latest/ide.html?utm_source=github&utm_medium=arduino-esp8266) -
|
- [Integration with Cloud and Standalone IDEs](https://docs.platformio.org/en/latest/ide.html?utm_source=arduino-esp8266) -
|
||||||
Cloud9, Codeanywhere, Eclipse Che (Codenvy), Atom, CLion, Eclipse, Emacs, NetBeans, Qt Creator, Sublime Text, VIM, Visual Studio, and VSCode
|
Cloud9, Codeanywhere, Eclipse Che (Codenvy), Atom, CLion, Eclipse, Emacs, NetBeans, Qt Creator, Sublime Text, VIM, Visual Studio, and VSCode
|
||||||
- [Project Examples](https://docs.platformio.org/en/latest/platforms/espressif8266.html?utm_source=github&utm_medium=arduino-esp8266#examples)
|
- [Project Examples](https://docs.platformio.org/en/latest/platforms/espressif8266.html?utm_source=arduino-esp8266#examples)
|
||||||
|
|
||||||
### Building with make
|
### Building with make
|
||||||
|
|
||||||
@ -95,7 +73,7 @@ Documentation for latest development version: https://arduino-esp8266.readthedoc
|
|||||||
|
|
||||||
### Issues and support ###
|
### Issues and support ###
|
||||||
|
|
||||||
[ESP8266 Community Forum](https://www.esp8266.com/u/arduinoanswers) is a well established community for questions and answers about Arduino for ESP8266. If you need help, have a "How do I..." type question, have a problem with a 3rd party lib not hosted in this repo, or just want to discuss how to approach a problem , please ask there.
|
[ESP8266 Community Forum](https://www.esp8266.com/u/arduinoanswers) is a well-established community for questions and answers about Arduino for ESP8266. Stackoverflow is also an alternative. If you need help, have a "How do I..." type question, have a problem with a 3rd party library not hosted in this repo, or just want to discuss how to approach a problem, please ask there.
|
||||||
|
|
||||||
If you find the forum useful, please consider supporting it with a donation. <br />
|
If you find the forum useful, please consider supporting it with a donation. <br />
|
||||||
[](https://www.paypal.com/webscr?cmd=_s-xclick&hosted_button_id=4M56YCWV6PX66)
|
[](https://www.paypal.com/webscr?cmd=_s-xclick&hosted_button_id=4M56YCWV6PX66)
|
||||||
@ -106,19 +84,19 @@ Please provide as much context as possible, as well as the information requested
|
|||||||
|
|
||||||
- ESP8266 Arduino core version which you are using (you can check it in Boards Manager)
|
- ESP8266 Arduino core version which you are using (you can check it in Boards Manager)
|
||||||
- your sketch code; please wrap it into a code block, see [Github markdown manual](https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code)
|
- your sketch code; please wrap it into a code block, see [Github markdown manual](https://help.github.com/articles/basic-writing-and-formatting-syntax/#quoting-code)
|
||||||
- when encountering an issue which happens at run time, attach serial output. Wrap it into a code block, just like the code.
|
- when encountering an issue that happens at run time, attach the serial output. Wrap it into a code block, just like the code.
|
||||||
- for issues which happen at compile time, enable verbose compiler output in the IDE preferences, and attach that output (also inside a code block)
|
- for issues that happen at compile time, enable verbose compiler output in the IDE preferences, and attach that output (also inside a code block)
|
||||||
- ESP8266 development board model
|
- ESP8266 development board model
|
||||||
- IDE settings (board choice, flash size)
|
- IDE settings (board choice, flash size)
|
||||||
- etc
|
- etc
|
||||||
|
|
||||||
### Contributing
|
### Contributing
|
||||||
|
|
||||||
For minor fixes of code and documentation, please go ahead and submit a pull request.
|
For minor fixes of code and documentation, please go ahead and submit a pull request. A gentle introduction to the process can be found [here](https://www.freecodecamp.org/news/a-simple-git-guide-and-cheat-sheet-for-open-source-contributors/).
|
||||||
|
|
||||||
Check out the list of issues which are easy to fix — [easy issues pending](https://github.com/esp8266/Arduino/issues?q=is%3Aopen+is%3Aissue+label%3A%22level%3A+easy%22). Working on them is a great way to move the project forward.
|
Check out the list of issues that are easy to fix — [easy issues pending](https://github.com/esp8266/Arduino/issues?q=is%3Aopen+is%3Aissue+label%3A%22level%3A+easy%22). Working on them is a great way to move the project forward.
|
||||||
|
|
||||||
Larger changes (rewriting parts of existing code from scratch, adding new functions to the core, adding new libraries) should generally be discussed by opening an issue first.
|
Larger changes (rewriting parts of existing code from scratch, adding new functions to the core, adding new libraries) should generally be discussed by opening an issue first. PRs with such changes require testing and approval.
|
||||||
|
|
||||||
Feature branches with lots of small commits (especially titled "oops", "fix typo", "forgot to add file", etc.) should be squashed before opening a pull request. At the same time, please refrain from putting multiple unrelated changes into a single pull request.
|
Feature branches with lots of small commits (especially titled "oops", "fix typo", "forgot to add file", etc.) should be squashed before opening a pull request. At the same time, please refrain from putting multiple unrelated changes into a single pull request.
|
||||||
|
|
||||||
@ -128,18 +106,32 @@ Arduino IDE is developed and maintained by the Arduino team. The IDE is licensed
|
|||||||
|
|
||||||
ESP8266 core includes an xtensa gcc toolchain, which is also under GPL.
|
ESP8266 core includes an xtensa gcc toolchain, which is also under GPL.
|
||||||
|
|
||||||
Esptool written by Christian Klippel is licensed under GPLv2, currently maintained by Ivan Grokhotkov: https://github.com/igrr/esptool-ck.
|
Esptool.py was initially created by Fredrik Ahlberg (@themadinventor, @kongo), and is currently maintained by Angus Gratton (@projectgus) under GPL 2.0 license.
|
||||||
|
|
||||||
Espressif SDK included in this build is under Espressif MIT License.
|
[Espressif's NONOS SDK](https://github.com/espressif/ESP8266_NONOS_SDK) included in this build is under Espressif MIT License.
|
||||||
|
|
||||||
ESP8266 core files are licensed under LGPL.
|
ESP8266 core files are licensed under LGPL.
|
||||||
|
|
||||||
[SPI Flash File System (SPIFFS)](https://github.com/pellepl/spiffs) written by Peter Andersson is used in this project. It is distributed under MIT license.
|
[SPI Flash File System (SPIFFS)](https://github.com/pellepl/spiffs) written by Peter Andersson is used in this project. It is distributed under the MIT license.
|
||||||
|
|
||||||
[umm_malloc](https://github.com/rhempel/umm_malloc) memory management library written by Ralph Hempel is used in this project. It is distributed under MIT license.
|
[umm_malloc](https://github.com/rhempel/umm_malloc) memory management library written by Ralph Hempel is used in this project. It is distributed under the MIT license.
|
||||||
|
|
||||||
[SoftwareSerial](https://github.com/plerup/espsoftwareserial) library and examples written by Peter Lerup. Distributed under LGPL 2.1.
|
[SoftwareSerial](https://github.com/plerup/espsoftwareserial) library and examples written by Peter Lerup. Distributed under LGPL 2.1.
|
||||||
|
|
||||||
[axTLS](http://axtls.sourceforge.net/) library written by Cameron Rich, built from https://github.com/igrr/axtls-8266, is used in this project. It is distributed under [BSD license](https://github.com/igrr/axtls-8266/blob/master/LICENSE).
|
|
||||||
|
|
||||||
[BearSSL](https://bearssl.org) library written by Thomas Pornin, built from https://github.com/earlephilhower/bearssl-esp8266, is used in this project. It is distributed under the [MIT License](https://bearssl.org/#legal-details).
|
[BearSSL](https://bearssl.org) library written by Thomas Pornin, built from https://github.com/earlephilhower/bearssl-esp8266, is used in this project. It is distributed under the [MIT License](https://bearssl.org/#legal-details).
|
||||||
|
|
||||||
|
[LittleFS](https://github.com/ARMmbed/littlefs) library written by ARM Limited and released under the [BSD 3-clause license](https://github.com/ARMmbed/littlefs/blob/master/LICENSE.md).
|
||||||
|
|
||||||
|
[uzlib](https://github.com/pfalcon/uzlib) library written and (c) 2014-2018 Paul Sokolovsky, licensed under the ZLib license (https://www.zlib.net/zlib_license.html). uzlib is based on: tinf library by Joergen Ibsen (Deflate decompression); Deflate Static Huffman tree routines by Simon Tatham; LZ77 compressor by Paul Sokolovsky; with library integrated and maintained by Paul Sokolovsky.
|
||||||
|
|
||||||
|
### Other useful links ###
|
||||||
|
|
||||||
|
[Toolchain repo](https://github.com/earlephilhower/esp-quick-toolchain)
|
||||||
|
|
||||||
|
[Lwip link layer repo](https://github.com/d-a-v/esp82xx-nonos-linklayer)
|
||||||
|
|
||||||
|
[SoftwareSerial repo](https://github.com/plerup/espsoftwareserial)
|
||||||
|
|
||||||
|
[Serial Monitor Arduino IDE plugin](https://github.com/mytrain/arduino-esp8266-serial-plugin) Original discussion [here](https://github.com/esp8266/Arduino/issues/1360), quick download [there](http://mytrain.fr/cms//images/mytrain/private/ESP8266SM.v3.zip).
|
||||||
|
|
||||||
|
[FTP Client/Server Library](https://github.com/dplasa/FTPClientServer)
|
||||||
|
11971
boards.txt
11971
boards.txt
File diff suppressed because it is too large
Load Diff
@ -6,25 +6,30 @@ TARGET_DIR := ./
|
|||||||
|
|
||||||
TARGET_OBJ_FILES := \
|
TARGET_OBJ_FILES := \
|
||||||
eboot.o \
|
eboot.o \
|
||||||
eboot_command.o \
|
eboot_command.o
|
||||||
|
|
||||||
|
|
||||||
TARGET_OBJ_PATHS := $(addprefix $(TARGET_DIR)/,$(TARGET_OBJ_FILES))
|
TARGET_OBJ_PATHS := $(addprefix $(TARGET_DIR)/,$(TARGET_OBJ_FILES))
|
||||||
|
|
||||||
|
UZLIB_PATH := ../../tools/sdk/uzlib/src
|
||||||
|
UZLIB_FLAGS := -DRUNTIME_BITS_TABLES
|
||||||
|
|
||||||
CC := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-gcc
|
CC := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-gcc
|
||||||
CXX := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-g++
|
CXX := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-g++
|
||||||
AR := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-ar
|
AR := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-ar
|
||||||
LD := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-gcc
|
LD := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-gcc
|
||||||
OBJDUMP := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-objdump
|
OBJDUMP := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-objdump
|
||||||
|
|
||||||
INC += -I../../tools/sdk/include
|
INC += -I../../tools/sdk/include -I../../tools/sdk/uzlib/src
|
||||||
CFLAGS += -std=gnu99
|
|
||||||
|
|
||||||
CFLAGS += -O0 -g -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mno-text-section-literals
|
CFLAGS += -std=gnu17
|
||||||
|
|
||||||
|
CFLAGS += -Os -fcommon -g -Wall -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mno-text-section-literals -ffunction-sections -fdata-sections -free -fipa-pta
|
||||||
|
|
||||||
CFLAGS += $(INC)
|
CFLAGS += $(INC)
|
||||||
|
|
||||||
LDFLAGS += -nostdlib -Wl,--no-check-sections -umain
|
CFLAGS += $(UZLIB_FLAGS)
|
||||||
|
|
||||||
|
LDFLAGS += -nostdlib -Wl,--no-check-sections -Wl,--gc-sections -umain -Wl,-Map,$(@:.elf=.map)
|
||||||
|
|
||||||
LD_SCRIPT := -Teboot.ld
|
LD_SCRIPT := -Teboot.ld
|
||||||
|
|
||||||
@ -32,23 +37,26 @@ APP_OUT:= eboot.elf
|
|||||||
APP_AR := eboot.a
|
APP_AR := eboot.a
|
||||||
APP_FW := eboot.bin
|
APP_FW := eboot.bin
|
||||||
|
|
||||||
all: $(APP_FW)
|
|
||||||
|
|
||||||
$(APP_AR): $(TARGET_OBJ_PATHS)
|
all: $(APP_OUT)
|
||||||
|
|
||||||
|
tinflate.o: $(UZLIB_PATH)/tinflate.c $(UZLIB_PATH)/uzlib.h $(UZLIB_PATH)/uzlib_conf.h Makefile
|
||||||
|
$(CC) $(CFLAGS) -c -o tinflate.o $(UZLIB_PATH)/tinflate.c
|
||||||
|
|
||||||
|
tinfgzip.o: $(UZLIB_PATH)/tinfgzip.c $(UZLIB_PATH)/uzlib.h $(UZLIB_PATH)/uzlib_conf.h Makefile
|
||||||
|
$(CC) $(CFLAGS) -c -o tinfgzip.o $(UZLIB_PATH)/tinfgzip.c
|
||||||
|
|
||||||
|
$(APP_AR): $(TARGET_OBJ_PATHS) tinflate.o tinfgzip.o Makefile
|
||||||
$(AR) cru $@ $^
|
$(AR) cru $@ $^
|
||||||
|
|
||||||
|
$(APP_OUT): $(APP_AR) eboot.ld | Makefile
|
||||||
$(APP_OUT): $(APP_AR)
|
$(LD) $(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group -Wl,--sort-common $(APP_AR) -Wl,--end-group -o $@
|
||||||
$(LD) $(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group -Wl,--whole-archive $(APP_AR) -Wl,--end-group -o $@
|
|
||||||
|
|
||||||
$(APP_FW): $(APP_OUT)
|
|
||||||
$(ESPTOOL) -vvv -eo $(APP_OUT) -bo $@ -bs .text -bs .data -bs .rodata -bc -ec || true
|
|
||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *.o
|
rm -f *.o
|
||||||
rm -f $(APP_AR)
|
rm -f $(APP_AR)
|
||||||
rm -f $(APP_OUT)
|
rm -f $(APP_OUT)
|
||||||
|
rm -f *.map
|
||||||
|
|
||||||
|
|
||||||
.PHONY: all clean default
|
.PHONY: all clean default
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "flash.h"
|
#include "flash.h"
|
||||||
#include "eboot_command.h"
|
#include "eboot_command.h"
|
||||||
|
#include <uzlib.h>
|
||||||
|
|
||||||
|
|
||||||
#define SWRST do { (*((volatile uint32_t*) 0x60000700)) |= 0x80000000; } while(0);
|
#define SWRST do { (*((volatile uint32_t*) 0x60000700)) |= 0x80000000; } while(0);
|
||||||
|
|
||||||
@ -24,11 +26,7 @@ int print_version(const uint32_t flash_addr)
|
|||||||
if (SPIRead(flash_addr + APP_START_OFFSET + sizeof(image_header_t) + sizeof(section_header_t), &ver, sizeof(ver))) {
|
if (SPIRead(flash_addr + APP_START_OFFSET + sizeof(image_header_t) + sizeof(section_header_t), &ver, sizeof(ver))) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
const char* __attribute__ ((aligned (4))) fmtt = "v%08x\n\0\0";
|
ets_printf("v%08x\n", ver);
|
||||||
uint32_t fmt[2];
|
|
||||||
fmt[0] = ((uint32_t*) fmtt)[0];
|
|
||||||
fmt[1] = ((uint32_t*) fmtt)[1];
|
|
||||||
ets_printf((const char*) fmt, ver);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +59,9 @@ int load_app_from_flash_raw(const uint32_t flash_addr)
|
|||||||
load = true;
|
load = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (address >= 0x40100000 && address < 0x40108000) {
|
// The final IRAM size, once boot has completed, can be either 32K or 48K.
|
||||||
|
// Allow for the higher in range testing.
|
||||||
|
if (address >= 0x40100000 && address < 0x4010C000) {
|
||||||
load = true;
|
load = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,41 +80,125 @@ int load_app_from_flash_raw(const uint32_t flash_addr)
|
|||||||
pos += section_header.size;
|
pos += section_header.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
register uint32_t sp asm("a1") = 0x3ffffff0;
|
asm volatile("" ::: "memory");
|
||||||
register uint32_t pc asm("a3") = image_header.entry;
|
asm volatile ("mov.n a1, %0\n"
|
||||||
__asm__ __volatile__ ("jx a3");
|
"mov.n a3, %1\n"
|
||||||
|
"jx a3\n" : : "r" (0x3ffffff0), "r" (image_header.entry) );
|
||||||
|
|
||||||
|
__builtin_unreachable(); // Save a few bytes by letting GCC know no need to pop regs/return
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t read_flash_byte(const uint32_t addr)
|
||||||
|
{
|
||||||
|
uint8_t __attribute__((aligned(4))) buff[4];
|
||||||
|
SPIRead(addr & ~3, buff, 4);
|
||||||
|
return buff[addr & 3];
|
||||||
|
}
|
||||||
|
unsigned char __attribute__((aligned(4))) uzlib_flash_read_cb_buff[4096];
|
||||||
|
uint32_t uzlib_flash_read_cb_addr;
|
||||||
|
int uzlib_flash_read_cb(struct uzlib_uncomp *m)
|
||||||
|
{
|
||||||
|
m->source = uzlib_flash_read_cb_buff;
|
||||||
|
m->source_limit = uzlib_flash_read_cb_buff + sizeof(uzlib_flash_read_cb_buff);
|
||||||
|
SPIRead(uzlib_flash_read_cb_addr, uzlib_flash_read_cb_buff, sizeof(uzlib_flash_read_cb_buff));
|
||||||
|
uzlib_flash_read_cb_addr += sizeof(uzlib_flash_read_cb_buff);
|
||||||
|
return *(m->source++);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char gzip_dict[32768];
|
||||||
|
uint8_t buffer2[FLASH_SECTOR_SIZE]; // no room for this on the stack
|
||||||
|
|
||||||
int copy_raw(const uint32_t src_addr,
|
int copy_raw(const uint32_t src_addr,
|
||||||
const uint32_t dst_addr,
|
const uint32_t dst_addr,
|
||||||
const uint32_t size)
|
const uint32_t size,
|
||||||
|
const bool verify)
|
||||||
{
|
{
|
||||||
// require regions to be aligned
|
// require regions to be aligned
|
||||||
if (src_addr & 0xfff != 0 ||
|
if ((src_addr & 0xfff) != 0 ||
|
||||||
dst_addr & 0xfff != 0) {
|
(dst_addr & 0xfff) != 0) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t buffer_size = FLASH_SECTOR_SIZE;
|
const uint32_t buffer_size = FLASH_SECTOR_SIZE;
|
||||||
uint8_t buffer[buffer_size];
|
uint8_t buffer[buffer_size];
|
||||||
uint32_t left = ((size+buffer_size-1) & ~(buffer_size-1));
|
int32_t left = ((size+buffer_size-1) & ~(buffer_size-1));
|
||||||
uint32_t saddr = src_addr;
|
uint32_t saddr = src_addr;
|
||||||
uint32_t daddr = dst_addr;
|
uint32_t daddr = dst_addr;
|
||||||
|
struct uzlib_uncomp m_uncomp;
|
||||||
|
bool gzip = false;
|
||||||
|
|
||||||
while (left) {
|
// Check if we are uncompressing a GZIP upload or not
|
||||||
if (SPIEraseSector(daddr/buffer_size)) {
|
if ((read_flash_byte(saddr) == 0x1f) && (read_flash_byte(saddr + 1) == 0x8b)) {
|
||||||
return 2;
|
// GZIP signature matched. Find real size as encoded at the end
|
||||||
|
left = read_flash_byte(saddr + size - 4);
|
||||||
|
left += read_flash_byte(saddr + size - 3)<<8;
|
||||||
|
left += read_flash_byte(saddr + size - 2)<<16;
|
||||||
|
left += read_flash_byte(saddr + size - 1)<<24;
|
||||||
|
|
||||||
|
uzlib_init();
|
||||||
|
|
||||||
|
/* all 3 fields below must be initialized by user */
|
||||||
|
m_uncomp.source = NULL;
|
||||||
|
m_uncomp.source_limit = NULL;
|
||||||
|
uzlib_flash_read_cb_addr = src_addr;
|
||||||
|
m_uncomp.source_read_cb = uzlib_flash_read_cb;
|
||||||
|
uzlib_uncompress_init(&m_uncomp, gzip_dict, sizeof(gzip_dict));
|
||||||
|
|
||||||
|
int res = uzlib_gzip_parse_header(&m_uncomp);
|
||||||
|
if (res != TINF_OK) {
|
||||||
|
return 5; // Error uncompress header read
|
||||||
}
|
}
|
||||||
|
gzip = true;
|
||||||
|
}
|
||||||
|
while (left > 0) {
|
||||||
|
if (!gzip) {
|
||||||
if (SPIRead(saddr, buffer, buffer_size)) {
|
if (SPIRead(saddr, buffer, buffer_size)) {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
m_uncomp.dest_start = buffer;
|
||||||
|
m_uncomp.dest = buffer;
|
||||||
|
int to_read = (left > buffer_size) ? buffer_size : left;
|
||||||
|
m_uncomp.dest_limit = buffer + to_read;
|
||||||
|
int res = uzlib_uncompress(&m_uncomp);
|
||||||
|
if ((res != TINF_DONE) && (res != TINF_OK)) {
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
// Fill any remaining with 0xff
|
||||||
|
for (int i = to_read; i < buffer_size; i++) {
|
||||||
|
buffer[i] = 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (verify) {
|
||||||
|
if (SPIRead(daddr, buffer2, buffer_size)) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if (memcmp(buffer, buffer2, buffer_size)) {
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Special treatment for address 0 (bootloader). Only erase and
|
||||||
|
// rewrite if the data is different (i.e. very rarely).
|
||||||
|
bool skip = false;
|
||||||
|
if (daddr == 0) {
|
||||||
|
if (SPIRead(daddr, buffer2, buffer_size)) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if (!memcmp(buffer2, buffer, buffer_size)) {
|
||||||
|
ets_putc('B'); // Note we skipped the bootloader in output
|
||||||
|
skip = true; // And skip erase/write
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!skip) {
|
||||||
|
if (SPIEraseSector(daddr/buffer_size)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
if (SPIWrite(daddr, buffer, buffer_size)) {
|
if (SPIWrite(daddr, buffer, buffer_size)) {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
saddr += buffer_size;
|
saddr += buffer_size;
|
||||||
daddr += buffer_size;
|
daddr += buffer_size;
|
||||||
left -= buffer_size;
|
left -= buffer_size;
|
||||||
@ -123,18 +207,27 @@ int copy_raw(const uint32_t src_addr,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
{
|
||||||
int res = 9;
|
int res = 9;
|
||||||
|
bool clear_cmd = false;
|
||||||
struct eboot_command cmd;
|
struct eboot_command cmd;
|
||||||
|
|
||||||
|
// BSS init commented out for now to save space. If any static variables set
|
||||||
|
// to 0 are used, need to uncomment it or else the BSS will not be cleared and
|
||||||
|
// the static vars will power on with random values.
|
||||||
|
#if 0
|
||||||
|
// Clear BSS ourselves, we don't have handy C runtime
|
||||||
|
extern char _bss_start;
|
||||||
|
extern char _bss_end;
|
||||||
|
ets_bzero(&_bss_start, &_bss_end - &_bss_start);
|
||||||
|
#endif
|
||||||
|
|
||||||
print_version(0);
|
print_version(0);
|
||||||
|
|
||||||
if (eboot_command_read(&cmd) == 0) {
|
if (eboot_command_read(&cmd) == 0) {
|
||||||
// valid command was passed via RTC_MEM
|
// valid command was passed via RTC_MEM
|
||||||
eboot_command_clear();
|
clear_cmd = true;
|
||||||
ets_putc('@');
|
ets_putc('@');
|
||||||
} else {
|
} else {
|
||||||
// no valid command found
|
// no valid command found
|
||||||
@ -144,22 +237,42 @@ void main()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cmd.action == ACTION_COPY_RAW) {
|
if (cmd.action == ACTION_COPY_RAW) {
|
||||||
ets_putc('c'); ets_putc('p'); ets_putc(':');
|
ets_printf("cp:");
|
||||||
|
|
||||||
ets_wdt_disable();
|
ets_wdt_disable();
|
||||||
res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2]);
|
res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2], false);
|
||||||
ets_wdt_enable();
|
ets_wdt_enable();
|
||||||
ets_putc('0'+res); ets_putc('\n');
|
|
||||||
|
ets_printf("%d\n", res);
|
||||||
|
#if 0
|
||||||
|
//devyte: this verify step below (cmp:) only works when the end of copy operation above does not overwrite the
|
||||||
|
//beginning of the image in the empty area, see #7458. Disabling for now.
|
||||||
|
//TODO: replace the below verify with hash type, crc, or similar.
|
||||||
|
// Verify the copy
|
||||||
|
ets_printf("cmp:");
|
||||||
|
if (res == 0) {
|
||||||
|
ets_wdt_disable();
|
||||||
|
res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2], true);
|
||||||
|
ets_wdt_enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ets_printf("%d\n", res);
|
||||||
|
#endif
|
||||||
if (res == 0) {
|
if (res == 0) {
|
||||||
cmd.action = ACTION_LOAD_APP;
|
cmd.action = ACTION_LOAD_APP;
|
||||||
cmd.args[0] = cmd.args[1];
|
cmd.args[0] = cmd.args[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clear_cmd) {
|
||||||
|
eboot_command_clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (cmd.action == ACTION_LOAD_APP) {
|
if (cmd.action == ACTION_LOAD_APP) {
|
||||||
ets_putc('l'); ets_putc('d'); ets_putc('\n');
|
ets_printf("ld\n");
|
||||||
res = load_app_from_flash_raw(cmd.args[0]);
|
res = load_app_from_flash_raw(cmd.args[0]);
|
||||||
//we will get to this only on load fail
|
// We will get to this only on load fail
|
||||||
ets_putc('e'); ets_putc(':'); ets_putc('0'+res); ets_putc('\n');
|
ets_printf("e:%d\n", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
@ -167,4 +280,7 @@ void main()
|
|||||||
}
|
}
|
||||||
|
|
||||||
while(true){}
|
while(true){}
|
||||||
|
|
||||||
|
__builtin_unreachable();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
@ -42,51 +42,13 @@ PROVIDE(_memmap_cacheattr_reset = _memmap_cacheattr_wb_trapnull);
|
|||||||
SECTIONS
|
SECTIONS
|
||||||
{
|
{
|
||||||
|
|
||||||
.dport0.rodata : ALIGN(4)
|
.globals : ALIGN(4)
|
||||||
{
|
{
|
||||||
_dport0_rodata_start = ABSOLUTE(.);
|
*(COMMON) /* Global vars */
|
||||||
*(.dport0.rodata)
|
} >dram0_0_seg :dram0_0_bss_phdr
|
||||||
*(.dport.rodata)
|
|
||||||
_dport0_rodata_end = ABSOLUTE(.);
|
|
||||||
} >dport0_0_seg :dport0_0_phdr
|
|
||||||
|
|
||||||
.dport0.literal : ALIGN(4)
|
|
||||||
{
|
|
||||||
_dport0_literal_start = ABSOLUTE(.);
|
|
||||||
*(.dport0.literal)
|
|
||||||
*(.dport.literal)
|
|
||||||
_dport0_literal_end = ABSOLUTE(.);
|
|
||||||
} >dport0_0_seg :dport0_0_phdr
|
|
||||||
|
|
||||||
.dport0.data : ALIGN(4)
|
|
||||||
{
|
|
||||||
_dport0_data_start = ABSOLUTE(.);
|
|
||||||
*(.dport0.data)
|
|
||||||
*(.dport.data)
|
|
||||||
_dport0_data_end = ABSOLUTE(.);
|
|
||||||
} >dport0_0_seg :dport0_0_phdr
|
|
||||||
|
|
||||||
.data : ALIGN(4)
|
.data : ALIGN(4)
|
||||||
{
|
{
|
||||||
_heap_start = ABSOLUTE(.);
|
|
||||||
/* _stack_sentry = ALIGN(0x8); */
|
|
||||||
} >dram0_0_seg :dram0_0_bss_phdr
|
|
||||||
/* __stack = 0x3ffc8000; */
|
|
||||||
|
|
||||||
.text : ALIGN(4)
|
|
||||||
{
|
|
||||||
_stext = .;
|
|
||||||
_text_start = ABSOLUTE(.);
|
|
||||||
*(.entry.text)
|
|
||||||
*(.init.literal)
|
|
||||||
*(.init)
|
|
||||||
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
|
|
||||||
*(.fini.literal)
|
|
||||||
*(.fini)
|
|
||||||
*(.gnu.version)
|
|
||||||
_text_end = ABSOLUTE(.);
|
|
||||||
_etext = .;
|
|
||||||
. = ALIGN (8);
|
|
||||||
_data_start = ABSOLUTE(.);
|
_data_start = ABSOLUTE(.);
|
||||||
*(.data)
|
*(.data)
|
||||||
*(.data.*)
|
*(.data.*)
|
||||||
@ -100,7 +62,10 @@ SECTIONS
|
|||||||
*(.gnu.linkonce.s2.*)
|
*(.gnu.linkonce.s2.*)
|
||||||
*(.jcr)
|
*(.jcr)
|
||||||
_data_end = ABSOLUTE(.);
|
_data_end = ABSOLUTE(.);
|
||||||
. = ALIGN (8);
|
} >dram0_0_seg :dram0_0_bss_phdr
|
||||||
|
|
||||||
|
.rodata : ALIGN(4)
|
||||||
|
{
|
||||||
_rodata_start = ABSOLUTE(.);
|
_rodata_start = ABSOLUTE(.);
|
||||||
*(.rodata)
|
*(.rodata)
|
||||||
*(.rodata.*)
|
*(.rodata.*)
|
||||||
@ -129,14 +94,11 @@ SECTIONS
|
|||||||
*(.xt_except_desc_end)
|
*(.xt_except_desc_end)
|
||||||
*(.dynamic)
|
*(.dynamic)
|
||||||
*(.gnu.version_d)
|
*(.gnu.version_d)
|
||||||
. = ALIGN(4); /* this table MUST be 4-byte aligned */
|
|
||||||
_bss_table_start = ABSOLUTE(.);
|
|
||||||
LONG(_bss_start)
|
|
||||||
LONG(_bss_end)
|
|
||||||
_bss_table_end = ABSOLUTE(.);
|
|
||||||
_rodata_end = ABSOLUTE(.);
|
_rodata_end = ABSOLUTE(.);
|
||||||
|
} >dram0_0_seg :dram0_0_bss_phdr
|
||||||
|
|
||||||
. = ALIGN (8);
|
.bss : ALIGN(4)
|
||||||
|
{
|
||||||
_bss_start = ABSOLUTE(.);
|
_bss_start = ABSOLUTE(.);
|
||||||
*(.dynsbss)
|
*(.dynsbss)
|
||||||
*(.sbss)
|
*(.sbss)
|
||||||
@ -150,9 +112,24 @@ SECTIONS
|
|||||||
*(.bss)
|
*(.bss)
|
||||||
*(.bss.*)
|
*(.bss.*)
|
||||||
*(.gnu.linkonce.b.*)
|
*(.gnu.linkonce.b.*)
|
||||||
*(COMMON)
|
|
||||||
. = ALIGN (8);
|
|
||||||
_bss_end = ABSOLUTE(.);
|
_bss_end = ABSOLUTE(.);
|
||||||
|
} >dram0_0_seg :dram0_0_bss_phdr
|
||||||
|
|
||||||
|
|
||||||
|
.text : ALIGN(4)
|
||||||
|
{
|
||||||
|
_stext = .;
|
||||||
|
_text_start = ABSOLUTE(.);
|
||||||
|
*(.entry.text)
|
||||||
|
*(.init.literal)
|
||||||
|
*(.init)
|
||||||
|
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
|
||||||
|
*(.fini.literal)
|
||||||
|
*(.fini)
|
||||||
|
*(.gnu.version)
|
||||||
|
_text_end = ABSOLUTE(.);
|
||||||
|
_etext = .;
|
||||||
|
. = ALIGN (4); /* Ensure 32b alignment since this is written to IRAM */
|
||||||
} >iram1_0_seg :iram1_0_phdr
|
} >iram1_0_seg :iram1_0_phdr
|
||||||
|
|
||||||
.lit4 : ALIGN(4)
|
.lit4 : ALIGN(4)
|
||||||
|
@ -37,7 +37,7 @@ int eboot_command_read(struct eboot_command* cmd)
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t crc32 = eboot_command_calculate_crc32(cmd);
|
uint32_t crc32 = eboot_command_calculate_crc32(cmd);
|
||||||
if (cmd->magic & EBOOT_MAGIC_MASK != EBOOT_MAGIC ||
|
if ((cmd->magic & EBOOT_MAGIC_MASK) != EBOOT_MAGIC ||
|
||||||
cmd->crc32 != crc32) {
|
cmd->crc32 != crc32) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
63
bootloaders/eboot/spi_vendors.h
Normal file
63
bootloaders/eboot/spi_vendors.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
spi_vendors.h - Vendor IDs for SPI chips
|
||||||
|
Copyright (c) 2019 Mike Nix. 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 __SPI_VENDORS_H__
|
||||||
|
#define __SPI_VENDORS_H__
|
||||||
|
|
||||||
|
// Vendor IDs taken from Flashrom project
|
||||||
|
// https://review.coreboot.org/cgit/flashrom.git/tree/flashchips.h?h=1.0.x
|
||||||
|
// Moved here from ../../cores/esp8266/Esp.h
|
||||||
|
typedef enum {
|
||||||
|
SPI_FLASH_VENDOR_ALLIANCE = 0x52, /* Alliance Semiconductor */
|
||||||
|
SPI_FLASH_VENDOR_AMD = 0x01, /* AMD */
|
||||||
|
SPI_FLASH_VENDOR_AMIC = 0x37, /* AMIC */
|
||||||
|
SPI_FLASH_VENDOR_ATMEL = 0x1F, /* Atmel (now used by Adesto) */
|
||||||
|
SPI_FLASH_VENDOR_BRIGHT = 0xAD, /* Bright Microelectronics */
|
||||||
|
SPI_FLASH_VENDOR_CATALYST = 0x31, /* Catalyst */
|
||||||
|
SPI_FLASH_VENDOR_EON = 0x1C, /* EON Silicon Devices, missing 0x7F prefix */
|
||||||
|
SPI_FLASH_VENDOR_ESMT = 0x8C, /* Elite Semiconductor Memory Technology (ESMT) / EFST Elite Flash Storage */
|
||||||
|
SPI_FLASH_VENDOR_EXCEL = 0x4A, /* ESI, missing 0x7F prefix */
|
||||||
|
SPI_FLASH_VENDOR_FIDELIX = 0xF8, /* Fidelix */
|
||||||
|
SPI_FLASH_VENDOR_FUJITSU = 0x04, /* Fujitsu */
|
||||||
|
SPI_FLASH_VENDOR_GIGADEVICE = 0xC8, /* GigaDevice */
|
||||||
|
SPI_FLASH_VENDOR_HYUNDAI = 0xAD, /* Hyundai */
|
||||||
|
SPI_FLASH_VENDOR_INTEL = 0x89, /* Intel */
|
||||||
|
SPI_FLASH_VENDOR_ISSI = 0xD5, /* ISSI Integrated Silicon Solutions, see also PMC. */
|
||||||
|
SPI_FLASH_VENDOR_MACRONIX = 0xC2, /* Macronix (MX) */
|
||||||
|
SPI_FLASH_VENDOR_NANTRONICS = 0xD5, /* Nantronics, missing prefix */
|
||||||
|
SPI_FLASH_VENDOR_PMC = 0x9D, /* PMC, missing 0x7F prefix */
|
||||||
|
SPI_FLASH_VENDOR_PUYA = 0x85, /* Puya semiconductor (shanghai) co. ltd */
|
||||||
|
SPI_FLASH_VENDOR_SANYO = 0x62, /* Sanyo */
|
||||||
|
SPI_FLASH_VENDOR_SHARP = 0xB0, /* Sharp */
|
||||||
|
SPI_FLASH_VENDOR_SPANSION = 0x01, /* Spansion, same ID as AMD */
|
||||||
|
SPI_FLASH_VENDOR_SST = 0xBF, /* SST */
|
||||||
|
SPI_FLASH_VENDOR_ST = 0x20, /* ST / SGS/Thomson / Numonyx (later acquired by Micron) */
|
||||||
|
SPI_FLASH_VENDOR_SYNCMOS_MVC = 0x40, /* SyncMOS (SM) and Mosel Vitelic Corporation (MVC) */
|
||||||
|
SPI_FLASH_VENDOR_TENX = 0x5E, /* Tenx Technologies */
|
||||||
|
SPI_FLASH_VENDOR_TI = 0x97, /* Texas Instruments */
|
||||||
|
SPI_FLASH_VENDOR_TI_OLD = 0x01, /* TI chips from last century */
|
||||||
|
SPI_FLASH_VENDOR_WINBOND = 0xDA, /* Winbond */
|
||||||
|
SPI_FLASH_VENDOR_WINBOND_NEX = 0xEF, /* Winbond (ex Nexcom) serial flashes */
|
||||||
|
SPI_FLASH_VENDOR_XMC = 0x20, /* Wuhan Xinxin Semiconductor Manufacturing Corp */
|
||||||
|
|
||||||
|
SPI_FLASH_VENDOR_UNKNOWN = 0xFF
|
||||||
|
} SPI_FLASH_VENDOR_t;
|
||||||
|
|
||||||
|
#endif // __SPI_VENDORS_H__
|
@ -127,6 +127,8 @@ struct netifWrapper
|
|||||||
const char* ifhostname () const { return _netif->hostname?: emptyString.c_str(); }
|
const char* ifhostname () const { return _netif->hostname?: emptyString.c_str(); }
|
||||||
const char* ifmac () const { return (const char*)_netif->hwaddr; }
|
const char* ifmac () const { return (const char*)_netif->hwaddr; }
|
||||||
int ifnumber () const { return _netif->num; }
|
int ifnumber () const { return _netif->num; }
|
||||||
|
bool ifUp () const { return !!(_netif->flags & NETIF_FLAG_UP); }
|
||||||
|
const netif* interface () const { return _netif; }
|
||||||
|
|
||||||
const ip_addr_t* ipFromNetifNum () const
|
const ip_addr_t* ipFromNetifNum () const
|
||||||
{
|
{
|
||||||
@ -166,8 +168,6 @@ public:
|
|||||||
bool operator== (AddressListIterator& o) { return netIf.equal(*o); }
|
bool operator== (AddressListIterator& o) { return netIf.equal(*o); }
|
||||||
bool operator!= (AddressListIterator& o) { return !netIf.equal(*o); }
|
bool operator!= (AddressListIterator& o) { return !netIf.equal(*o); }
|
||||||
|
|
||||||
AddressListIterator& operator= (const AddressListIterator& o) { netIf = o.netIf; return *this; }
|
|
||||||
|
|
||||||
AddressListIterator operator++ (int)
|
AddressListIterator operator++ (int)
|
||||||
{
|
{
|
||||||
AddressListIterator ret = *this;
|
AddressListIterator ret = *this;
|
||||||
|
@ -37,14 +37,13 @@ extern "C" {
|
|||||||
#include "binary.h"
|
#include "binary.h"
|
||||||
#include "esp8266_peri.h"
|
#include "esp8266_peri.h"
|
||||||
#include "twi.h"
|
#include "twi.h"
|
||||||
|
|
||||||
#include "core_esp8266_features.h"
|
#include "core_esp8266_features.h"
|
||||||
#include "core_esp8266_version.h"
|
#include "core_esp8266_version.h"
|
||||||
|
|
||||||
#define HIGH 0x1
|
#define HIGH 0x1
|
||||||
#define LOW 0x0
|
#define LOW 0x0
|
||||||
|
|
||||||
#define PWMRANGE 1023
|
|
||||||
|
|
||||||
//GPIO FUNCTIONS
|
//GPIO FUNCTIONS
|
||||||
#define INPUT 0x00
|
#define INPUT 0x00
|
||||||
#define INPUT_PULLUP 0x02
|
#define INPUT_PULLUP 0x02
|
||||||
@ -127,45 +126,14 @@ void timer0_isr_init(void);
|
|||||||
void timer0_attachInterrupt(timercallback userFunc);
|
void timer0_attachInterrupt(timercallback userFunc);
|
||||||
void timer0_detachInterrupt(void);
|
void timer0_detachInterrupt(void);
|
||||||
|
|
||||||
// undefine stdlib's abs if encountered
|
|
||||||
#ifdef abs
|
|
||||||
#undef abs
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define abs(x) ((x)>0?(x):-(x))
|
|
||||||
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
|
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
|
||||||
#define round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))
|
|
||||||
#define radians(deg) ((deg)*DEG_TO_RAD)
|
#define radians(deg) ((deg)*DEG_TO_RAD)
|
||||||
#define degrees(rad) ((rad)*RAD_TO_DEG)
|
#define degrees(rad) ((rad)*RAD_TO_DEG)
|
||||||
#define sq(x) ((x)*(x))
|
#define sq(x) ((x)*(x))
|
||||||
|
|
||||||
void ets_intr_lock();
|
|
||||||
void ets_intr_unlock();
|
|
||||||
|
|
||||||
#ifndef __STRINGIFY
|
|
||||||
#define __STRINGIFY(a) #a
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// these low level routines provide a replacement for SREG interrupt save that AVR uses
|
|
||||||
// but are esp8266 specific. A normal use pattern is like
|
|
||||||
//
|
|
||||||
//{
|
|
||||||
// uint32_t savedPS = xt_rsil(1); // this routine will allow level 2 and above
|
|
||||||
// // do work here
|
|
||||||
// xt_wsr_ps(savedPS); // restore the state
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
// level (0-15), interrupts of the given level and above will be active
|
|
||||||
// level 15 will disable ALL interrupts,
|
|
||||||
// level 0 will enable ALL interrupts,
|
|
||||||
//
|
|
||||||
#define xt_rsil(level) (__extension__({uint32_t state; __asm__ __volatile__("rsil %0," __STRINGIFY(level) : "=a" (state)); state;}))
|
|
||||||
#define xt_wsr_ps(state) __asm__ __volatile__("wsr %0,ps; isync" :: "a" (state) : "memory")
|
|
||||||
|
|
||||||
#define interrupts() xt_rsil(0)
|
#define interrupts() xt_rsil(0)
|
||||||
#define noInterrupts() xt_rsil(15)
|
#define noInterrupts() xt_rsil(15)
|
||||||
|
|
||||||
|
|
||||||
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
|
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
|
||||||
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
|
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
|
||||||
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
|
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
|
||||||
@ -191,25 +159,23 @@ typedef uint16_t word;
|
|||||||
typedef bool boolean;
|
typedef bool boolean;
|
||||||
typedef uint8_t byte;
|
typedef uint8_t byte;
|
||||||
|
|
||||||
|
void ets_intr_lock();
|
||||||
|
void ets_intr_unlock();
|
||||||
|
|
||||||
void init(void);
|
void init(void);
|
||||||
void initVariant(void);
|
void initVariant(void);
|
||||||
|
|
||||||
int atexit(void (*func)()) __attribute__((weak));
|
|
||||||
|
|
||||||
void pinMode(uint8_t pin, uint8_t mode);
|
void pinMode(uint8_t pin, uint8_t mode);
|
||||||
void digitalWrite(uint8_t pin, uint8_t val);
|
void digitalWrite(uint8_t pin, uint8_t val);
|
||||||
int digitalRead(uint8_t pin);
|
int digitalRead(uint8_t pin);
|
||||||
int analogRead(uint8_t pin);
|
int analogRead(uint8_t pin);
|
||||||
void analogReference(uint8_t mode);
|
void analogReference(uint8_t mode);
|
||||||
void analogWrite(uint8_t pin, int val);
|
void analogWrite(uint8_t pin, int val);
|
||||||
|
void analogWriteMode(uint8_t pin, int val, bool openDrain);
|
||||||
void analogWriteFreq(uint32_t freq);
|
void analogWriteFreq(uint32_t freq);
|
||||||
|
void analogWriteResolution(int res);
|
||||||
void analogWriteRange(uint32_t range);
|
void analogWriteRange(uint32_t range);
|
||||||
|
|
||||||
unsigned long millis(void);
|
|
||||||
unsigned long micros(void);
|
|
||||||
uint64_t micros64(void);
|
|
||||||
void delay(unsigned long);
|
|
||||||
void delayMicroseconds(unsigned int us);
|
|
||||||
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout);
|
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout);
|
||||||
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout);
|
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout);
|
||||||
|
|
||||||
@ -218,12 +184,14 @@ uint8_t shiftIn(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder);
|
|||||||
|
|
||||||
void attachInterrupt(uint8_t pin, void (*)(void), int mode);
|
void attachInterrupt(uint8_t pin, void (*)(void), int mode);
|
||||||
void detachInterrupt(uint8_t pin);
|
void detachInterrupt(uint8_t pin);
|
||||||
|
void attachInterruptArg(uint8_t pin, void (*)(void*), void* arg, int mode);
|
||||||
|
|
||||||
void preinit(void);
|
void preinit(void);
|
||||||
void setup(void);
|
void setup(void);
|
||||||
void loop(void);
|
void loop(void);
|
||||||
|
|
||||||
void yield(void);
|
void yield(void);
|
||||||
|
|
||||||
void optimistic_yield(uint32_t interval_us);
|
void optimistic_yield(uint32_t interval_us);
|
||||||
|
|
||||||
#define _PORT_GPIO16 1
|
#define _PORT_GPIO16 1
|
||||||
@ -243,35 +211,37 @@ void optimistic_yield(uint32_t interval_us);
|
|||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// undefine stdlib's definitions when encountered, provide abs that supports floating point for C code
|
||||||
|
#ifndef __cplusplus
|
||||||
|
#undef abs
|
||||||
|
#define abs(x) ({ __typeof__(x) _x = (x); _x > 0 ? _x : -_x; })
|
||||||
|
#undef round
|
||||||
|
#define round(x) ({ __typeof__(x) _x = (x); _x >= 0 ? (long)(_x + 0.5) : (long)(_x - 0.5); })
|
||||||
|
#endif // ifndef __cplusplus
|
||||||
|
|
||||||
//for compatibility, below 4 lines to be removed in release 3.0.0
|
// from this point onward, we need to configure the c++ environment
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C"
|
|
||||||
#endif
|
|
||||||
const int TIM_DIV265 __attribute__((deprecated, weak)) = TIM_DIV256;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <pgmspace.h>
|
#include <cstdlib>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include "WCharacter.h"
|
|
||||||
#include "WString.h"
|
|
||||||
|
|
||||||
#include "HardwareSerial.h"
|
#include "mmu_iram.h"
|
||||||
#include "Esp.h"
|
|
||||||
#include "Updater.h"
|
|
||||||
#include "debug.h"
|
|
||||||
|
|
||||||
using std::min;
|
using std::min;
|
||||||
using std::max;
|
using std::max;
|
||||||
|
using std::round;
|
||||||
using std::isinf;
|
using std::isinf;
|
||||||
using std::isnan;
|
using std::isnan;
|
||||||
|
|
||||||
#define _min(a,b) ((a)<(b)?(a):(b))
|
// Use float-compatible stl abs() and round(), we don't use Arduino macros to avoid issues with the C++ libraries
|
||||||
#define _max(a,b) ((a)>(b)?(a):(b))
|
using std::abs;
|
||||||
|
using std::round;
|
||||||
|
|
||||||
|
#define _min(a,b) ({ decltype(a) _a = (a); decltype(b) _b = (b); _a < _b? _a : _b; })
|
||||||
|
#define _max(a,b) ({ decltype(a) _a = (a); decltype(b) _b = (b); _a > _b? _a : _b; })
|
||||||
|
|
||||||
uint16_t makeWord(uint16_t w);
|
uint16_t makeWord(uint16_t w);
|
||||||
uint16_t makeWord(byte h, byte l);
|
uint16_t makeWord(byte h, byte l);
|
||||||
@ -294,11 +264,34 @@ long secureRandom(long);
|
|||||||
long secureRandom(long, long);
|
long secureRandom(long, long);
|
||||||
long map(long, long, long, long, long);
|
long map(long, long, long, long, long);
|
||||||
|
|
||||||
extern "C" void configTime(long timezone, int daylightOffset_sec,
|
void setTZ(const char* tz);
|
||||||
const char* server1, const char* server2 = nullptr, const char* server3 = nullptr);
|
|
||||||
|
|
||||||
#endif
|
void configTime(int timezone, int daylightOffset_sec, const char* server1,
|
||||||
|
const char* server2 = nullptr, const char* server3 = nullptr);
|
||||||
|
|
||||||
|
void configTime(const char* tz, const char* server1,
|
||||||
|
const char* server2 = nullptr, const char* server3 = nullptr);
|
||||||
|
|
||||||
|
// esp32 api compatibility
|
||||||
|
inline void configTzTime(const char* tz, const char* server1,
|
||||||
|
const char* server2 = nullptr, const char* server3 = nullptr)
|
||||||
|
{
|
||||||
|
configTime(tz, server1, server2, server3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything we expect to be implicitly loaded for the sketch
|
||||||
|
#include <pgmspace.h>
|
||||||
|
|
||||||
|
#include "WCharacter.h"
|
||||||
|
#include "WString.h"
|
||||||
|
|
||||||
|
#include "HardwareSerial.h"
|
||||||
|
#include "Esp.h"
|
||||||
|
#include "Updater.h"
|
||||||
|
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
#include "pins_arduino.h"
|
#include "pins_arduino.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
85
cores/esp8266/CallBackList.h
Normal file
85
cores/esp8266/CallBackList.h
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#ifndef __CALLBACKLIST_H__
|
||||||
|
#define __CALLBACKLIST_H__
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
CallBackList, An implemention for handling callback execution
|
||||||
|
|
||||||
|
Copyright (c) 2019 Herman Reintke. 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 <memory>
|
||||||
|
#include <list>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace experimental
|
||||||
|
{
|
||||||
|
namespace CBListImplentation
|
||||||
|
{
|
||||||
|
|
||||||
|
template<class cbFunctionT>
|
||||||
|
class CallBackList
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CallBackList (){};
|
||||||
|
|
||||||
|
struct CallBackInfo
|
||||||
|
{
|
||||||
|
CallBackInfo(cbFunctionT f) : cbFunction(f, true){};
|
||||||
|
CallBackInfo(cbFunctionT f, bool ar) : cbFunction(f), _allowRemove(ar) {};
|
||||||
|
cbFunctionT cbFunction;
|
||||||
|
bool _allowRemove = true;
|
||||||
|
bool allowRemove()
|
||||||
|
{
|
||||||
|
return _allowRemove;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using CallBackHandler = std::shared_ptr<CallBackInfo> ;
|
||||||
|
std::list<CallBackHandler> callBackEventList;
|
||||||
|
|
||||||
|
CallBackHandler add(cbFunctionT af, bool ad = true) {
|
||||||
|
CallBackHandler handler = std::make_shared<CallBackInfo>(CallBackInfo(af,ad));
|
||||||
|
callBackEventList.emplace_back(handler);
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(CallBackHandler& dh) {
|
||||||
|
callBackEventList.remove(dh);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
int execute(Args... params) {
|
||||||
|
for(auto it = std::begin(callBackEventList); it != std::end(callBackEventList); ) {
|
||||||
|
CallBackHandler &handler = *it;
|
||||||
|
if (handler->allowRemove() && handler.unique()) {
|
||||||
|
it = callBackEventList.erase(it);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
handler->cbFunction(params...);
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return callBackEventList.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} //CBListImplementation
|
||||||
|
}//experimental
|
||||||
|
|
||||||
|
#endif // __CALLBACKLIST_H__
|
@ -28,13 +28,13 @@ class Client: public Stream {
|
|||||||
public:
|
public:
|
||||||
virtual int connect(IPAddress ip, uint16_t port) = 0;
|
virtual int connect(IPAddress ip, uint16_t port) = 0;
|
||||||
virtual int connect(const char *host, uint16_t port) = 0;
|
virtual int connect(const char *host, uint16_t port) = 0;
|
||||||
virtual size_t write(uint8_t) =0;
|
virtual size_t write(uint8_t) override = 0;
|
||||||
virtual size_t write(const uint8_t *buf, size_t size) =0;
|
virtual size_t write(const uint8_t *buf, size_t size) override = 0;
|
||||||
virtual int available() = 0;
|
virtual int available() override = 0;
|
||||||
virtual int read() = 0;
|
virtual int read() override = 0;
|
||||||
virtual int read(uint8_t *buf, size_t size) = 0;
|
virtual int read(uint8_t *buf, size_t size) override = 0;
|
||||||
virtual int peek() = 0;
|
virtual int peek() override = 0;
|
||||||
virtual void flush() = 0;
|
virtual void flush() override = 0;
|
||||||
virtual void stop() = 0;
|
virtual void stop() = 0;
|
||||||
virtual uint8_t connected() = 0;
|
virtual uint8_t connected() = 0;
|
||||||
virtual operator bool() = 0;
|
virtual operator bool() = 0;
|
||||||
@ -42,11 +42,9 @@ class Client: public Stream {
|
|||||||
uint8_t* rawIPAddress(IPAddress& addr) {
|
uint8_t* rawIPAddress(IPAddress& addr) {
|
||||||
return addr.raw_address();
|
return addr.raw_address();
|
||||||
}
|
}
|
||||||
#if LWIP_VERSION_MAJOR != 1
|
|
||||||
const uint8_t* rawIPAddress(const IPAddress& addr) {
|
const uint8_t* rawIPAddress(const IPAddress& addr) {
|
||||||
return addr.raw_address();
|
return addr.raw_address();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
553
cores/esp8266/Crypto.cpp
Normal file
553
cores/esp8266/Crypto.cpp
Normal file
@ -0,0 +1,553 @@
|
|||||||
|
/*
|
||||||
|
BearSSL Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||||
|
Rest of this file Copyright (C) 2019 Anders Löfgren
|
||||||
|
|
||||||
|
License (MIT license):
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Crypto.h"
|
||||||
|
#include <TypeConversion.h>
|
||||||
|
#include <bearssl/bearssl.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
namespace TypeCast = experimental::TypeConversion;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
size_t _ctMinDataLength = 0;
|
||||||
|
size_t _ctMaxDataLength = 1024;
|
||||||
|
|
||||||
|
uint8_t *defaultNonceGenerator(uint8_t *nonceArray, const size_t nonceLength)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
The ESP32 Technical Reference Manual v4.1 chapter 24 has the following to say about random number generation (no information found for ESP8266):
|
||||||
|
|
||||||
|
"When used correctly, every 32-bit value the system reads from the RNG_DATA_REG register of the random number generator is a true random number.
|
||||||
|
These true random numbers are generated based on the noise in the Wi-Fi/BT RF system.
|
||||||
|
When Wi-Fi and BT are disabled, the random number generator will give out pseudo-random numbers.
|
||||||
|
|
||||||
|
When Wi-Fi or BT is enabled, the random number generator is fed two bits of entropy every APB clock cycle (normally 80 MHz).
|
||||||
|
Thus, for the maximum amount of entropy, it is advisable to read the random register at a maximum rate of 5 MHz.
|
||||||
|
A data sample of 2 GB, read from the random number generator with Wi-Fi enabled and the random register read at 5 MHz,
|
||||||
|
has been tested using the Dieharder Random Number Testsuite (version 3.31.1).
|
||||||
|
The sample passed all tests."
|
||||||
|
|
||||||
|
Since ESP32 is the sequal to ESP8266 it is unlikely that the ESP8266 is able to generate random numbers more quickly than 5 MHz when run at a 80 MHz frequency.
|
||||||
|
A maximum random number frequency of 0.5 MHz is used here to leave some margin for possibly inferior components in the ESP8266.
|
||||||
|
It should be noted that the ESP8266 has no Bluetooth functionality, so turning the WiFi off is likely to cause RANDOM_REG32 to use pseudo-random numbers.
|
||||||
|
|
||||||
|
It is possible that yield() must be called on the ESP8266 to properly feed the hardware random number generator new bits, since there is only one processor core available.
|
||||||
|
However, no feeding requirements are mentioned in the ESP32 documentation, and using yield() could possibly cause extended delays during nonce generation.
|
||||||
|
Thus only delayMicroseconds() is used below.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return ESP.random(nonceArray, nonceLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
experimental::crypto::nonceGeneratorType _nonceGenerator = defaultNonceGenerator;
|
||||||
|
|
||||||
|
void *createBearsslHmac(const br_hash_class *hashType, const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
// Comments mainly from https://www.bearssl.org/apidoc/bearssl__hmac_8h.html
|
||||||
|
|
||||||
|
// HMAC is initialized with a key and an underlying hash function; it then fills a "key context". That context contains the processed key.
|
||||||
|
// With the key context, a HMAC context can be initialized to process the input bytes and obtain the MAC output. The key context is not modified during that process, and can be reused.
|
||||||
|
|
||||||
|
br_hmac_key_context keyContext; // Holds general HMAC info
|
||||||
|
br_hmac_context hmacContext; // Holds general HMAC info + specific info for the current operation
|
||||||
|
|
||||||
|
// HMAC key context initialisation.
|
||||||
|
// Initialise the key context with the provided hash key, using the hash function identified by hashType. This supports arbitrary key lengths.
|
||||||
|
br_hmac_key_init(&keyContext, hashType, hashKey, hashKeyLength);
|
||||||
|
|
||||||
|
// Initialise a HMAC context with a key context. The key context is unmodified.
|
||||||
|
// Relevant data from the key context is immediately copied; the key context can thus be independently reused, modified or released without impacting this HMAC computation.
|
||||||
|
// An explicit output length can be specified; the actual output length will be the minimum of that value and the natural HMAC output length.
|
||||||
|
// If outputLength is 0, then the natural HMAC output length is selected. The "natural output length" is the output length of the underlying hash function.
|
||||||
|
br_hmac_init(&hmacContext, &keyContext, outputLength);
|
||||||
|
|
||||||
|
// Provide the HMAC context with the data to create a HMAC from.
|
||||||
|
// The provided dataLength bytes are injected as extra input in the HMAC computation incarnated by the hmacContext.
|
||||||
|
// It is acceptable that dataLength is zero, in which case data is ignored (and may be NULL) and this function does nothing.
|
||||||
|
br_hmac_update(&hmacContext, data, dataLength);
|
||||||
|
|
||||||
|
// Compute the HMAC output.
|
||||||
|
// The destination buffer MUST be large enough to accommodate the result; its length is at most the "natural length" of HMAC (i.e. the output length of the underlying hash function).
|
||||||
|
// The context is NOT modified; further bytes may be processed. Thus, "partial HMAC" values can be efficiently obtained.
|
||||||
|
// Optionally the constant-time version br_hmac_outCT() can be used. More info here: https://www.bearssl.org/constanttime.html .
|
||||||
|
br_hmac_out(&hmacContext, resultArray); // returns size_t outputLength
|
||||||
|
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
String createBearsslHmac(const br_hash_class *hashType, const uint8_t hashTypeNaturalLength, const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
(void) hashTypeNaturalLength;
|
||||||
|
assert(1 <= hmacLength && hmacLength <= hashTypeNaturalLength);
|
||||||
|
|
||||||
|
uint8_t hmac[hmacLength];
|
||||||
|
createBearsslHmac(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, hmac, hmacLength);
|
||||||
|
return TypeCast::uint8ArrayToHexString(hmac, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *createBearsslHmacCT(const br_hash_class *hashType, const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
assert(_ctMinDataLength <= dataLength && dataLength <= _ctMaxDataLength);
|
||||||
|
|
||||||
|
// Comments mainly from https://www.bearssl.org/apidoc/bearssl__hmac_8h.html
|
||||||
|
|
||||||
|
// HMAC is initialized with a key and an underlying hash function; it then fills a "key context". That context contains the processed key.
|
||||||
|
// With the key context, a HMAC context can be initialized to process the input bytes and obtain the MAC output. The key context is not modified during that process, and can be reused.
|
||||||
|
|
||||||
|
br_hmac_key_context keyContext; // Holds general HMAC info
|
||||||
|
br_hmac_context hmacContext; // Holds general HMAC info + specific info for the current operation
|
||||||
|
|
||||||
|
// HMAC key context initialisation.
|
||||||
|
// Initialise the key context with the provided hash key, using the hash function identified by hashType. This supports arbitrary key lengths.
|
||||||
|
br_hmac_key_init(&keyContext, hashType, hashKey, hashKeyLength);
|
||||||
|
|
||||||
|
// Initialise a HMAC context with a key context. The key context is unmodified.
|
||||||
|
// Relevant data from the key context is immediately copied; the key context can thus be independently reused, modified or released without impacting this HMAC computation.
|
||||||
|
// An explicit output length can be specified; the actual output length will be the minimum of that value and the natural HMAC output length.
|
||||||
|
// If outputLength is 0, then the natural HMAC output length is selected. The "natural output length" is the output length of the underlying hash function.
|
||||||
|
br_hmac_init(&hmacContext, &keyContext, outputLength);
|
||||||
|
|
||||||
|
// Provide the HMAC context with the data to create a HMAC from.
|
||||||
|
// The provided dataLength bytes are injected as extra input in the HMAC computation incarnated by the hmacContext.
|
||||||
|
// It is acceptable that dataLength is zero, in which case data is ignored (and may be NULL) and this function does nothing.
|
||||||
|
// No need for br_hmac_update when using constant-time version it seems. If it is used, the data provided to br_hmac_outCT will just be appended.
|
||||||
|
// br_hmac_update(&hmacContext, data, dataLength);
|
||||||
|
|
||||||
|
// Compute the HMAC output. Assumes message is minimum _ctMinDataLength bytes and maximum _ctMaxDataLength bytes.
|
||||||
|
// As long as this is true, the correct HMAC output is calculated in constant-time. More constant-time info here: https://www.bearssl.org/constanttime.html
|
||||||
|
// Some extra input bytes are processed, then the output is computed.
|
||||||
|
// The extra input consists in the dataLength bytes pointed to by data. The dataLength parameter must lie between _ctMinDataLength and _ctMaxDataLength (inclusive);
|
||||||
|
// _ctMaxDataLength bytes are actually read from data (indicating each data byte can be read multiple times, if dataLength < _ctMaxDataLength).
|
||||||
|
// Computing time (and memory access pattern) will not depend upon the data byte contents or the value of dataLength.
|
||||||
|
// The output is written in the resultArray buffer, that MUST be large enough to receive it.
|
||||||
|
// The difference _ctMaxDataLength - _ctMinDataLength MUST be less than 2^30 (i.e. about one gigabyte).
|
||||||
|
// This function computes the output properly only if the underlying hash function uses MD padding (i.e. MD5, SHA-1, SHA-224, SHA-256, SHA-384 or SHA-512).
|
||||||
|
// The provided context is NOT modified.
|
||||||
|
br_hmac_outCT(&hmacContext, data, dataLength, _ctMinDataLength, _ctMaxDataLength, resultArray); // returns size_t outputLength
|
||||||
|
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
String createBearsslHmacCT(const br_hash_class *hashType, const uint8_t hashTypeNaturalLength, const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
(void) hashTypeNaturalLength;
|
||||||
|
assert(1 <= hmacLength && hmacLength <= hashTypeNaturalLength);
|
||||||
|
|
||||||
|
uint8_t hmac[hmacLength];
|
||||||
|
createBearsslHmacCT(hashType, message.c_str(), message.length(), hashKey, hashKeyLength, hmac, hmacLength);
|
||||||
|
return TypeCast::uint8ArrayToHexString(hmac, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helper function to avoid deprecated warnings.
|
||||||
|
void *md5HashHelper(const void *data, const size_t dataLength, void *resultArray)
|
||||||
|
{
|
||||||
|
br_md5_context context;
|
||||||
|
br_md5_init(&context);
|
||||||
|
br_md5_update(&context, data, dataLength);
|
||||||
|
br_md5_out(&context, resultArray);
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to avoid deprecated warnings.
|
||||||
|
void *sha1HashHelper(const void *data, const size_t dataLength, void *resultArray)
|
||||||
|
{
|
||||||
|
br_sha1_context context;
|
||||||
|
br_sha1_init(&context);
|
||||||
|
br_sha1_update(&context, data, dataLength);
|
||||||
|
br_sha1_out(&context, resultArray);
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace experimental
|
||||||
|
{
|
||||||
|
namespace crypto
|
||||||
|
{
|
||||||
|
void setCtMinDataLength(const size_t ctMinDataLength)
|
||||||
|
{
|
||||||
|
assert(getCtMaxDataLength() - ctMinDataLength <= CT_MAX_DIFF);
|
||||||
|
_ctMinDataLength = ctMinDataLength;
|
||||||
|
}
|
||||||
|
size_t getCtMinDataLength()
|
||||||
|
{
|
||||||
|
return _ctMinDataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCtMaxDataLength(const size_t ctMaxDataLength)
|
||||||
|
{
|
||||||
|
assert(ctMaxDataLength - getCtMinDataLength() <= CT_MAX_DIFF);
|
||||||
|
_ctMaxDataLength = ctMaxDataLength;
|
||||||
|
}
|
||||||
|
size_t getCtMaxDataLength()
|
||||||
|
{
|
||||||
|
return _ctMaxDataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setNonceGenerator(nonceGeneratorType nonceGenerator)
|
||||||
|
{
|
||||||
|
_nonceGenerator = nonceGenerator;
|
||||||
|
}
|
||||||
|
nonceGeneratorType getNonceGenerator()
|
||||||
|
{
|
||||||
|
return _nonceGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #################### MD5 ####################
|
||||||
|
|
||||||
|
// resultArray must have size MD5::NATURAL_LENGTH or greater
|
||||||
|
void *MD5::hash(const void *data, const size_t dataLength, void *resultArray)
|
||||||
|
{
|
||||||
|
return md5HashHelper(data, dataLength, resultArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
String MD5::hash(const String &message)
|
||||||
|
{
|
||||||
|
uint8_t hashArray[NATURAL_LENGTH];
|
||||||
|
md5HashHelper(message.c_str(), message.length(), hashArray);
|
||||||
|
return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *MD5::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_md5_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String MD5::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_md5_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *MD5::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_md5_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String MD5::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_md5_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #################### SHA-1 ####################
|
||||||
|
|
||||||
|
// resultArray must have size SHA1::NATURAL_LENGTH or greater
|
||||||
|
void *SHA1::hash(const void *data, const size_t dataLength, void *resultArray)
|
||||||
|
{
|
||||||
|
return sha1HashHelper(data, dataLength, resultArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA1::hash(const String &message)
|
||||||
|
{
|
||||||
|
uint8_t hashArray[NATURAL_LENGTH];
|
||||||
|
sha1HashHelper(message.c_str(), message.length(), hashArray);
|
||||||
|
return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *SHA1::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_sha1_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA1::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_sha1_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *SHA1::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_sha1_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA1::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_sha1_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #################### SHA-224 ####################
|
||||||
|
|
||||||
|
// resultArray must have size SHA224::NATURAL_LENGTH or greater
|
||||||
|
void *SHA224::hash(const void *data, const size_t dataLength, void *resultArray)
|
||||||
|
{
|
||||||
|
br_sha224_context context;
|
||||||
|
br_sha224_init(&context);
|
||||||
|
br_sha224_update(&context, data, dataLength);
|
||||||
|
br_sha224_out(&context, resultArray);
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA224::hash(const String &message)
|
||||||
|
{
|
||||||
|
uint8_t hashArray[NATURAL_LENGTH];
|
||||||
|
hash(message.c_str(), message.length(), hashArray);
|
||||||
|
return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *SHA224::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_sha224_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA224::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_sha224_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *SHA224::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_sha224_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA224::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_sha224_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #################### SHA-256 ####################
|
||||||
|
|
||||||
|
// resultArray must have size SHA256::NATURAL_LENGTH or greater
|
||||||
|
void *SHA256::hash(const void *data, const size_t dataLength, void *resultArray)
|
||||||
|
{
|
||||||
|
br_sha256_context context;
|
||||||
|
br_sha256_init(&context);
|
||||||
|
br_sha256_update(&context, data, dataLength);
|
||||||
|
br_sha256_out(&context, resultArray);
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA256::hash(const String &message)
|
||||||
|
{
|
||||||
|
uint8_t hashArray[NATURAL_LENGTH];
|
||||||
|
hash(message.c_str(), message.length(), hashArray);
|
||||||
|
return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *SHA256::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_sha256_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA256::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_sha256_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *SHA256::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_sha256_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA256::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_sha256_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #################### SHA-384 ####################
|
||||||
|
|
||||||
|
// resultArray must have size SHA384::NATURAL_LENGTH or greater
|
||||||
|
void *SHA384::hash(const void *data, const size_t dataLength, void *resultArray)
|
||||||
|
{
|
||||||
|
br_sha384_context context;
|
||||||
|
br_sha384_init(&context);
|
||||||
|
br_sha384_update(&context, data, dataLength);
|
||||||
|
br_sha384_out(&context, resultArray);
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA384::hash(const String &message)
|
||||||
|
{
|
||||||
|
uint8_t hashArray[NATURAL_LENGTH];
|
||||||
|
hash(message.c_str(), message.length(), hashArray);
|
||||||
|
return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *SHA384::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_sha384_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA384::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_sha384_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *SHA384::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_sha384_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA384::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_sha384_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #################### SHA-512 ####################
|
||||||
|
|
||||||
|
// resultArray must have size SHA512::NATURAL_LENGTH or greater
|
||||||
|
void *SHA512::hash(const void *data, const size_t dataLength, void *resultArray)
|
||||||
|
{
|
||||||
|
br_sha512_context context;
|
||||||
|
br_sha512_init(&context);
|
||||||
|
br_sha512_update(&context, data, dataLength);
|
||||||
|
br_sha512_out(&context, resultArray);
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA512::hash(const String &message)
|
||||||
|
{
|
||||||
|
uint8_t hashArray[NATURAL_LENGTH];
|
||||||
|
hash(message.c_str(), message.length(), hashArray);
|
||||||
|
return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *SHA512::hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_sha512_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA512::hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmac(&br_sha512_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *SHA512::hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_sha512_vtable, data, dataLength, hashKey, hashKeyLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
String SHA512::hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength)
|
||||||
|
{
|
||||||
|
return createBearsslHmacCT(&br_sha512_vtable, NATURAL_LENGTH, message, hashKey, hashKeyLength, hmacLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #################### MD5+SHA-1 ####################
|
||||||
|
|
||||||
|
// resultArray must have size MD5SHA1::NATURAL_LENGTH or greater
|
||||||
|
void *MD5SHA1::hash(const void *data, const size_t dataLength, void *resultArray)
|
||||||
|
{
|
||||||
|
br_md5sha1_context context;
|
||||||
|
br_md5sha1_init(&context);
|
||||||
|
br_md5sha1_update(&context, data, dataLength);
|
||||||
|
br_md5sha1_out(&context, resultArray);
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
String MD5SHA1::hash(const String &message)
|
||||||
|
{
|
||||||
|
uint8_t hashArray[NATURAL_LENGTH];
|
||||||
|
hash(message.c_str(), message.length(), hashArray);
|
||||||
|
return TypeCast::uint8ArrayToHexString(hashArray, NATURAL_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #################### HKDF ####################
|
||||||
|
|
||||||
|
HKDF::HKDF(const void *keyMaterial, const size_t keyMaterialLength, const void *salt, const size_t saltLength)
|
||||||
|
{
|
||||||
|
init(keyMaterial, keyMaterialLength, salt, saltLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HKDF::init(const void *keyMaterial, const size_t keyMaterialLength, const void *salt, const size_t saltLength)
|
||||||
|
{
|
||||||
|
// Comments mainly from https://www.bearssl.org/apidoc/bearssl__kdf_8h.html
|
||||||
|
|
||||||
|
// Initialize an HKDF context, with a hash function, and the salt. This starts the HKDF-Extract process.
|
||||||
|
br_hkdf_init(&hkdfContext, &br_sha256_vtable, salt, saltLength);
|
||||||
|
|
||||||
|
// Inject more input bytes. This function may be called repeatedly if the input data is provided by chunks, after br_hkdf_init() but before br_hkdf_flip().
|
||||||
|
br_hkdf_inject(&hkdfContext, keyMaterial, keyMaterialLength);
|
||||||
|
|
||||||
|
// End the HKDF-Extract process, and start the HKDF-Expand process.
|
||||||
|
br_hkdf_flip(&hkdfContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HKDF::produce(void *resultArray, const size_t outputLength, const void *info, const size_t infoLength)
|
||||||
|
{
|
||||||
|
// Comments mainly from https://www.bearssl.org/apidoc/bearssl__kdf_8h.html
|
||||||
|
|
||||||
|
// HKDF output production (HKDF-Expand).
|
||||||
|
// Produces more output bytes from the current state. This function may be called several times, but only after br_hkdf_flip().
|
||||||
|
// Returned value is the number of actually produced bytes. The total output length is limited to 255 times the output length of the underlying hash function.
|
||||||
|
return br_hkdf_produce(&hkdfContext, info, infoLength, resultArray, outputLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #################### Authenticated Encryption with Associated Data (AEAD) ####################
|
||||||
|
|
||||||
|
|
||||||
|
// #################### ChaCha20+Poly1305 AEAD ####################
|
||||||
|
|
||||||
|
void chacha20Poly1305Kernel(const int encrypt, void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength,
|
||||||
|
const void *nonce, void *tag, const void *aad, const size_t aadLength)
|
||||||
|
{
|
||||||
|
if (keySalt == nullptr)
|
||||||
|
{
|
||||||
|
br_poly1305_ctmul32_run(key, nonce, data, dataLength, aad, aadLength, tag, br_chacha20_ct_run, encrypt);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HKDF hkdfInstance(key, ENCRYPTION_KEY_LENGTH, keySalt, keySaltLength);
|
||||||
|
uint8_t derivedEncryptionKey[ENCRYPTION_KEY_LENGTH] {0};
|
||||||
|
hkdfInstance.produce(derivedEncryptionKey, ENCRYPTION_KEY_LENGTH);
|
||||||
|
br_poly1305_ctmul32_run(derivedEncryptionKey, nonce, data, dataLength, aad, aadLength, tag, br_chacha20_ct_run, encrypt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChaCha20Poly1305::encrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength,
|
||||||
|
void *resultingNonce, void *resultingTag, const void *aad, const size_t aadLength)
|
||||||
|
{
|
||||||
|
uint8_t *nonce = (uint8_t *)resultingNonce;
|
||||||
|
getNonceGenerator()(nonce, 12);
|
||||||
|
|
||||||
|
chacha20Poly1305Kernel(1, data, dataLength, key, keySalt, keySaltLength, nonce, resultingTag, aad, aadLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ChaCha20Poly1305::decrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength,
|
||||||
|
const void *encryptionNonce, const void *encryptionTag, const void *aad, const size_t aadLength)
|
||||||
|
{
|
||||||
|
const uint8_t *oldTag = (const uint8_t *)encryptionTag;
|
||||||
|
uint8_t newTag[16] {0};
|
||||||
|
|
||||||
|
chacha20Poly1305Kernel(0, data, dataLength, key, keySalt, keySaltLength, encryptionNonce, newTag, aad, aadLength);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < sizeof newTag; ++i)
|
||||||
|
{
|
||||||
|
if (newTag[i] != oldTag[i])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
845
cores/esp8266/Crypto.h
Normal file
845
cores/esp8266/Crypto.h
Normal file
@ -0,0 +1,845 @@
|
|||||||
|
/*
|
||||||
|
BearSSL Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
|
||||||
|
Rest of this file Copyright (C) 2019 Anders Löfgren
|
||||||
|
|
||||||
|
License (MIT license):
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __ESP8266_ARDUINO_CRYPTO_H__
|
||||||
|
#define __ESP8266_ARDUINO_CRYPTO_H__
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <bearssl/bearssl_kdf.h>
|
||||||
|
|
||||||
|
namespace experimental
|
||||||
|
{
|
||||||
|
namespace crypto
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
Regarding constant-time (CT) HMAC:
|
||||||
|
|
||||||
|
Basically, constant-time algorithms makes it harder for attackers to learn things about your system based on the execution time of code.
|
||||||
|
Good intro here: https://www.bearssl.org/constanttime.html
|
||||||
|
|
||||||
|
It should be noted that every HMAC is already partially constant-time. Quoting the link above:
|
||||||
|
"Hash functions implemented by BearSSL (MD5, SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512) consist in bitwise logical operations and additions on 32-bit or 64-bit words,
|
||||||
|
naturally yielding constant-time operations. HMAC is naturally as constant-time as the underlying hash function. The size of the MACed data, and the size of the key,
|
||||||
|
may leak, though; only the contents are protected."
|
||||||
|
|
||||||
|
For messages much smaller than getCtMaxDataLength(), constant-time processing takes substantially longer time to complete than a normal HMAC,
|
||||||
|
determined by the size of (getCtMaxDataLength() - getCtMinDataLength()).
|
||||||
|
Constant-time processing also sets limits on the data length.
|
||||||
|
|
||||||
|
Making the fixed data length limits variable will generally defeat the purpose of using constant-time.
|
||||||
|
Using data that exceeds the fixed data length limits will create the wrong HMAC.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
The nonce generator should take an uint8_t array with a given size in bytes and fill it with the nonce.
|
||||||
|
The uint8_t array should then be returned by the nonce generator.
|
||||||
|
*/
|
||||||
|
using nonceGeneratorType = std::function<uint8_t *(uint8_t *, const size_t)>;
|
||||||
|
|
||||||
|
constexpr uint8_t ENCRYPTION_KEY_LENGTH = 32;
|
||||||
|
|
||||||
|
constexpr uint32_t CT_MAX_DIFF = 1073741823; // 2^30 - 1
|
||||||
|
|
||||||
|
/**
|
||||||
|
This function allows for fine-tuning of the specifications for the constant time calculations.
|
||||||
|
It should not be changed once a constant time function has been used at least once.
|
||||||
|
Otherwise the constant time will not be constant for the used functions.
|
||||||
|
|
||||||
|
The difference getCtMaxDataLength() - getCtMinDataLength() MUST be less than 2^30 (i.e. about one gigabyte).
|
||||||
|
*/
|
||||||
|
void setCtMinDataLength(const size_t ctMinDataLength);
|
||||||
|
/**
|
||||||
|
0 by default.
|
||||||
|
*/
|
||||||
|
size_t getCtMinDataLength();
|
||||||
|
|
||||||
|
/**
|
||||||
|
This function allows for fine-tuning of the specifications for the constant time calculations.
|
||||||
|
It should not be changed once a constant time function has been used at least once.
|
||||||
|
Otherwise the constant time will not be constant for the used functions.
|
||||||
|
|
||||||
|
The difference getCtMaxDataLength() - getCtMinDataLength() MUST be less than 2^30 (i.e. about one gigabyte).
|
||||||
|
*/
|
||||||
|
void setCtMaxDataLength(const size_t ctMaxDataLength);
|
||||||
|
/**
|
||||||
|
1024 by default.
|
||||||
|
*/
|
||||||
|
size_t getCtMaxDataLength();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set the nonce generator used by the Crypto functions.
|
||||||
|
|
||||||
|
@param nonceGenerator The nonce generator to use.
|
||||||
|
*/
|
||||||
|
void setNonceGenerator(nonceGeneratorType nonceGenerator);
|
||||||
|
nonceGeneratorType getNonceGenerator();
|
||||||
|
|
||||||
|
|
||||||
|
// #################### MD5 ####################
|
||||||
|
|
||||||
|
struct MD5
|
||||||
|
{
|
||||||
|
static constexpr uint8_t NATURAL_LENGTH = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
WARNING! The MD5 hash is broken in terms of attacker resistance.
|
||||||
|
Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise.
|
||||||
|
|
||||||
|
Create a MD5 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the hash.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hash(const void *data, const size_t dataLength, void *resultArray) __attribute__((deprecated));
|
||||||
|
|
||||||
|
/**
|
||||||
|
WARNING! The MD5 hash is broken in terms of attacker resistance.
|
||||||
|
Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise.
|
||||||
|
|
||||||
|
Create a MD5 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the hash.
|
||||||
|
|
||||||
|
@return A String with the generated hash in HEX format.
|
||||||
|
*/
|
||||||
|
static String hash(const String &message) __attribute__((deprecated));
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a MD5 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a MD5 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a MD5 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a MD5 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// #################### SHA-1 ####################
|
||||||
|
|
||||||
|
struct SHA1
|
||||||
|
{
|
||||||
|
static constexpr uint8_t NATURAL_LENGTH = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
WARNING! The SHA-1 hash is broken in terms of attacker resistance.
|
||||||
|
Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise.
|
||||||
|
|
||||||
|
Create a SHA1 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the hash.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hash(const void *data, const size_t dataLength, void *resultArray) __attribute__((deprecated));
|
||||||
|
|
||||||
|
/**
|
||||||
|
WARNING! The SHA-1 hash is broken in terms of attacker resistance.
|
||||||
|
Only use it in those cases where attacker resistance is not important. Prefer SHA-256 or higher otherwise.
|
||||||
|
|
||||||
|
Create a SHA1 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the hash.
|
||||||
|
|
||||||
|
@return A String with the generated hash in HEX format.
|
||||||
|
*/
|
||||||
|
static String hash(const String &message) __attribute__((deprecated));
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA1 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA1 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA1 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA1 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// #################### SHA-224 ####################
|
||||||
|
|
||||||
|
struct SHA224
|
||||||
|
{
|
||||||
|
static constexpr uint8_t NATURAL_LENGTH = 28;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA224 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the hash.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hash(const void *data, const size_t dataLength, void *resultArray);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA224 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the hash.
|
||||||
|
|
||||||
|
@return A String with the generated hash in HEX format.
|
||||||
|
*/
|
||||||
|
static String hash(const String &message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA224 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA224 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA224 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA224 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// #################### SHA-256 ####################
|
||||||
|
|
||||||
|
struct SHA256
|
||||||
|
{
|
||||||
|
static constexpr uint8_t NATURAL_LENGTH = 32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA256 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the hash.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hash(const void *data, const size_t dataLength, void *resultArray);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA256 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the hash.
|
||||||
|
|
||||||
|
@return A String with the generated hash in HEX format.
|
||||||
|
*/
|
||||||
|
static String hash(const String &message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA256 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA256 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA256 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// #################### SHA-384 ####################
|
||||||
|
|
||||||
|
struct SHA384
|
||||||
|
{
|
||||||
|
static constexpr uint8_t NATURAL_LENGTH = 48;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA384 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the hash.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hash(const void *data, const size_t dataLength, void *resultArray);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA384 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the hash.
|
||||||
|
|
||||||
|
@return A String with the generated hash in HEX format.
|
||||||
|
*/
|
||||||
|
static String hash(const String &message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA384 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA384 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA384 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA384 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// #################### SHA-512 ####################
|
||||||
|
|
||||||
|
struct SHA512
|
||||||
|
{
|
||||||
|
static constexpr uint8_t NATURAL_LENGTH = 64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA512 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the hash.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hash(const void *data, const size_t dataLength, void *resultArray);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA512 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the hash.
|
||||||
|
|
||||||
|
@return A String with the generated hash in HEX format.
|
||||||
|
*/
|
||||||
|
static String hash(const String &message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA512 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmac(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA512 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC.
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmac(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA512 HMAC from the data, using the provided hashKey. The result will be up to outputLength bytes long and stored in resultArray.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param data The data array from which to create the HMAC.
|
||||||
|
@param dataLength The length of the data array in bytes. Valid values are in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting HMAC.
|
||||||
|
@param outputLength The desired length of the generated HMAC, in bytes. Must fit within resultArray. If outputLength is greater than NATURAL_LENGTH,
|
||||||
|
the first (lowest index) NATURAL_LENGTH bytes of resultArray will be used for the HMAC.
|
||||||
|
If outputLength is 0, then the natural HMAC output length is selected.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hmacCT(const void *data, const size_t dataLength, const void *hashKey, const size_t hashKeyLength, void *resultArray, const size_t outputLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a SHA512 HMAC from the message, using the provided hashKey. The result will be hmacLength bytes long and returned as a String in HEX format.
|
||||||
|
Constant-time version.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param message The string from which to create the HMAC. Must have a length in the range [getCtMinDataLength(), getCtMaxDataLength()].
|
||||||
|
@param hashKey The hash key to use when creating the HMAC.
|
||||||
|
@param hashKeyLength The length of the hash key in bytes.
|
||||||
|
@param hmacLength The desired length of the generated HMAC, in bytes. Valid values are 1 to NATURAL_LENGTH.
|
||||||
|
|
||||||
|
@return A String with the generated HMAC in HEX format.
|
||||||
|
*/
|
||||||
|
static String hmacCT(const String &message, const void *hashKey, const size_t hashKeyLength, const size_t hmacLength);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// #################### MD5+SHA-1 ####################
|
||||||
|
|
||||||
|
struct MD5SHA1
|
||||||
|
{
|
||||||
|
static constexpr uint8_t NATURAL_LENGTH = 36;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a MD5+SHA-1 hash of the data. The result will be NATURAL_LENGTH bytes long and stored in resultArray.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
MD5+SHA-1 is the concatenation of MD5 and SHA-1 computed over the same input; in the implementation, the internal data buffer is shared,
|
||||||
|
thus making it more memory-efficient than separate MD5 and SHA-1. It can be useful in implementing SSL 3.0, TLS 1.0 and TLS 1.1.
|
||||||
|
|
||||||
|
@param data The data array from which to create the hash.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param resultArray The array wherein to store the resulting hash. MUST be be able to contain NATURAL_LENGTH bytes or more.
|
||||||
|
|
||||||
|
@return A pointer to resultArray.
|
||||||
|
*/
|
||||||
|
static void *hash(const void *data, const size_t dataLength, void *resultArray);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a MD5+SHA-1 hash of the data. The result will be NATURAL_LENGTH bytes long and returned as a String in HEX format.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
MD5+SHA-1 is the concatenation of MD5 and SHA-1 computed over the same input; in the implementation, the internal data buffer is shared,
|
||||||
|
thus making it more memory-efficient than separate MD5 and SHA-1. It can be useful in implementing SSL 3.0, TLS 1.0 and TLS 1.1.
|
||||||
|
|
||||||
|
@param message The string from which to create the hash.
|
||||||
|
|
||||||
|
@return A String with the generated hash in HEX format.
|
||||||
|
*/
|
||||||
|
static String hash(const String &message);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// #################### HKDF ####################
|
||||||
|
|
||||||
|
struct HKDF
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
KDFs (key derivation functions) are functions that takes a variable length input, and provide a variable length output, meant to be used to derive subkeys from a master key.
|
||||||
|
HKDF is a KDF defined by RFC 5869. It is based on HMAC. The provided implementation uses SHA-256 as the underlying hash function.
|
||||||
|
|
||||||
|
The constructor initializes the HKDF implementation with the input data to use for HKDF processing. (calls HKDF::init())
|
||||||
|
|
||||||
|
@param keyMaterial An array containing the key material to use when deriving subkeys. Typically this would be the master key.
|
||||||
|
@param keyMaterialLength The length of keyMaterial in bytes.
|
||||||
|
@param salt An array containing the salt to use when ingesting key material. Salt is non-secret and can be empty.
|
||||||
|
Its role is normally to bind the input to a conventional identifier that qualify it within the used protocol or application.
|
||||||
|
@param saltLength The length of the salt array, in bytes.
|
||||||
|
*/
|
||||||
|
HKDF(const void *keyMaterial, const size_t keyMaterialLength, const void *salt = nullptr, const size_t saltLength = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
This method initializes the HKDF implementation with the input data to use for HKDF processing.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param keyMaterial An array containing the key material to use when deriving subkeys. Typically this would be the master key.
|
||||||
|
@param keyMaterialLength The length of keyMaterial in bytes.
|
||||||
|
@param salt An array containing the salt to use when ingesting key material. Salt is non-secret and can be empty.
|
||||||
|
Its role is normally to bind the input to a conventional identifier that qualify it within the used protocol or application.
|
||||||
|
@param saltLength The length of the salt array, in bytes.
|
||||||
|
*/
|
||||||
|
void init(const void *keyMaterial, const size_t keyMaterialLength, const void *salt = nullptr, const size_t saltLength = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Produce more output bytes from the current HKDF state. This method may be called several times to obtain the full output by chunks.
|
||||||
|
The total output size is limited to 255 * SHA256::NATURAL_LENGTH bytes per unique HKDF::init()/constructor call.
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
@param resultArray The array wherein to store the resulting HKDF.
|
||||||
|
@param outputLength The requested number of bytes to fill with HKDF output in resultArray.
|
||||||
|
@param info NOTE: For correct HKDF processing, the same "info" string must be provided for every call until there's a new unique HKDF::init().
|
||||||
|
An array containing the information string to use when producing output. Info is non-secret and can be empty.
|
||||||
|
Its role is normally to bind the output to a conventional identifier that qualify it within the used protocol or application.
|
||||||
|
@param infoLength The length of the info array, in bytes.
|
||||||
|
|
||||||
|
@return The number of HKDF bytes actually produced.
|
||||||
|
*/
|
||||||
|
size_t produce(void *resultArray, const size_t outputLength, const void *info = nullptr, size_t infoLength = 0);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
br_hkdf_context hkdfContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// #################### Authenticated Encryption with Associated Data (AEAD) ####################
|
||||||
|
|
||||||
|
/**
|
||||||
|
From https://www.bearssl.org/apidoc/bearssl__aead_8h.html
|
||||||
|
|
||||||
|
An AEAD algorithm processes messages and provides confidentiality (encryption) and checked integrity (MAC). It uses the following parameters:
|
||||||
|
|
||||||
|
- A symmetric key. Exact size depends on the AEAD algorithm.
|
||||||
|
- A nonce (IV). Size depends on the AEAD algorithm; for most algorithms, it is crucial for security that any given nonce value is never used twice for the same key and distinct messages.
|
||||||
|
- Data to encrypt and protect.
|
||||||
|
- Additional authenticated data, which is covered by the MAC but otherwise left untouched (i.e. not encrypted).
|
||||||
|
|
||||||
|
The AEAD algorithm encrypts the data, and produces an authentication tag.
|
||||||
|
It is assumed that the encrypted data, the tag, the additional authenticated data and the nonce are sent to the receiver;
|
||||||
|
the additional data and the nonce may be implicit (e.g. using elements of the underlying transport protocol, such as record sequence numbers).
|
||||||
|
The receiver will recompute the tag value and compare it with the one received;
|
||||||
|
if they match, then the data is correct, and can be decrypted and used;
|
||||||
|
otherwise, at least one of the elements was altered in transit, normally leading to wholesale rejection of the complete message.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
// #################### ChaCha20+Poly1305 AEAD ####################
|
||||||
|
|
||||||
|
struct ChaCha20Poly1305
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
Encrypt the data array using the ChaCha20 stream cipher and use Poly1305 for message authentication.
|
||||||
|
The function generates in place an equal-length ChaCha20 encrypted version of the data array.
|
||||||
|
More information about this encryption standard can be found here: https://tools.ietf.org/html/rfc7539 , https://tools.ietf.org/html/rfc8439
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
Encryption of small messages (up to a few hundred data bytes) takes around 0.5-1 ms with the default nonceGenerator, half of this without keySalt.
|
||||||
|
|
||||||
|
The output values of ChaCha20Poly1305::encrypt should be passed as input values to ChaCha20Poly1305::decrypt.
|
||||||
|
|
||||||
|
Note that a 12 byte nonce is generated via getNonceGenerator() every time ChaCha20Poly1305::encrypt is called.
|
||||||
|
If the same key and nonce combination is used more than once for distinct messages, the encryption will be broken, so keep the following in mind:
|
||||||
|
|
||||||
|
By default the nonce is generated via the hardware random number generator of the ESP8266.
|
||||||
|
The entropy of this source may not be sufficient to avoid nonce collisions, so to further reduce the risk of encryption failure
|
||||||
|
it is recommended that a keySalt is always provided when using the default nonceGenerator. Using a keySalt will create a
|
||||||
|
pseudorandom subkey from the original key via HKDF, and use that for the encryption/decryption.
|
||||||
|
The same key + keySalt will always generate the same subkey.
|
||||||
|
|
||||||
|
An alternative to using a keySalt is to change the nonceGenerator so that it does not rely on random numbers.
|
||||||
|
One way to do this would be to use a counter that guarantees the same key + nonce combination is never used.
|
||||||
|
This may not be easily achievable in all scenarios, however.
|
||||||
|
|
||||||
|
@param data An array containing the data to encrypt. The encrypted data is generated in place, so when the function returns the data array will contain the encrypted data.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param key The secret encryption key to use. Must be 32 bytes (ENCRYPTION_KEY_LENGTH) long.
|
||||||
|
@param keySalt The salt to use when generating a subkey from key. Note that the same salt must be used during decryption as during encryption. Set to nullptr to prevent subkey generation.
|
||||||
|
@param keySaltLength The length of keySalt in bytes.
|
||||||
|
@param resultingNonce The array that will store the nonce generated during encryption. Must be able to contain at least 12 bytes. The nonce is not secret and must be passed to the decryption function.
|
||||||
|
@param resultingTag The array that will store the message authentication tag generated during encryption. Must be able to contain at least 16 bytes. The tag is not secret and must be passed to the decryption function.
|
||||||
|
@param aad Additional authenticated data. This data will be covered by the Poly1305 MAC, but not encrypted.
|
||||||
|
You can include the unencrypted parts of your message as AAD to ensure that the encrypted content cannot
|
||||||
|
be re-sent with replaced unencrypted data by an attacker.
|
||||||
|
Defaults to nullptr.
|
||||||
|
@param aadLength The length of the aad array in bytes. Defaults to 0.
|
||||||
|
*/
|
||||||
|
static void encrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, void *resultingNonce, void *resultingTag, const void *aad = nullptr, const size_t aadLength = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Decrypt the data array using the ChaCha20 stream cipher and use Poly1305 for message authentication.
|
||||||
|
The function generates in place an equal-length ChaCha20 decrypted version of the data array.
|
||||||
|
More information about this encryption standard can be found here: https://tools.ietf.org/html/rfc7539 , https://tools.ietf.org/html/rfc8439
|
||||||
|
Uses the BearSSL cryptographic library.
|
||||||
|
|
||||||
|
Decryption of small messages (up to a few hundred data bytes) takes around 0.5-1 ms, half of this without keySalt.
|
||||||
|
|
||||||
|
The output values of ChaCha20Poly1305::encrypt should be passed as input values to ChaCha20Poly1305::decrypt.
|
||||||
|
|
||||||
|
@param data An array containing the data to decrypt. The decrypted data is generated in place, so when the function returns the data array will contain the decrypted data.
|
||||||
|
@param dataLength The length of the data array in bytes.
|
||||||
|
@param key The secret encryption key to use. Must be 32 bytes (ENCRYPTION_KEY_LENGTH) long.
|
||||||
|
@param keySalt The salt to use when generating a subkey from key. Note that the same salt must be used during decryption as during encryption. Set to nullptr to prevent subkey generation.
|
||||||
|
@param keySaltLength The length of keySalt in bytes.
|
||||||
|
@param encryptionNonce An array containing the nonce that was generated during encryption. The nonce should be 12 bytes.
|
||||||
|
@param encryptionTag An array containing the message authentication tag that was generated during encryption. The tag should be 16 bytes.
|
||||||
|
@param aad Additional authenticated data. This data will be covered by the Poly1305 MAC, but not decrypted.
|
||||||
|
You can include the unencrypted parts of your message as AAD to ensure that the encrypted content cannot
|
||||||
|
be re-sent with replaced unencrypted data by an attacker.
|
||||||
|
Defaults to nullptr.
|
||||||
|
@param aadLength The length of the aad array in bytes. Defaults to 0.
|
||||||
|
|
||||||
|
@return True if the decryption was successful (the generated tag matches encryptionTag). False otherwise. Note that the data array is modified regardless of this outcome.
|
||||||
|
*/
|
||||||
|
static bool decrypt(void *data, const size_t dataLength, const void *key, const void *keySalt, const size_t keySaltLength, const void *encryptionNonce, const void *encryptionTag, const void *aad = nullptr, const size_t aadLength = 0);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
@ -29,20 +29,23 @@ void EspClass::getHeapStats(uint32_t* hfree, uint16_t* hmax, uint8_t* hfrag)
|
|||||||
// Having getFreeHeap()=sum(hole-size), fragmentation is given by
|
// Having getFreeHeap()=sum(hole-size), fragmentation is given by
|
||||||
// 100 * (1 - sqrt(sum(hole-size²)) / sum(hole-size))
|
// 100 * (1 - sqrt(sum(hole-size²)) / sum(hole-size))
|
||||||
|
|
||||||
umm_info(NULL, 0);
|
umm_info(NULL, false);
|
||||||
uint8_t block_size = umm_block_size();
|
|
||||||
uint32_t fh = ummHeapInfo.freeBlocks * block_size;
|
uint32_t free_size = umm_free_heap_size_core(umm_get_current_heap());
|
||||||
if (hfree)
|
if (hfree)
|
||||||
*hfree = fh;
|
*hfree = free_size;
|
||||||
if (hmax)
|
if (hmax)
|
||||||
*hmax = ummHeapInfo.maxFreeContiguousBlocks * block_size;
|
*hmax = (uint16_t)umm_max_block_size_core(umm_get_current_heap());
|
||||||
if (hfrag)
|
if (hfrag) {
|
||||||
*hfrag = 100 - (sqrt32(ummHeapInfo.freeSize2) * 100) / fh;
|
if (free_size) {
|
||||||
|
*hfrag = umm_fragmentation_metric_core(umm_get_current_heap());
|
||||||
|
} else {
|
||||||
|
*hfrag = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t EspClass::getHeapFragmentation()
|
uint8_t EspClass::getHeapFragmentation()
|
||||||
{
|
{
|
||||||
uint8_t hfrag;
|
return (uint8_t)umm_fragmentation_metric();
|
||||||
getHeapStats(nullptr, nullptr, &hfrag);
|
|
||||||
return hfrag;
|
|
||||||
}
|
}
|
||||||
|
@ -21,36 +21,29 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <user_interface.h>
|
#include <user_interface.h>
|
||||||
#include <core_version.h>
|
#include <core_version.h>
|
||||||
#include <lwip/init.h> // LWIP_VERSION_*
|
|
||||||
#include <lwipopts.h> // LWIP_HASH_STR (lwip2)
|
#include <lwipopts.h> // LWIP_HASH_STR (lwip2)
|
||||||
#include <bearssl/bearssl_git.h> // BEARSSL_GIT short hash
|
#include <bearssl/bearssl_git.h> // BEARSSL_GIT short hash
|
||||||
|
|
||||||
#define STRHELPER(x) #x
|
#define STRHELPER(x) #x
|
||||||
#define STR(x) STRHELPER(x) // stringifier
|
#define STR(x) STRHELPER(x) // stringifier
|
||||||
|
|
||||||
static const char arduino_esp8266_git_ver [] PROGMEM = STR(ARDUINO_ESP8266_GIT_DESC);
|
static const char arduino_esp8266_git_ver [] PROGMEM = "/Core:" STR(ARDUINO_ESP8266_GIT_DESC) "=";
|
||||||
|
#if LWIP_IPV6
|
||||||
|
static const char lwip_version [] PROGMEM = "/lwIP:IPv6+" LWIP_HASH_STR;
|
||||||
|
#else
|
||||||
|
static const char lwip_version [] PROGMEM = "/lwIP:" LWIP_HASH_STR;
|
||||||
|
#endif
|
||||||
static const char bearssl_version [] PROGMEM = "/BearSSL:" STR(BEARSSL_GIT);
|
static const char bearssl_version [] PROGMEM = "/BearSSL:" STR(BEARSSL_GIT);
|
||||||
|
|
||||||
String EspClass::getFullVersion()
|
String EspClass::getFullVersion() {
|
||||||
{
|
String s(F("SDK:"));
|
||||||
return String(F("SDK:")) + system_get_sdk_version()
|
s.reserve(127);
|
||||||
+ F("/Core:") + FPSTR(arduino_esp8266_git_ver)
|
|
||||||
+ F("=") + String(esp8266::coreVersionNumeric())
|
s += system_get_sdk_version();
|
||||||
#if LWIP_VERSION_MAJOR == 1
|
s += FPSTR(arduino_esp8266_git_ver);
|
||||||
+ F("/lwIP:") + String(LWIP_VERSION_MAJOR) + "." + String(LWIP_VERSION_MINOR) + "." + String(LWIP_VERSION_REVISION)
|
s += String(esp8266::coreVersionNumeric());
|
||||||
#if LWIP_VERSION_IS_DEVELOPMENT
|
s += FPSTR(lwip_version);
|
||||||
+ F("-dev")
|
s += FPSTR(bearssl_version);
|
||||||
#endif
|
|
||||||
#if LWIP_VERSION_IS_RC
|
return s;
|
||||||
+ F("rc") + String(LWIP_VERSION_RC)
|
|
||||||
#endif
|
|
||||||
#else // LWIP_VERSION_MAJOR != 1
|
|
||||||
+ F("/lwIP:")
|
|
||||||
#if LWIP_IPV6
|
|
||||||
+ F("IPv6+")
|
|
||||||
#endif // LWIP_IPV6
|
|
||||||
+ F(LWIP_HASH_STR)
|
|
||||||
#endif // LWIP_VERSION_MAJOR != 1
|
|
||||||
+ FPSTR(bearssl_version)
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Arduino.h"
|
#include "Esp.h"
|
||||||
#include "flash_utils.h"
|
#include "flash_utils.h"
|
||||||
#include "eboot_command.h"
|
#include "eboot_command.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -27,6 +27,11 @@
|
|||||||
#include "umm_malloc/umm_malloc.h"
|
#include "umm_malloc/umm_malloc.h"
|
||||||
#include "cont.h"
|
#include "cont.h"
|
||||||
|
|
||||||
|
#include "coredecls.h"
|
||||||
|
#include "umm_malloc/umm_malloc.h"
|
||||||
|
#include <pgmspace.h>
|
||||||
|
#include "reboot_uart_dwnld.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "user_interface.h"
|
#include "user_interface.h"
|
||||||
|
|
||||||
@ -36,6 +41,9 @@ extern struct rst_info resetInfo;
|
|||||||
|
|
||||||
//#define DEBUG_SERIAL Serial
|
//#define DEBUG_SERIAL Serial
|
||||||
|
|
||||||
|
#ifndef PUYA_SUPPORT
|
||||||
|
#define PUYA_SUPPORT 1
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User-defined Literals
|
* User-defined Literals
|
||||||
@ -184,9 +192,6 @@ bool EspClass::rtcUserMemoryWrite(uint32_t offset, uint32_t *data, size_t size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
extern "C" void __real_system_restart_local();
|
|
||||||
void EspClass::reset(void)
|
void EspClass::reset(void)
|
||||||
{
|
{
|
||||||
__real_system_restart_local();
|
__real_system_restart_local();
|
||||||
@ -198,9 +203,18 @@ void EspClass::restart(void)
|
|||||||
esp_yield();
|
esp_yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void EspClass::rebootIntoUartDownloadMode()
|
||||||
|
{
|
||||||
|
wdtDisable();
|
||||||
|
/* disable hardware watchdog */
|
||||||
|
CLEAR_PERI_REG_MASK(PERIPHS_HW_WDT, 0x1);
|
||||||
|
|
||||||
|
esp8266RebootIntoUartDownloadMode();
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t EspClass::getVcc(void)
|
uint16_t EspClass::getVcc(void)
|
||||||
{
|
{
|
||||||
InterruptLock lock;
|
esp8266::InterruptLock lock;
|
||||||
(void)lock;
|
(void)lock;
|
||||||
return system_get_vdd33();
|
return system_get_vdd33();
|
||||||
}
|
}
|
||||||
@ -258,12 +272,6 @@ uint8_t EspClass::getBootMode(void)
|
|||||||
return system_get_boot_mode();
|
return system_get_boot_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t EspClass::getCpuFreqMHz(void)
|
|
||||||
{
|
|
||||||
return system_get_cpu_freq();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
uint32_t EspClass::getFlashChipId(void)
|
uint32_t EspClass::getFlashChipId(void)
|
||||||
{
|
{
|
||||||
static uint32_t flash_chip_id = 0;
|
static uint32_t flash_chip_id = 0;
|
||||||
@ -397,6 +405,8 @@ uint32_t EspClass::getFlashChipSizeByChipId(void) {
|
|||||||
return (64_kB);
|
return (64_kB);
|
||||||
|
|
||||||
// Winbond
|
// Winbond
|
||||||
|
case 0x1840EF: // W25Q128
|
||||||
|
return (16_MB);
|
||||||
case 0x1640EF: // W25Q32
|
case 0x1640EF: // W25Q32
|
||||||
return (4_MB);
|
return (4_MB);
|
||||||
case 0x1540EF: // W25Q16
|
case 0x1540EF: // W25Q16
|
||||||
@ -416,6 +426,10 @@ uint32_t EspClass::getFlashChipSizeByChipId(void) {
|
|||||||
case 0x1340E0: // BG25Q40
|
case 0x1340E0: // BG25Q40
|
||||||
return (512_kB);
|
return (512_kB);
|
||||||
|
|
||||||
|
// XMC - Wuhan Xinxin Semiconductor Manufacturing Corp
|
||||||
|
case 0x164020: // XM25QH32B
|
||||||
|
return (4_MB);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -439,35 +453,59 @@ bool EspClass::checkFlashConfig(bool needsEquals) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These are defined in the linker script, and filled in by the elf2bin.py util
|
||||||
|
extern "C" uint32_t __crc_len;
|
||||||
|
extern "C" uint32_t __crc_val;
|
||||||
|
|
||||||
|
bool EspClass::checkFlashCRC() {
|
||||||
|
// Dummy CRC fill
|
||||||
|
uint32_t z[2];
|
||||||
|
z[0] = z[1] = 0;
|
||||||
|
|
||||||
|
uint32_t firstPart = (uintptr_t)&__crc_len - 0x40200000; // How many bytes to check before the 1st CRC val
|
||||||
|
|
||||||
|
// Start the checksum
|
||||||
|
uint32_t crc = crc32((const void*)0x40200000, firstPart, 0xffffffff);
|
||||||
|
// Pretend the 2 words of crc/len are zero to be idempotent
|
||||||
|
crc = crc32(z, 8, crc);
|
||||||
|
// Finish the CRC calculation over the rest of flash
|
||||||
|
crc = crc32((const void*)(0x40200000 + firstPart + 8), __crc_len - (firstPart + 8), crc);
|
||||||
|
return crc == __crc_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
String EspClass::getResetReason(void) {
|
String EspClass::getResetReason(void) {
|
||||||
char buff[32];
|
const __FlashStringHelper* buff;
|
||||||
if (resetInfo.reason == REASON_DEFAULT_RST) { // normal startup by power on
|
|
||||||
strcpy_P(buff, PSTR("Power on"));
|
switch(resetInfo.reason) {
|
||||||
} else if (resetInfo.reason == REASON_WDT_RST) { // hardware watch dog reset
|
// normal startup by power on
|
||||||
strcpy_P(buff, PSTR("Hardware Watchdog"));
|
case REASON_DEFAULT_RST: buff = F("Power On"); break;
|
||||||
} else if (resetInfo.reason == REASON_EXCEPTION_RST) { // exception reset, GPIO status won’t change
|
// hardware watch dog reset
|
||||||
strcpy_P(buff, PSTR("Exception"));
|
case REASON_WDT_RST: buff = F("Hardware Watchdog"); break;
|
||||||
} else if (resetInfo.reason == REASON_SOFT_WDT_RST) { // software watch dog reset, GPIO status won’t change
|
// exception reset, GPIO status won’t change
|
||||||
strcpy_P(buff, PSTR("Software Watchdog"));
|
case REASON_EXCEPTION_RST: buff = F("Exception"); break;
|
||||||
} else if (resetInfo.reason == REASON_SOFT_RESTART) { // software restart ,system_restart , GPIO status won’t change
|
// software watch dog reset, GPIO status won’t change
|
||||||
strcpy_P(buff, PSTR("Software/System restart"));
|
case REASON_SOFT_WDT_RST: buff = F("Software Watchdog"); break;
|
||||||
} else if (resetInfo.reason == REASON_DEEP_SLEEP_AWAKE) { // wake up from deep-sleep
|
// software restart ,system_restart , GPIO status won’t change
|
||||||
strcpy_P(buff, PSTR("Deep-Sleep Wake"));
|
case REASON_SOFT_RESTART: buff = F("Software/System restart"); break;
|
||||||
} else if (resetInfo.reason == REASON_EXT_SYS_RST) { // external system reset
|
// wake up from deep-sleep
|
||||||
strcpy_P(buff, PSTR("External System"));
|
case REASON_DEEP_SLEEP_AWAKE: buff = F("Deep-Sleep Wake"); break;
|
||||||
} else {
|
// // external system reset
|
||||||
strcpy_P(buff, PSTR("Unknown"));
|
case REASON_EXT_SYS_RST: buff = F("External System"); break;
|
||||||
|
default: buff = F("Unknown"); break;
|
||||||
}
|
}
|
||||||
return String(buff);
|
return String(buff);
|
||||||
}
|
}
|
||||||
|
|
||||||
String EspClass::getResetInfo(void) {
|
String EspClass::getResetInfo(void) {
|
||||||
if(resetInfo.reason != 0) {
|
if (resetInfo.reason >= REASON_WDT_RST && resetInfo.reason <= REASON_SOFT_WDT_RST) {
|
||||||
char buff[200];
|
char buff[200];
|
||||||
sprintf(&buff[0], "Fatal exception:%d flag:%d (%s) epc1:0x%08x epc2:0x%08x epc3:0x%08x excvaddr:0x%08x depc:0x%08x", resetInfo.exccause, resetInfo.reason, (resetInfo.reason == 0 ? "DEFAULT" : resetInfo.reason == 1 ? "WDT" : resetInfo.reason == 2 ? "EXCEPTION" : resetInfo.reason == 3 ? "SOFT_WDT" : resetInfo.reason == 4 ? "SOFT_RESTART" : resetInfo.reason == 5 ? "DEEP_SLEEP_AWAKE" : resetInfo.reason == 6 ? "EXT_SYS_RST" : "???"), resetInfo.epc1, resetInfo.epc2, resetInfo.epc3, resetInfo.excvaddr, resetInfo.depc);
|
sprintf_P(buff, PSTR("Fatal exception:%d flag:%d (%s) epc1:0x%08x epc2:0x%08x epc3:0x%08x excvaddr:0x%08x depc:0x%08x"),
|
||||||
|
resetInfo.exccause, resetInfo.reason, getResetReason().c_str(),
|
||||||
|
resetInfo.epc1, resetInfo.epc2, resetInfo.epc3, resetInfo.excvaddr, resetInfo.depc);
|
||||||
return String(buff);
|
return String(buff);
|
||||||
}
|
}
|
||||||
return String("flag: 0");
|
return getResetReason();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct rst_info * EspClass::getResetInfoPtr(void) {
|
struct rst_info * EspClass::getResetInfoPtr(void) {
|
||||||
@ -487,6 +525,63 @@ bool EspClass::eraseConfig(void) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes) const
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The ESP32 Technical Reference Manual v4.1 chapter 24 has the following to say about random number generation (no information found for ESP8266):
|
||||||
|
*
|
||||||
|
* "When used correctly, every 32-bit value the system reads from the RNG_DATA_REG register of the random number generator is a true random number.
|
||||||
|
* These true random numbers are generated based on the noise in the Wi-Fi/BT RF system.
|
||||||
|
* When Wi-Fi and BT are disabled, the random number generator will give out pseudo-random numbers.
|
||||||
|
*
|
||||||
|
* When Wi-Fi or BT is enabled, the random number generator is fed two bits of entropy every APB clock cycle (normally 80 MHz).
|
||||||
|
* Thus, for the maximum amount of entropy, it is advisable to read the random register at a maximum rate of 5 MHz.
|
||||||
|
* A data sample of 2 GB, read from the random number generator with Wi-Fi enabled and the random register read at 5 MHz,
|
||||||
|
* has been tested using the Dieharder Random Number Testsuite (version 3.31.1).
|
||||||
|
* The sample passed all tests."
|
||||||
|
*
|
||||||
|
* Since ESP32 is the sequal to ESP8266 it is unlikely that the ESP8266 is able to generate random numbers more quickly than 5 MHz when run at a 80 MHz frequency.
|
||||||
|
* A maximum random number frequency of 0.5 MHz is used here to leave some margin for possibly inferior components in the ESP8266.
|
||||||
|
* It should be noted that the ESP8266 has no Bluetooth functionality, so turning the WiFi off is likely to cause RANDOM_REG32 to use pseudo-random numbers.
|
||||||
|
*
|
||||||
|
* It is possible that yield() must be called on the ESP8266 to properly feed the hardware random number generator new bits, since there is only one processor core available.
|
||||||
|
* However, no feeding requirements are mentioned in the ESP32 documentation, and using yield() could possibly cause extended delays during number generation.
|
||||||
|
* Thus only delayMicroseconds() is used below.
|
||||||
|
*/
|
||||||
|
|
||||||
|
constexpr uint8_t cooldownMicros = 2;
|
||||||
|
static uint32_t lastCalledMicros = micros() - cooldownMicros;
|
||||||
|
|
||||||
|
uint32_t randomNumber = 0;
|
||||||
|
|
||||||
|
for(size_t byteIndex = 0; byteIndex < outputSizeBytes; ++byteIndex)
|
||||||
|
{
|
||||||
|
if(byteIndex % 4 == 0)
|
||||||
|
{
|
||||||
|
// Old random number has been used up (random number could be exactly 0, so we can't check for that)
|
||||||
|
|
||||||
|
uint32_t timeSinceLastCall = micros() - lastCalledMicros;
|
||||||
|
if(timeSinceLastCall < cooldownMicros)
|
||||||
|
delayMicroseconds(cooldownMicros - timeSinceLastCall);
|
||||||
|
|
||||||
|
randomNumber = RANDOM_REG32;
|
||||||
|
lastCalledMicros = micros();
|
||||||
|
}
|
||||||
|
|
||||||
|
resultArray[byteIndex] = randomNumber;
|
||||||
|
randomNumber >>= 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::random() const
|
||||||
|
{
|
||||||
|
union { uint32_t b32; uint8_t b8[4]; } result;
|
||||||
|
random(result.b8, 4);
|
||||||
|
return result.b32;
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t EspClass::getSketchSize() {
|
uint32_t EspClass::getSketchSize() {
|
||||||
static uint32_t result = 0;
|
static uint32_t result = 0;
|
||||||
if (result)
|
if (result)
|
||||||
@ -494,7 +589,7 @@ uint32_t EspClass::getSketchSize() {
|
|||||||
|
|
||||||
image_header_t image_header;
|
image_header_t image_header;
|
||||||
uint32_t pos = APP_START_OFFSET;
|
uint32_t pos = APP_START_OFFSET;
|
||||||
if (spi_flash_read(pos, (uint32_t*) &image_header, sizeof(image_header))) {
|
if (spi_flash_read(pos, (uint32_t*) &image_header, sizeof(image_header)) != SPI_FLASH_RESULT_OK) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
pos += sizeof(image_header);
|
pos += sizeof(image_header);
|
||||||
@ -506,7 +601,7 @@ uint32_t EspClass::getSketchSize() {
|
|||||||
++section_index)
|
++section_index)
|
||||||
{
|
{
|
||||||
section_header_t section_header = {0, 0};
|
section_header_t section_header = {0, 0};
|
||||||
if (spi_flash_read(pos, (uint32_t*) §ion_header, sizeof(section_header))) {
|
if (spi_flash_read(pos, (uint32_t*) §ion_header, sizeof(section_header)) != SPI_FLASH_RESULT_OK) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
pos += sizeof(section_header);
|
pos += sizeof(section_header);
|
||||||
@ -519,14 +614,14 @@ uint32_t EspClass::getSketchSize() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" uint32_t _SPIFFS_start;
|
extern "C" uint32_t _FS_start;
|
||||||
|
|
||||||
uint32_t EspClass::getFreeSketchSpace() {
|
uint32_t EspClass::getFreeSketchSpace() {
|
||||||
|
|
||||||
uint32_t usedSize = getSketchSize();
|
uint32_t usedSize = getSketchSize();
|
||||||
// round one sector up
|
// round one sector up
|
||||||
uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
uint32_t freeSpaceStart = (usedSize + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
||||||
uint32_t freeSpaceEnd = (uint32_t)&_SPIFFS_start - 0x40200000;
|
uint32_t freeSpaceEnd = (uint32_t)&_FS_start - 0x40200000;
|
||||||
|
|
||||||
#ifdef DEBUG_SERIAL
|
#ifdef DEBUG_SERIAL
|
||||||
DEBUG_SERIAL.printf("usedSize=%u freeSpaceStart=%u freeSpaceEnd=%u\r\n", usedSize, freeSpaceStart, freeSpaceEnd);
|
DEBUG_SERIAL.printf("usedSize=%u freeSpaceStart=%u freeSpaceEnd=%u\r\n", usedSize, freeSpaceStart, freeSpaceEnd);
|
||||||
@ -577,35 +672,39 @@ bool EspClass::flashEraseSector(uint32_t sector) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if PUYA_SUPPORT
|
#if PUYA_SUPPORT
|
||||||
static int spi_flash_write_puya(uint32_t offset, uint32_t *data, size_t size) {
|
static SpiFlashOpResult spi_flash_write_puya(uint32_t offset, uint32_t *data, size_t size) {
|
||||||
if (data == nullptr) {
|
if (data == nullptr) {
|
||||||
return 1; // SPI_FLASH_RESULT_ERR
|
return SPI_FLASH_RESULT_ERR;
|
||||||
|
}
|
||||||
|
if (size % 4 != 0) {
|
||||||
|
return SPI_FLASH_RESULT_ERR;
|
||||||
}
|
}
|
||||||
// PUYA flash chips need to read existing data, update in memory and write modified data again.
|
// PUYA flash chips need to read existing data, update in memory and write modified data again.
|
||||||
static uint32_t *flash_write_puya_buf = nullptr;
|
static uint32_t *flash_write_puya_buf = nullptr;
|
||||||
int rc = 0;
|
|
||||||
uint32_t* ptr = data;
|
|
||||||
|
|
||||||
if (flash_write_puya_buf == nullptr) {
|
if (flash_write_puya_buf == nullptr) {
|
||||||
flash_write_puya_buf = (uint32_t*) malloc(PUYA_BUFFER_SIZE);
|
flash_write_puya_buf = (uint32_t*) malloc(FLASH_PAGE_SIZE);
|
||||||
// No need to ever free this, since the flash chip will never change at runtime.
|
// No need to ever free this, since the flash chip will never change at runtime.
|
||||||
if (flash_write_puya_buf == nullptr) {
|
if (flash_write_puya_buf == nullptr) {
|
||||||
// Memory could not be allocated.
|
// Memory could not be allocated.
|
||||||
return 1; // SPI_FLASH_RESULT_ERR
|
return SPI_FLASH_RESULT_ERR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SpiFlashOpResult rc = SPI_FLASH_RESULT_OK;
|
||||||
|
uint32_t* ptr = data;
|
||||||
size_t bytesLeft = size;
|
size_t bytesLeft = size;
|
||||||
uint32_t pos = offset;
|
uint32_t pos = offset;
|
||||||
while (bytesLeft > 0 && rc == 0) {
|
while (bytesLeft > 0 && rc == SPI_FLASH_RESULT_OK) {
|
||||||
size_t bytesNow = bytesLeft;
|
size_t bytesNow = bytesLeft;
|
||||||
if (bytesNow > PUYA_BUFFER_SIZE) {
|
if (bytesNow > FLASH_PAGE_SIZE) {
|
||||||
bytesNow = PUYA_BUFFER_SIZE;
|
bytesNow = FLASH_PAGE_SIZE;
|
||||||
bytesLeft -= PUYA_BUFFER_SIZE;
|
bytesLeft -= FLASH_PAGE_SIZE;
|
||||||
} else {
|
} else {
|
||||||
bytesLeft = 0;
|
bytesLeft = 0;
|
||||||
}
|
}
|
||||||
rc = spi_flash_read(pos, flash_write_puya_buf, bytesNow);
|
rc = spi_flash_read(pos, flash_write_puya_buf, bytesNow);
|
||||||
if (rc != 0) {
|
if (rc != SPI_FLASH_RESULT_OK) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < bytesNow / 4; ++i) {
|
for (size_t i = 0; i < bytesNow / 4; ++i) {
|
||||||
@ -619,23 +718,240 @@ static int spi_flash_write_puya(uint32_t offset, uint32_t *data, size_t size) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool EspClass::flashWrite(uint32_t offset, uint32_t *data, size_t size) {
|
bool EspClass::flashReplaceBlock(uint32_t address, const uint8_t *value, uint32_t byteCount) {
|
||||||
int rc = 0;
|
uint32_t alignedAddress = (address & ~3);
|
||||||
|
uint32_t alignmentOffset = address - alignedAddress;
|
||||||
|
|
||||||
|
if (alignedAddress != ((address + byteCount - 1) & ~3)) {
|
||||||
|
// Only one 4 byte block is supported
|
||||||
|
return false;
|
||||||
|
}
|
||||||
#if PUYA_SUPPORT
|
#if PUYA_SUPPORT
|
||||||
if (getFlashChipVendorId() == SPI_FLASH_VENDOR_PUYA) {
|
if (getFlashChipVendorId() == SPI_FLASH_VENDOR_PUYA) {
|
||||||
rc = spi_flash_write_puya(offset, data, size);
|
uint8_t tempData[4] __attribute__((aligned(4)));
|
||||||
|
if (spi_flash_read(alignedAddress, (uint32_t *)tempData, 4) != SPI_FLASH_RESULT_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < byteCount; i++) {
|
||||||
|
tempData[i + alignmentOffset] &= value[i];
|
||||||
|
}
|
||||||
|
if (spi_flash_write(alignedAddress, (uint32_t *)tempData, 4) != SPI_FLASH_RESULT_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#endif
|
#endif // PUYA_SUPPORT
|
||||||
{
|
{
|
||||||
rc = spi_flash_write(offset, data, size);
|
uint32_t tempData;
|
||||||
|
if (spi_flash_read(alignedAddress, &tempData, 4) != SPI_FLASH_RESULT_OK) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return rc == 0;
|
memcpy((uint8_t *)&tempData + alignmentOffset, value, byteCount);
|
||||||
|
if (spi_flash_write(alignedAddress, &tempData, 4) != SPI_FLASH_RESULT_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EspClass::flashRead(uint32_t offset, uint32_t *data, size_t size) {
|
size_t EspClass::flashWriteUnalignedMemory(uint32_t address, const uint8_t *data, size_t size) {
|
||||||
int rc = spi_flash_read(offset, (uint32_t*) data, size);
|
size_t sizeLeft = (size & ~3);
|
||||||
return rc == 0;
|
size_t currentOffset = 0;
|
||||||
|
// Memory is unaligned, so we need to copy it to an aligned buffer
|
||||||
|
uint32_t alignedData[FLASH_PAGE_SIZE / sizeof(uint32_t)] __attribute__((aligned(4)));
|
||||||
|
// Handle page boundary
|
||||||
|
bool pageBreak = ((address % 4) != 0) && ((address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1) / FLASH_PAGE_SIZE));
|
||||||
|
|
||||||
|
if (pageBreak) {
|
||||||
|
size_t byteCount = 4 - (address % 4);
|
||||||
|
|
||||||
|
if (!flashReplaceBlock(address, data, byteCount)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// We will now have aligned address, so we can cross page boundaries
|
||||||
|
currentOffset += byteCount;
|
||||||
|
// Realign size to 4
|
||||||
|
sizeLeft = (size - byteCount) & ~3;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sizeLeft) {
|
||||||
|
size_t willCopy = std::min(sizeLeft, sizeof(alignedData));
|
||||||
|
memcpy(alignedData, data + currentOffset, willCopy);
|
||||||
|
// We now have address, data and size aligned to 4 bytes, so we can use aligned write
|
||||||
|
if (!flashWrite(address + currentOffset, alignedData, willCopy))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
sizeLeft -= willCopy;
|
||||||
|
currentOffset += willCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::flashWritePageBreak(uint32_t address, const uint8_t *data, size_t size) {
|
||||||
|
if (size > 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
size_t pageLeft = FLASH_PAGE_SIZE - (address % FLASH_PAGE_SIZE);
|
||||||
|
size_t offset = 0;
|
||||||
|
size_t sizeLeft = size;
|
||||||
|
if (pageLeft > 3) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!flashReplaceBlock(address, data, pageLeft)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
offset += pageLeft;
|
||||||
|
sizeLeft -= pageLeft;
|
||||||
|
// We replaced last 4-byte block of the page, now we write the remainder in next page
|
||||||
|
if (!flashReplaceBlock(address + offset, data + offset, sizeLeft)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::flashWrite(uint32_t address, const uint32_t *data, size_t size) {
|
||||||
|
SpiFlashOpResult rc = SPI_FLASH_RESULT_OK;
|
||||||
|
bool pageBreak = ((address % 4) != 0 && (address / FLASH_PAGE_SIZE) != ((address + size - 1) / FLASH_PAGE_SIZE));
|
||||||
|
|
||||||
|
if ((uintptr_t)data % 4 != 0 || size % 4 != 0 || pageBreak) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#if PUYA_SUPPORT
|
||||||
|
if (getFlashChipVendorId() == SPI_FLASH_VENDOR_PUYA) {
|
||||||
|
rc = spi_flash_write_puya(address, const_cast<uint32_t *>(data), size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif // PUYA_SUPPORT
|
||||||
|
{
|
||||||
|
rc = spi_flash_write(address, const_cast<uint32_t *>(data), size);
|
||||||
|
}
|
||||||
|
return rc == SPI_FLASH_RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::flashWrite(uint32_t address, const uint8_t *data, size_t size) {
|
||||||
|
if (size == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sizeLeft = size & ~3;
|
||||||
|
size_t currentOffset = 0;
|
||||||
|
|
||||||
|
if (sizeLeft) {
|
||||||
|
if ((uintptr_t)data % 4 != 0) {
|
||||||
|
size_t written = flashWriteUnalignedMemory(address, data, size);
|
||||||
|
if (!written) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset += written;
|
||||||
|
sizeLeft -= written;
|
||||||
|
} else {
|
||||||
|
bool pageBreak = ((address % 4) != 0 && (address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1) / FLASH_PAGE_SIZE));
|
||||||
|
|
||||||
|
if (pageBreak) {
|
||||||
|
while (sizeLeft) {
|
||||||
|
// We cannot cross page boundary, but the write must be 4 byte aligned,
|
||||||
|
// so this is the maximum amount we can write
|
||||||
|
size_t pageBoundary = (FLASH_PAGE_SIZE - ((address + currentOffset) % FLASH_PAGE_SIZE)) & ~3;
|
||||||
|
|
||||||
|
if (sizeLeft > pageBoundary) {
|
||||||
|
// Aligned write up to page boundary
|
||||||
|
if (!flashWrite(address + currentOffset, (uint32_t *)(data + currentOffset), pageBoundary)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset += pageBoundary;
|
||||||
|
sizeLeft -= pageBoundary;
|
||||||
|
// Cross the page boundary
|
||||||
|
if (!flashWritePageBreak(address + currentOffset, data + currentOffset, 4)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset += 4;
|
||||||
|
sizeLeft -= 4;
|
||||||
|
} else {
|
||||||
|
// We do not cross page boundary
|
||||||
|
if (!flashWrite(address + currentOffset, (uint32_t *)(data + currentOffset), sizeLeft)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset += sizeLeft;
|
||||||
|
sizeLeft = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pointer is properly aligned and write does not cross page boundary,
|
||||||
|
// so use aligned write
|
||||||
|
if (!flashWrite(address, (uint32_t *)data, sizeLeft)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset = sizeLeft;
|
||||||
|
sizeLeft = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sizeLeft = size - currentOffset;
|
||||||
|
if (sizeLeft > 0) {
|
||||||
|
// Size was not aligned, so we have some bytes left to write, we also need to recheck for
|
||||||
|
// page boundary crossing
|
||||||
|
bool pageBreak = ((address % 4) != 0 && (address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1) / FLASH_PAGE_SIZE));
|
||||||
|
|
||||||
|
if (pageBreak) {
|
||||||
|
// Cross the page boundary
|
||||||
|
if (!flashWritePageBreak(address + currentOffset, data + currentOffset, sizeLeft)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Just write partial block
|
||||||
|
flashReplaceBlock(address + currentOffset, data + currentOffset, sizeLeft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::flashRead(uint32_t address, uint8_t *data, size_t size) {
|
||||||
|
size_t sizeAligned = size & ~3;
|
||||||
|
size_t currentOffset = 0;
|
||||||
|
|
||||||
|
if ((uintptr_t)data % 4 != 0) {
|
||||||
|
uint32_t alignedData[FLASH_PAGE_SIZE / sizeof(uint32_t)] __attribute__((aligned(4)));
|
||||||
|
size_t sizeLeft = sizeAligned;
|
||||||
|
|
||||||
|
while (sizeLeft) {
|
||||||
|
size_t willCopy = std::min(sizeLeft, sizeof(alignedData));
|
||||||
|
// We read to our aligned buffer and then copy to data
|
||||||
|
if (!flashRead(address + currentOffset, alignedData, willCopy))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memcpy(data + currentOffset, alignedData, willCopy);
|
||||||
|
sizeLeft -= willCopy;
|
||||||
|
currentOffset += willCopy;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pointer is properly aligned, so use aligned read
|
||||||
|
if (!flashRead(address, (uint32_t *)data, sizeAligned)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset = sizeAligned;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentOffset < size) {
|
||||||
|
uint32_t tempData;
|
||||||
|
if (spi_flash_read(address + currentOffset, &tempData, 4) != SPI_FLASH_RESULT_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memcpy((uint8_t *)data + currentOffset, &tempData, size - currentOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::flashRead(uint32_t address, uint32_t *data, size_t size) {
|
||||||
|
if ((uintptr_t)data % 4 != 0 || size % 4 != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (spi_flash_read(address, data, size) == SPI_FLASH_RESULT_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
String EspClass::getSketchMD5()
|
String EspClass::getSketchMD5()
|
||||||
@ -646,17 +962,17 @@ String EspClass::getSketchMD5()
|
|||||||
}
|
}
|
||||||
uint32_t lengthLeft = getSketchSize();
|
uint32_t lengthLeft = getSketchSize();
|
||||||
const size_t bufSize = 512;
|
const size_t bufSize = 512;
|
||||||
std::unique_ptr<uint8_t[]> buf(new uint8_t[bufSize]);
|
std::unique_ptr<uint8_t[]> buf(new (std::nothrow) uint8_t[bufSize]);
|
||||||
uint32_t offset = 0;
|
uint32_t offset = 0;
|
||||||
if(!buf.get()) {
|
if(!buf.get()) {
|
||||||
return String();
|
return emptyString;
|
||||||
}
|
}
|
||||||
MD5Builder md5;
|
MD5Builder md5;
|
||||||
md5.begin();
|
md5.begin();
|
||||||
while( lengthLeft > 0) {
|
while( lengthLeft > 0) {
|
||||||
size_t readBytes = (lengthLeft < bufSize) ? lengthLeft : bufSize;
|
size_t readBytes = (lengthLeft < bufSize) ? lengthLeft : bufSize;
|
||||||
if (!flashRead(offset, reinterpret_cast<uint32_t*>(buf.get()), (readBytes + 3) & ~3)) {
|
if (!flashRead(offset, reinterpret_cast<uint32_t*>(buf.get()), (readBytes + 3) & ~3)) {
|
||||||
return String();
|
return emptyString;
|
||||||
}
|
}
|
||||||
md5.add(buf.get(), readBytes);
|
md5.add(buf.get(), readBytes);
|
||||||
lengthLeft -= readBytes;
|
lengthLeft -= readBytes;
|
||||||
@ -666,3 +982,47 @@ String EspClass::getSketchMD5()
|
|||||||
result = md5.toString();
|
result = md5.toString();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EspClass::setExternalHeap()
|
||||||
|
{
|
||||||
|
#ifdef UMM_HEAP_EXTERNAL
|
||||||
|
if (!umm_push_heap(UMM_HEAP_EXTERNAL)) {
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspClass::setIramHeap()
|
||||||
|
{
|
||||||
|
#ifdef UMM_HEAP_IRAM
|
||||||
|
if (!umm_push_heap(UMM_HEAP_IRAM)) {
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspClass::setDramHeap()
|
||||||
|
{
|
||||||
|
#if defined(UMM_HEAP_EXTERNAL) && !defined(UMM_HEAP_IRAM)
|
||||||
|
if (!umm_push_heap(UMM_HEAP_DRAM)) {
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
#elif defined(UMM_HEAP_IRAM)
|
||||||
|
if (!umm_push_heap(UMM_HEAP_DRAM)) {
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspClass::resetHeap()
|
||||||
|
{
|
||||||
|
#if defined(UMM_HEAP_EXTERNAL) && !defined(UMM_HEAP_IRAM)
|
||||||
|
if (!umm_pop_heap()) {
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
#elif defined(UMM_HEAP_IRAM)
|
||||||
|
if (!umm_pop_heap()) {
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
@ -22,52 +22,8 @@
|
|||||||
#define ESP_H
|
#define ESP_H
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include "core_esp8266_features.h"
|
||||||
#ifndef PUYA_SUPPORT
|
#include "spi_vendors.h"
|
||||||
#define PUYA_SUPPORT 0
|
|
||||||
#endif
|
|
||||||
#ifndef PUYA_BUFFER_SIZE
|
|
||||||
// Good alternative for buffer size is: SPI_FLASH_SEC_SIZE (= 4k)
|
|
||||||
// Always use a multiple of flash page size (256 bytes)
|
|
||||||
#define PUYA_BUFFER_SIZE 256
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Vendor IDs taken from Flashrom project
|
|
||||||
// https://review.coreboot.org/cgit/flashrom.git/tree/flashchips.h?h=1.0.x
|
|
||||||
typedef enum {
|
|
||||||
SPI_FLASH_VENDOR_ALLIANCE = 0x52, /* Alliance Semiconductor */
|
|
||||||
SPI_FLASH_VENDOR_AMD = 0x01, /* AMD */
|
|
||||||
SPI_FLASH_VENDOR_AMIC = 0x37, /* AMIC */
|
|
||||||
SPI_FLASH_VENDOR_ATMEL = 0x1F, /* Atmel (now used by Adesto) */
|
|
||||||
SPI_FLASH_VENDOR_BRIGHT = 0xAD, /* Bright Microelectronics */
|
|
||||||
SPI_FLASH_VENDOR_CATALYST = 0x31, /* Catalyst */
|
|
||||||
SPI_FLASH_VENDOR_EON = 0x1C, /* EON Silicon Devices, missing 0x7F prefix */
|
|
||||||
SPI_FLASH_VENDOR_ESMT = 0x8C, /* Elite Semiconductor Memory Technology (ESMT) / EFST Elite Flash Storage */
|
|
||||||
SPI_FLASH_VENDOR_EXCEL = 0x4A, /* ESI, missing 0x7F prefix */
|
|
||||||
SPI_FLASH_VENDOR_FIDELIX = 0xF8, /* Fidelix */
|
|
||||||
SPI_FLASH_VENDOR_FUJITSU = 0x04, /* Fujitsu */
|
|
||||||
SPI_FLASH_VENDOR_GIGADEVICE = 0xC8, /* GigaDevice */
|
|
||||||
SPI_FLASH_VENDOR_HYUNDAI = 0xAD, /* Hyundai */
|
|
||||||
SPI_FLASH_VENDOR_INTEL = 0x89, /* Intel */
|
|
||||||
SPI_FLASH_VENDOR_ISSI = 0xD5, /* ISSI Integrated Silicon Solutions, see also PMC. */
|
|
||||||
SPI_FLASH_VENDOR_MACRONIX = 0xC2, /* Macronix (MX) */
|
|
||||||
SPI_FLASH_VENDOR_NANTRONICS = 0xD5, /* Nantronics, missing prefix */
|
|
||||||
SPI_FLASH_VENDOR_PMC = 0x9D, /* PMC, missing 0x7F prefix */
|
|
||||||
SPI_FLASH_VENDOR_PUYA = 0x85, /* Puya semiconductor (shanghai) co. ltd */
|
|
||||||
SPI_FLASH_VENDOR_SANYO = 0x62, /* Sanyo */
|
|
||||||
SPI_FLASH_VENDOR_SHARP = 0xB0, /* Sharp */
|
|
||||||
SPI_FLASH_VENDOR_SPANSION = 0x01, /* Spansion, same ID as AMD */
|
|
||||||
SPI_FLASH_VENDOR_SST = 0xBF, /* SST */
|
|
||||||
SPI_FLASH_VENDOR_ST = 0x20, /* ST / SGS/Thomson / Numonyx (later acquired by Micron) */
|
|
||||||
SPI_FLASH_VENDOR_SYNCMOS_MVC = 0x40, /* SyncMOS (SM) and Mosel Vitelic Corporation (MVC) */
|
|
||||||
SPI_FLASH_VENDOR_TENX = 0x5E, /* Tenx Technologies */
|
|
||||||
SPI_FLASH_VENDOR_TI = 0x97, /* Texas Instruments */
|
|
||||||
SPI_FLASH_VENDOR_TI_OLD = 0x01, /* TI chips from last century */
|
|
||||||
SPI_FLASH_VENDOR_WINBOND = 0xDA, /* Winbond */
|
|
||||||
SPI_FLASH_VENDOR_WINBOND_NEX = 0xEF, /* Winbond (ex Nexcom) serial flashes */
|
|
||||||
|
|
||||||
SPI_FLASH_VENDOR_UNKNOWN = 0xFF
|
|
||||||
} SPI_FLASH_VENDOR_t;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AVR macros for WDT managment
|
* AVR macros for WDT managment
|
||||||
@ -147,6 +103,12 @@ class EspClass {
|
|||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
void restart();
|
void restart();
|
||||||
|
/**
|
||||||
|
* @brief When calling this method the ESP8266 reboots into the UART download mode without
|
||||||
|
* the need of any external wiring. This is the same mode which can also be entered by
|
||||||
|
* pulling GPIO0=low, GPIO2=high, GPIO15=low and resetting the ESP8266.
|
||||||
|
*/
|
||||||
|
[[noreturn]] void rebootIntoUartDownloadMode();
|
||||||
|
|
||||||
uint16_t getVcc();
|
uint16_t getVcc();
|
||||||
uint32_t getChipId();
|
uint32_t getChipId();
|
||||||
@ -166,7 +128,13 @@ class EspClass {
|
|||||||
uint8_t getBootVersion();
|
uint8_t getBootVersion();
|
||||||
uint8_t getBootMode();
|
uint8_t getBootMode();
|
||||||
|
|
||||||
uint8_t getCpuFreqMHz();
|
#if defined(F_CPU) || defined(CORE_MOCK)
|
||||||
|
constexpr
|
||||||
|
#endif
|
||||||
|
inline uint8_t getCpuFreqMHz() const __attribute__((always_inline))
|
||||||
|
{
|
||||||
|
return esp_get_cpu_freq_mhz();
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t getFlashChipId();
|
uint32_t getFlashChipId();
|
||||||
uint8_t getFlashChipVendorId();
|
uint8_t getFlashChipVendorId();
|
||||||
@ -185,9 +153,51 @@ class EspClass {
|
|||||||
|
|
||||||
bool checkFlashConfig(bool needsEquals = false);
|
bool checkFlashConfig(bool needsEquals = false);
|
||||||
|
|
||||||
|
bool checkFlashCRC();
|
||||||
|
|
||||||
bool flashEraseSector(uint32_t sector);
|
bool flashEraseSector(uint32_t sector);
|
||||||
bool flashWrite(uint32_t offset, uint32_t *data, size_t size);
|
/**
|
||||||
bool flashRead(uint32_t offset, uint32_t *data, size_t size);
|
* @brief Write @a size bytes from @a data to flash at @a address
|
||||||
|
* This overload requires @a data and @a size to be always 4 byte aligned and
|
||||||
|
* @a address to be 4 byte aligned if the write crossess page boundary,
|
||||||
|
* but guarantees no overhead (except on PUYA flashes)
|
||||||
|
* @param address address on flash where write should start, 4 byte alignment is conditional
|
||||||
|
* @param data input buffer, must be 4-byte aligned
|
||||||
|
* @param size amount of data, must be a multiple of 4
|
||||||
|
* @return bool result of operation
|
||||||
|
* @retval true success
|
||||||
|
* @retval false failure to write to flash or incorrect alignment of params
|
||||||
|
*/
|
||||||
|
bool flashWrite(uint32_t address, const uint32_t *data, size_t size);
|
||||||
|
/**
|
||||||
|
* @brief Write @a size bytes from @a data to flash at @a address
|
||||||
|
* This overload handles all misalignment cases
|
||||||
|
* @param address address on flash where write should start
|
||||||
|
* @param data input buffer, passing unaligned memory will cause significant stack usage
|
||||||
|
* @param size amount of data, passing not multiple of 4 will cause additional reads and writes
|
||||||
|
* @return bool result of operation
|
||||||
|
*/
|
||||||
|
bool flashWrite(uint32_t address, const uint8_t *data, size_t size);
|
||||||
|
/**
|
||||||
|
* @brief Read @a size bytes to @a data to flash at @a address
|
||||||
|
* This overload requires @a data and @a size to be 4 byte aligned
|
||||||
|
* @param address address on flash where read should start
|
||||||
|
* @param data input buffer, must be 4-byte aligned
|
||||||
|
* @param size amount of data, must be a multiple of 4
|
||||||
|
* @return bool result of operation
|
||||||
|
* @retval true success
|
||||||
|
* @retval false failure to read from flash or incorrect alignment of params
|
||||||
|
*/
|
||||||
|
bool flashRead(uint32_t address, uint32_t *data, size_t size);
|
||||||
|
/**
|
||||||
|
* @brief Read @a size bytes to @a data to flash at @a address
|
||||||
|
* This overload handles all misalignment cases
|
||||||
|
* @param address address on flash where read should start
|
||||||
|
* @param data input buffer, passing unaligned memory will cause significant stack usage
|
||||||
|
* @param size amount of data, passing not multiple of 4 will cause additional read
|
||||||
|
* @return bool result of operation
|
||||||
|
*/
|
||||||
|
bool flashRead(uint32_t address, uint8_t *data, size_t size);
|
||||||
|
|
||||||
uint32_t getSketchSize();
|
uint32_t getSketchSize();
|
||||||
String getSketchMD5();
|
String getSketchMD5();
|
||||||
@ -200,20 +210,81 @@ class EspClass {
|
|||||||
|
|
||||||
bool eraseConfig();
|
bool eraseConfig();
|
||||||
|
|
||||||
#ifndef CORE_MOCK
|
uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes) const;
|
||||||
inline
|
uint32_t random() const;
|
||||||
#endif
|
|
||||||
uint32_t getCycleCount();
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifndef CORE_MOCK
|
#if !defined(CORE_MOCK)
|
||||||
uint32_t EspClass::getCycleCount()
|
inline uint32_t getCycleCount() __attribute__((always_inline))
|
||||||
{
|
{
|
||||||
uint32_t ccount;
|
return esp_get_cycle_count();
|
||||||
__asm__ __volatile__("esync; rsr %0,ccount":"=a" (ccount));
|
|
||||||
return ccount;
|
|
||||||
}
|
}
|
||||||
#endif
|
#else
|
||||||
|
uint32_t getCycleCount();
|
||||||
|
#endif // !defined(CORE_MOCK)
|
||||||
|
/**
|
||||||
|
* @brief Push current Heap selection and set Heap selection to DRAM.
|
||||||
|
*
|
||||||
|
* @param none
|
||||||
|
* @return none
|
||||||
|
*/
|
||||||
|
void setDramHeap();
|
||||||
|
/**
|
||||||
|
* @brief Push current Heap selection and set Heap selection to IRAM.
|
||||||
|
*
|
||||||
|
* @param none
|
||||||
|
* @return none
|
||||||
|
*/
|
||||||
|
void setIramHeap();
|
||||||
|
/**
|
||||||
|
* @brief Push current Heap selection and set Heap selection to External. (Experimental)
|
||||||
|
*
|
||||||
|
* @param none
|
||||||
|
* @return none
|
||||||
|
*/
|
||||||
|
void setExternalHeap();
|
||||||
|
/**
|
||||||
|
* @brief Restores Heap selection back to value present when
|
||||||
|
* setDramHeap, setIramHeap, or setExternalHeap was called.
|
||||||
|
*
|
||||||
|
* @param none
|
||||||
|
* @return none
|
||||||
|
*/
|
||||||
|
void resetHeap();
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Replaces @a byteCount bytes of a 4 byte block on flash
|
||||||
|
*
|
||||||
|
* @param address flash address
|
||||||
|
* @param value buffer with data
|
||||||
|
* @param byteCount number of bytes to replace
|
||||||
|
* @return bool result of operation
|
||||||
|
* @retval true success
|
||||||
|
* @retval false failed to read/write or invalid args
|
||||||
|
*/
|
||||||
|
bool flashReplaceBlock(uint32_t address, const uint8_t *value, uint32_t byteCount);
|
||||||
|
/**
|
||||||
|
* @brief Write up to @a size bytes from @a data to flash at @a address
|
||||||
|
* This function takes case of unaligned memory acces by copying @a data to a temporary buffer,
|
||||||
|
* it also takes care of page boundary crossing see @a flashWritePageBreak as to why it's done.
|
||||||
|
* Less than @a size bytes may be written, due to 4 byte alignment requirement of spi_flash_write
|
||||||
|
* @param address address on flash where write should start
|
||||||
|
* @param data input buffer
|
||||||
|
* @param size amount of data
|
||||||
|
* @return size_t amount of data written, 0 on failure
|
||||||
|
*/
|
||||||
|
size_t flashWriteUnalignedMemory(uint32_t address, const uint8_t *data, size_t size);
|
||||||
|
/**
|
||||||
|
* @brief Splits up to 4 bytes into 4 byte blocks and writes them to flash
|
||||||
|
* We need this since spi_flash_write cannot handle writing over a page boundary with unaligned offset
|
||||||
|
* i.e. spi_flash_write(254, data, 4) will fail, also we cannot write less bytes as in
|
||||||
|
* spi_flash_write(254, data, 2) since it will be extended internally to 4 bytes and fail
|
||||||
|
* @param address start of write
|
||||||
|
* @param data data to be written
|
||||||
|
* @param size amount of data, must be < 4
|
||||||
|
* @return bool result of operation
|
||||||
|
*/
|
||||||
|
bool flashWritePageBreak(uint32_t address, const uint8_t *data, size_t size);
|
||||||
|
};
|
||||||
|
|
||||||
extern EspClass ESP;
|
extern EspClass ESP;
|
||||||
|
|
||||||
|
@ -46,6 +46,14 @@ int File::available() {
|
|||||||
return _p->size() - _p->position();
|
return _p->size() - _p->position();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int File::availableForWrite() {
|
||||||
|
if (!_p)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _p->availableForWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int File::read() {
|
int File::read() {
|
||||||
if (!_p)
|
if (!_p)
|
||||||
return -1;
|
return -1;
|
||||||
@ -58,9 +66,9 @@ int File::read() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t File::read(uint8_t* buf, size_t size) {
|
int File::read(uint8_t* buf, size_t size) {
|
||||||
if (!_p)
|
if (!_p)
|
||||||
return -1;
|
return 0;
|
||||||
|
|
||||||
return _p->read(buf, size);
|
return _p->read(buf, size);
|
||||||
}
|
}
|
||||||
@ -180,6 +188,27 @@ String File::readString()
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time_t File::getLastWrite() {
|
||||||
|
if (!_p)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return _p->getLastWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t File::getCreationTime() {
|
||||||
|
if (!_p)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return _p->getCreationTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
void File::setTimeCallback(time_t (*cb)(void)) {
|
||||||
|
if (!_p)
|
||||||
|
return;
|
||||||
|
_p->setTimeCallback(cb);
|
||||||
|
_timeCallback = cb;
|
||||||
|
}
|
||||||
|
|
||||||
File Dir::openFile(const char* mode) {
|
File Dir::openFile(const char* mode) {
|
||||||
if (!_impl) {
|
if (!_impl) {
|
||||||
return File();
|
return File();
|
||||||
@ -192,7 +221,9 @@ File Dir::openFile(const char* mode) {
|
|||||||
return File();
|
return File();
|
||||||
}
|
}
|
||||||
|
|
||||||
return File(_impl->openFile(om, am), _baseFS);
|
File f(_impl->openFile(om, am), _baseFS);
|
||||||
|
f.setTimeCallback(_timeCallback);
|
||||||
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Dir::fileName() {
|
String Dir::fileName() {
|
||||||
@ -203,6 +234,18 @@ String Dir::fileName() {
|
|||||||
return _impl->fileName();
|
return _impl->fileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time_t Dir::fileTime() {
|
||||||
|
if (!_impl)
|
||||||
|
return 0;
|
||||||
|
return _impl->fileTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t Dir::fileCreationTime() {
|
||||||
|
if (!_impl)
|
||||||
|
return 0;
|
||||||
|
return _impl->fileCreationTime();
|
||||||
|
}
|
||||||
|
|
||||||
size_t Dir::fileSize() {
|
size_t Dir::fileSize() {
|
||||||
if (!_impl) {
|
if (!_impl) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -241,6 +284,14 @@ bool Dir::rewind() {
|
|||||||
return _impl->rewind();
|
return _impl->rewind();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Dir::setTimeCallback(time_t (*cb)(void)) {
|
||||||
|
if (!_impl)
|
||||||
|
return;
|
||||||
|
_impl->setTimeCallback(cb);
|
||||||
|
_timeCallback = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool FS::setConfig(const FSConfig &cfg) {
|
bool FS::setConfig(const FSConfig &cfg) {
|
||||||
if (!_impl) {
|
if (!_impl) {
|
||||||
return false;
|
return false;
|
||||||
@ -251,9 +302,13 @@ bool FS::setConfig(const FSConfig &cfg) {
|
|||||||
|
|
||||||
bool FS::begin() {
|
bool FS::begin() {
|
||||||
if (!_impl) {
|
if (!_impl) {
|
||||||
|
DEBUGV("#error: FS: no implementation");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return _impl->begin();
|
_impl->setTimeCallback(_timeCallback);
|
||||||
|
bool ret = _impl->begin();
|
||||||
|
DEBUGV("%s\n", ret? "": "#error: FS could not start");
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FS::end() {
|
void FS::end() {
|
||||||
@ -269,6 +324,13 @@ bool FS::gc() {
|
|||||||
return _impl->gc();
|
return _impl->gc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FS::check() {
|
||||||
|
if (!_impl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _impl->check();
|
||||||
|
}
|
||||||
|
|
||||||
bool FS::format() {
|
bool FS::format() {
|
||||||
if (!_impl) {
|
if (!_impl) {
|
||||||
return false;
|
return false;
|
||||||
@ -283,6 +345,13 @@ bool FS::info(FSInfo& info){
|
|||||||
return _impl->info(info);
|
return _impl->info(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FS::info64(FSInfo64& info){
|
||||||
|
if (!_impl) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _impl->info64(info);
|
||||||
|
}
|
||||||
|
|
||||||
File FS::open(const String& path, const char* mode) {
|
File FS::open(const String& path, const char* mode) {
|
||||||
return open(path.c_str(), mode);
|
return open(path.c_str(), mode);
|
||||||
}
|
}
|
||||||
@ -298,7 +367,9 @@ File FS::open(const char* path, const char* mode) {
|
|||||||
DEBUGV("FS::open: invalid mode `%s`\r\n", mode);
|
DEBUGV("FS::open: invalid mode `%s`\r\n", mode);
|
||||||
return File();
|
return File();
|
||||||
}
|
}
|
||||||
return File(_impl->open(path, om, am), this);
|
File f(_impl->open(path, om, am), this);
|
||||||
|
f.setTimeCallback(_timeCallback);
|
||||||
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FS::exists(const char* path) {
|
bool FS::exists(const char* path) {
|
||||||
@ -317,7 +388,9 @@ Dir FS::openDir(const char* path) {
|
|||||||
return Dir();
|
return Dir();
|
||||||
}
|
}
|
||||||
DirImplPtr p = _impl->openDir(path);
|
DirImplPtr p = _impl->openDir(path);
|
||||||
return Dir(p, this);
|
Dir d(p, this);
|
||||||
|
d.setTimeCallback(_timeCallback);
|
||||||
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dir FS::openDir(const String& path) {
|
Dir FS::openDir(const String& path) {
|
||||||
@ -368,6 +441,19 @@ bool FS::rename(const String& pathFrom, const String& pathTo) {
|
|||||||
return rename(pathFrom.c_str(), pathTo.c_str());
|
return rename(pathFrom.c_str(), pathTo.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time_t FS::getCreationTime() {
|
||||||
|
if (!_impl) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return _impl->getCreationTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FS::setTimeCallback(time_t (*cb)(void)) {
|
||||||
|
if (!_impl)
|
||||||
|
return;
|
||||||
|
_impl->setTimeCallback(cb);
|
||||||
|
_timeCallback = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool sflags(const char* mode, OpenMode& om, AccessMode& am) {
|
static bool sflags(const char* mode, OpenMode& om, AccessMode& am) {
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <../include/time.h> // See issue #6714
|
||||||
|
|
||||||
class SDClass;
|
class SDClass;
|
||||||
|
|
||||||
@ -56,6 +57,7 @@ public:
|
|||||||
// Print methods:
|
// Print methods:
|
||||||
size_t write(uint8_t) override;
|
size_t write(uint8_t) override;
|
||||||
size_t write(const uint8_t *buf, size_t size) override;
|
size_t write(const uint8_t *buf, size_t size) override;
|
||||||
|
int availableForWrite() override;
|
||||||
|
|
||||||
// Stream methods:
|
// Stream methods:
|
||||||
int available() override;
|
int available() override;
|
||||||
@ -65,13 +67,14 @@ public:
|
|||||||
size_t readBytes(char *buffer, size_t length) override {
|
size_t readBytes(char *buffer, size_t length) override {
|
||||||
return read((uint8_t*)buffer, length);
|
return read((uint8_t*)buffer, length);
|
||||||
}
|
}
|
||||||
size_t read(uint8_t* buf, size_t size);
|
int read(uint8_t* buf, size_t size) override;
|
||||||
bool seek(uint32_t pos, SeekMode mode);
|
bool seek(uint32_t pos, SeekMode mode);
|
||||||
bool seek(uint32_t pos) {
|
bool seek(uint32_t pos) {
|
||||||
return seek(pos, SeekSet);
|
return seek(pos, SeekSet);
|
||||||
}
|
}
|
||||||
size_t position() const;
|
size_t position() const;
|
||||||
size_t size() const;
|
size_t size() const;
|
||||||
|
virtual ssize_t streamRemaining() override { return (ssize_t)size() - (ssize_t)position(); }
|
||||||
void close();
|
void close();
|
||||||
operator bool() const;
|
operator bool() const;
|
||||||
const char* name() const;
|
const char* name() const;
|
||||||
@ -82,6 +85,7 @@ public:
|
|||||||
bool isDirectory() const;
|
bool isDirectory() const;
|
||||||
|
|
||||||
// Arduino "class SD" methods for compatibility
|
// Arduino "class SD" methods for compatibility
|
||||||
|
//TODO use stream::send / check read(buf,size) result
|
||||||
template<typename T> size_t write(T &src){
|
template<typename T> size_t write(T &src){
|
||||||
uint8_t obuf[256];
|
uint8_t obuf[256];
|
||||||
size_t doneLen = 0;
|
size_t doneLen = 0;
|
||||||
@ -110,8 +114,13 @@ public:
|
|||||||
|
|
||||||
String readString() override;
|
String readString() override;
|
||||||
|
|
||||||
|
time_t getLastWrite();
|
||||||
|
time_t getCreationTime();
|
||||||
|
void setTimeCallback(time_t (*cb)(void));
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FileImplPtr _p;
|
FileImplPtr _p;
|
||||||
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
|
|
||||||
// Arduino SD class emulation
|
// Arduino SD class emulation
|
||||||
std::shared_ptr<Dir> _fakeDir;
|
std::shared_ptr<Dir> _fakeDir;
|
||||||
@ -126,17 +135,23 @@ public:
|
|||||||
|
|
||||||
String fileName();
|
String fileName();
|
||||||
size_t fileSize();
|
size_t fileSize();
|
||||||
|
time_t fileTime();
|
||||||
|
time_t fileCreationTime();
|
||||||
bool isFile() const;
|
bool isFile() const;
|
||||||
bool isDirectory() const;
|
bool isDirectory() const;
|
||||||
|
|
||||||
bool next();
|
bool next();
|
||||||
bool rewind();
|
bool rewind();
|
||||||
|
|
||||||
|
void setTimeCallback(time_t (*cb)(void));
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
DirImplPtr _impl;
|
DirImplPtr _impl;
|
||||||
FS *_baseFS;
|
FS *_baseFS;
|
||||||
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Backwards compatible, <4GB filesystem usage
|
||||||
struct FSInfo {
|
struct FSInfo {
|
||||||
size_t totalBytes;
|
size_t totalBytes;
|
||||||
size_t usedBytes;
|
size_t usedBytes;
|
||||||
@ -146,15 +161,24 @@ struct FSInfo {
|
|||||||
size_t maxPathLength;
|
size_t maxPathLength;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Support > 4GB filesystems (SD, etc.)
|
||||||
|
struct FSInfo64 {
|
||||||
|
uint64_t totalBytes;
|
||||||
|
uint64_t usedBytes;
|
||||||
|
size_t blockSize;
|
||||||
|
size_t pageSize;
|
||||||
|
size_t maxOpenFiles;
|
||||||
|
size_t maxPathLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
class FSConfig
|
class FSConfig
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FSConfig(bool autoFormat = true) {
|
static constexpr uint32_t FSId = 0x00000000;
|
||||||
_type = FSConfig::fsid::FSId;
|
|
||||||
_autoFormat = autoFormat;
|
FSConfig(uint32_t type = FSId, bool autoFormat = true) : _type(type), _autoFormat(autoFormat) { }
|
||||||
}
|
|
||||||
|
|
||||||
enum fsid { FSId = 0x00000000 };
|
|
||||||
FSConfig setAutoFormat(bool val = true) {
|
FSConfig setAutoFormat(bool val = true) {
|
||||||
_autoFormat = val;
|
_autoFormat = val;
|
||||||
return *this;
|
return *this;
|
||||||
@ -167,17 +191,17 @@ public:
|
|||||||
class SPIFFSConfig : public FSConfig
|
class SPIFFSConfig : public FSConfig
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SPIFFSConfig(bool autoFormat = true) {
|
static constexpr uint32_t FSId = 0x53504946;
|
||||||
_type = SPIFFSConfig::fsid::FSId;
|
SPIFFSConfig(bool autoFormat = true) : FSConfig(FSId, autoFormat) { }
|
||||||
_autoFormat = autoFormat;
|
|
||||||
}
|
// Inherit _type and _autoFormat
|
||||||
enum fsid { FSId = 0x53504946 };
|
// nothing yet, enableTime TBD when SPIFFS has metadate
|
||||||
};
|
};
|
||||||
|
|
||||||
class FS
|
class FS
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FS(FSImplPtr impl) : _impl(impl) { }
|
FS(FSImplPtr impl) : _impl(impl) { _timeCallback = _defaultTimeCB; }
|
||||||
|
|
||||||
bool setConfig(const FSConfig &cfg);
|
bool setConfig(const FSConfig &cfg);
|
||||||
|
|
||||||
@ -186,6 +210,7 @@ public:
|
|||||||
|
|
||||||
bool format();
|
bool format();
|
||||||
bool info(FSInfo& info);
|
bool info(FSInfo& info);
|
||||||
|
bool info64(FSInfo64& info);
|
||||||
|
|
||||||
File open(const char* path, const char* mode);
|
File open(const char* path, const char* mode);
|
||||||
File open(const String& path, const char* mode);
|
File open(const String& path, const char* mode);
|
||||||
@ -208,16 +233,31 @@ public:
|
|||||||
bool rmdir(const char* path);
|
bool rmdir(const char* path);
|
||||||
bool rmdir(const String& path);
|
bool rmdir(const String& path);
|
||||||
|
|
||||||
|
// Low-level FS routines, not needed by most applications
|
||||||
bool gc();
|
bool gc();
|
||||||
|
bool check();
|
||||||
|
|
||||||
|
time_t getCreationTime();
|
||||||
|
|
||||||
|
void setTimeCallback(time_t (*cb)(void));
|
||||||
|
|
||||||
friend class ::SDClass; // More of a frenemy, but SD needs internal implementation to get private FAT bits
|
friend class ::SDClass; // More of a frenemy, but SD needs internal implementation to get private FAT bits
|
||||||
protected:
|
protected:
|
||||||
FSImplPtr _impl;
|
FSImplPtr _impl;
|
||||||
FSImplPtr getImpl() { return _impl; }
|
FSImplPtr getImpl() { return _impl; }
|
||||||
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
|
static time_t _defaultTimeCB(void) { return time(NULL); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace fs
|
} // namespace fs
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
void close_all_fs(void);
|
||||||
|
void littlefs_request_end(void);
|
||||||
|
void spiffs_request_end(void);
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef FS_NO_GLOBALS
|
#ifndef FS_NO_GLOBALS
|
||||||
using fs::FS;
|
using fs::FS;
|
||||||
using fs::File;
|
using fs::File;
|
||||||
@ -228,10 +268,11 @@ using fs::SeekCur;
|
|||||||
using fs::SeekEnd;
|
using fs::SeekEnd;
|
||||||
using fs::FSInfo;
|
using fs::FSInfo;
|
||||||
using fs::FSConfig;
|
using fs::FSConfig;
|
||||||
|
using fs::SPIFFSConfig;
|
||||||
#endif //FS_NO_GLOBALS
|
#endif //FS_NO_GLOBALS
|
||||||
|
|
||||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPIFFS)
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SPIFFS)
|
||||||
extern fs::FS SPIFFS;
|
extern fs::FS SPIFFS __attribute__((deprecated("SPIFFS has been deprecated. Please consider moving to LittleFS or other filesystems.")));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif //FS_H
|
#endif //FS_H
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
namespace fs {
|
namespace fs {
|
||||||
|
|
||||||
@ -29,17 +30,33 @@ class FileImpl {
|
|||||||
public:
|
public:
|
||||||
virtual ~FileImpl() { }
|
virtual ~FileImpl() { }
|
||||||
virtual size_t write(const uint8_t *buf, size_t size) = 0;
|
virtual size_t write(const uint8_t *buf, size_t size) = 0;
|
||||||
virtual size_t read(uint8_t* buf, size_t size) = 0;
|
virtual int read(uint8_t* buf, size_t size) = 0;
|
||||||
virtual void flush() = 0;
|
virtual void flush() = 0;
|
||||||
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
|
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
|
||||||
virtual size_t position() const = 0;
|
virtual size_t position() const = 0;
|
||||||
virtual size_t size() const = 0;
|
virtual size_t size() const = 0;
|
||||||
|
virtual int availableForWrite() { return 0; }
|
||||||
virtual bool truncate(uint32_t size) = 0;
|
virtual bool truncate(uint32_t size) = 0;
|
||||||
virtual void close() = 0;
|
virtual void close() = 0;
|
||||||
virtual const char* name() const = 0;
|
virtual const char* name() const = 0;
|
||||||
virtual const char* fullName() const = 0;
|
virtual const char* fullName() const = 0;
|
||||||
virtual bool isFile() const = 0;
|
virtual bool isFile() const = 0;
|
||||||
virtual bool isDirectory() const = 0;
|
virtual bool isDirectory() const = 0;
|
||||||
|
|
||||||
|
// Filesystems *may* support a timestamp per-file, so allow the user to override with
|
||||||
|
// their own callback for *this specific* file (as opposed to the FSImpl call of the
|
||||||
|
// same name. The default implementation simply returns time(null)
|
||||||
|
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
|
||||||
|
|
||||||
|
// Return the last written time for a file. Undefined when called on a writable file
|
||||||
|
// as the FS is allowed to return either the time of the last write() operation or the
|
||||||
|
// time present in the filesystem metadata (often the last time the file was closed)
|
||||||
|
virtual time_t getLastWrite() { return 0; } // Default is to not support timestamps
|
||||||
|
// Same for creation time.
|
||||||
|
virtual time_t getCreationTime() { return 0; } // Default is to not support timestamps
|
||||||
|
|
||||||
|
protected:
|
||||||
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum OpenMode {
|
enum OpenMode {
|
||||||
@ -61,10 +78,23 @@ public:
|
|||||||
virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0;
|
virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0;
|
||||||
virtual const char* fileName() = 0;
|
virtual const char* fileName() = 0;
|
||||||
virtual size_t fileSize() = 0;
|
virtual size_t fileSize() = 0;
|
||||||
|
// Return the last written time for a file. Undefined when called on a writable file
|
||||||
|
// as the FS is allowed to return either the time of the last write() operation or the
|
||||||
|
// time present in the filesystem metadata (often the last time the file was closed)
|
||||||
|
virtual time_t fileTime() { return 0; } // By default, FS doesn't report file times
|
||||||
|
virtual time_t fileCreationTime() { return 0; } // By default, FS doesn't report file times
|
||||||
virtual bool isFile() const = 0;
|
virtual bool isFile() const = 0;
|
||||||
virtual bool isDirectory() const = 0;
|
virtual bool isDirectory() const = 0;
|
||||||
virtual bool next() = 0;
|
virtual bool next() = 0;
|
||||||
virtual bool rewind() = 0;
|
virtual bool rewind() = 0;
|
||||||
|
|
||||||
|
// Filesystems *may* support a timestamp per-file, so allow the user to override with
|
||||||
|
// their own callback for *this specific* file (as opposed to the FSImpl call of the
|
||||||
|
// same name. The default implementation simply returns time(null)
|
||||||
|
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FSImpl {
|
class FSImpl {
|
||||||
@ -75,6 +105,7 @@ public:
|
|||||||
virtual void end() = 0;
|
virtual void end() = 0;
|
||||||
virtual bool format() = 0;
|
virtual bool format() = 0;
|
||||||
virtual bool info(FSInfo& info) = 0;
|
virtual bool info(FSInfo& info) = 0;
|
||||||
|
virtual bool info64(FSInfo64& info) = 0;
|
||||||
virtual FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) = 0;
|
virtual FileImplPtr open(const char* path, OpenMode openMode, AccessMode accessMode) = 0;
|
||||||
virtual bool exists(const char* path) = 0;
|
virtual bool exists(const char* path) = 0;
|
||||||
virtual DirImplPtr openDir(const char* path) = 0;
|
virtual DirImplPtr openDir(const char* path) = 0;
|
||||||
@ -83,6 +114,16 @@ public:
|
|||||||
virtual bool mkdir(const char* path) = 0;
|
virtual bool mkdir(const char* path) = 0;
|
||||||
virtual bool rmdir(const char* path) = 0;
|
virtual bool rmdir(const char* path) = 0;
|
||||||
virtual bool gc() { return true; } // May not be implemented in all file systems.
|
virtual bool gc() { return true; } // May not be implemented in all file systems.
|
||||||
|
virtual bool check() { return true; } // May not be implemented in all file systems.
|
||||||
|
virtual time_t getCreationTime() { return 0; } // May not be implemented in all file systems.
|
||||||
|
|
||||||
|
// Filesystems *may* support a timestamp per-file, so allow the user to override with
|
||||||
|
// their own callback for all files on this FS. The default implementation simply
|
||||||
|
// returns the present time as reported by time(null)
|
||||||
|
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace fs
|
} // namespace fs
|
||||||
|
33
cores/esp8266/FSnoop.cpp
Normal file
33
cores/esp8266/FSnoop.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* no-op implementations
|
||||||
|
* used/linked when no strong implementation already exists elsewhere
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
|
||||||
|
void close_all_fs(void)
|
||||||
|
{
|
||||||
|
littlefs_request_end();
|
||||||
|
spiffs_request_end();
|
||||||
|
}
|
||||||
|
|
||||||
|
// default weak definitions
|
||||||
|
// they are overriden in their respective real implementation
|
||||||
|
// hint: https://github.com/esp8266/Arduino/pull/6699#issuecomment-549085382
|
||||||
|
|
||||||
|
void littlefs_request_end(void) __attribute__((weak));
|
||||||
|
void littlefs_request_end(void)
|
||||||
|
{
|
||||||
|
//ets_printf("debug: noop: littlefs_request_end\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void spiffs_request_end(void) __attribute__((weak));
|
||||||
|
void spiffs_request_end(void)
|
||||||
|
{
|
||||||
|
//ets_printf("debug: noop: spiffs_request_end\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,23 +1,21 @@
|
|||||||
#include <FunctionalInterrupt.h>
|
#include <FunctionalInterrupt.h>
|
||||||
#include <Schedule.h>
|
#include <Schedule.h>
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include <ScheduledFunctions.h>
|
|
||||||
|
|
||||||
// Duplicate typedefs from core_esp8266_wiring_digital_c
|
// Duplicate typedefs from core_esp8266_wiring_digital_c
|
||||||
typedef void (*voidFuncPtr)(void);
|
typedef void (*voidFuncPtr)(void);
|
||||||
typedef void (*voidFuncPtrArg)(void*);
|
typedef void (*voidFuncPtrArg)(void*);
|
||||||
|
|
||||||
// Helper functions for Functional interrupt routines
|
// Helper functions for Functional interrupt routines
|
||||||
extern "C" void ICACHE_RAM_ATTR __attachInterruptArg(uint8_t pin, voidFuncPtr userFunc, void*fp , int mode);
|
extern "C" void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtr userFunc, void*fp, int mode, bool functional);
|
||||||
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR interruptFunctional(void* arg)
|
void IRAM_ATTR interruptFunctional(void* arg)
|
||||||
{
|
{
|
||||||
ArgStructure* localArg = (ArgStructure*)arg;
|
ArgStructure* localArg = (ArgStructure*)arg;
|
||||||
if (localArg->functionInfo->reqScheduledFunction)
|
if (localArg->functionInfo->reqScheduledFunction)
|
||||||
{
|
{
|
||||||
schedule_function(std::bind(localArg->functionInfo->reqScheduledFunction,InterruptInfo(*(localArg->interruptInfo))));
|
schedule_function(std::bind(localArg->functionInfo->reqScheduledFunction,InterruptInfo(*(localArg->interruptInfo))));
|
||||||
// scheduledInterrupts->scheduleFunctionReg(std::bind(localArg->functionInfo->reqScheduledFunction,InterruptInfo(*(localArg->interruptInfo))), false, true);
|
|
||||||
}
|
}
|
||||||
if (localArg->functionInfo->reqFunction)
|
if (localArg->functionInfo->reqFunction)
|
||||||
{
|
{
|
||||||
@ -49,15 +47,11 @@ void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode
|
|||||||
as->interruptInfo = ii;
|
as->interruptInfo = ii;
|
||||||
as->functionInfo = fi;
|
as->functionInfo = fi;
|
||||||
|
|
||||||
__attachInterruptArg (pin, (voidFuncPtr)interruptFunctional, as, mode);
|
__attachInterruptFunctionalArg(pin, (voidFuncPtr)interruptFunctional, as, mode, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> scheduledIntRoutine, int mode)
|
void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> scheduledIntRoutine, int mode)
|
||||||
{
|
{
|
||||||
if (!scheduledInterrupts)
|
|
||||||
{
|
|
||||||
scheduledInterrupts = new ScheduledFunctions(32);
|
|
||||||
}
|
|
||||||
InterruptInfo* ii = new InterruptInfo;
|
InterruptInfo* ii = new InterruptInfo;
|
||||||
|
|
||||||
FunctionInfo* fi = new FunctionInfo;
|
FunctionInfo* fi = new FunctionInfo;
|
||||||
@ -67,5 +61,5 @@ void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> sc
|
|||||||
as->interruptInfo = ii;
|
as->interruptInfo = ii;
|
||||||
as->functionInfo = fi;
|
as->functionInfo = fi;
|
||||||
|
|
||||||
__attachInterruptArg (pin, (voidFuncPtr)interruptFunctional, as, mode);
|
__attachInterruptFunctionalArg(pin, (voidFuncPtr)interruptFunctional, as, mode, true);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <ScheduledFunctions.h>
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "c_types.h"
|
#include "c_types.h"
|
||||||
@ -29,7 +28,6 @@ struct ArgStructure {
|
|||||||
FunctionInfo* functionInfo = nullptr;
|
FunctionInfo* functionInfo = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
static ScheduledFunctions* scheduledInterrupts;
|
|
||||||
void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode);
|
void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode);
|
||||||
void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> scheduledIntRoutine, int mode);
|
void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> scheduledIntRoutine, int mode);
|
||||||
|
|
||||||
|
@ -32,14 +32,22 @@
|
|||||||
#include "HardwareSerial.h"
|
#include "HardwareSerial.h"
|
||||||
#include "Esp.h"
|
#include "Esp.h"
|
||||||
|
|
||||||
|
|
||||||
|
// SerialEvent functions are weak, so when the user doesn't define them,
|
||||||
|
// the linker just sets their address to 0 (which is checked below).
|
||||||
|
// The Serialx_available is just a wrapper around Serialx.available(),
|
||||||
|
// but we can refer to it weakly so we don't pull in the entire
|
||||||
|
// HardwareSerial instance if the user doesn't also refer to it.
|
||||||
|
void serialEvent() __attribute__((weak));
|
||||||
|
|
||||||
HardwareSerial::HardwareSerial(int uart_nr)
|
HardwareSerial::HardwareSerial(int uart_nr)
|
||||||
: _uart_nr(uart_nr), _rx_size(256)
|
: _uart_nr(uart_nr), _rx_size(256)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void HardwareSerial::begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin)
|
void HardwareSerial::begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin, bool invert)
|
||||||
{
|
{
|
||||||
end();
|
end();
|
||||||
_uart = uart_init(_uart_nr, baud, (int) config, (int) mode, tx_pin, _rx_size);
|
_uart = uart_init(_uart_nr, baud, (int) config, (int) mode, tx_pin, _rx_size, invert);
|
||||||
#if defined(DEBUG_ESP_PORT) && !defined(NDEBUG)
|
#if defined(DEBUG_ESP_PORT) && !defined(NDEBUG)
|
||||||
if (static_cast<void*>(this) == static_cast<void*>(&DEBUG_ESP_PORT))
|
if (static_cast<void*>(this) == static_cast<void*>(&DEBUG_ESP_PORT))
|
||||||
{
|
{
|
||||||
@ -60,6 +68,15 @@ void HardwareSerial::end()
|
|||||||
_uart = NULL;
|
_uart = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::updateBaudRate(unsigned long baud)
|
||||||
|
{
|
||||||
|
if(!_uart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uart_set_baudrate(_uart, baud);
|
||||||
|
}
|
||||||
|
|
||||||
size_t HardwareSerial::setRxBufferSize(size_t size){
|
size_t HardwareSerial::setRxBufferSize(size_t size){
|
||||||
if(_uart) {
|
if(_uart) {
|
||||||
_rx_size = uart_resize_rx_buffer(_uart, size);
|
_rx_size = uart_resize_rx_buffer(_uart, size);
|
||||||
@ -99,14 +116,16 @@ int HardwareSerial::available(void)
|
|||||||
|
|
||||||
void HardwareSerial::flush()
|
void HardwareSerial::flush()
|
||||||
{
|
{
|
||||||
|
uint8_t bit_length = 0;
|
||||||
if(!_uart || !uart_tx_enabled(_uart)) {
|
if(!_uart || !uart_tx_enabled(_uart)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bit_length = uart_get_bit_length(_uart_nr); // data width, parity and stop
|
||||||
uart_wait_tx_empty(_uart);
|
uart_wait_tx_empty(_uart);
|
||||||
//Workaround for a bug in serial not actually being finished yet
|
//Workaround for a bug in serial not actually being finished yet
|
||||||
//Wait for 8 data bits, 1 parity and 2 stop bits, just in case
|
//Wait for 8 data bits, 1 parity and 2 stop bits, just in case
|
||||||
delayMicroseconds(11000000 / uart_get_baudrate(_uart) + 1);
|
delayMicroseconds(bit_length * 1000000 / uart_get_baudrate(_uart) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HardwareSerial::startDetectBaudrate()
|
void HardwareSerial::startDetectBaudrate()
|
||||||
@ -121,9 +140,9 @@ unsigned long HardwareSerial::testBaudrate()
|
|||||||
|
|
||||||
unsigned long HardwareSerial::detectBaudrate(time_t timeoutMillis)
|
unsigned long HardwareSerial::detectBaudrate(time_t timeoutMillis)
|
||||||
{
|
{
|
||||||
time_t startMillis = millis();
|
esp8266::polledTimeout::oneShotFastMs timeOut(timeoutMillis);
|
||||||
unsigned long detectedBaudrate;
|
unsigned long detectedBaudrate = 0;
|
||||||
while ((time_t) millis() - startMillis < timeoutMillis) {
|
while (!timeOut) {
|
||||||
if ((detectedBaudrate = testBaudrate())) {
|
if ((detectedBaudrate = testBaudrate())) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -151,6 +170,14 @@ size_t HardwareSerial::readBytes(char* buffer, size_t size)
|
|||||||
|
|
||||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
|
||||||
HardwareSerial Serial(UART0);
|
HardwareSerial Serial(UART0);
|
||||||
|
|
||||||
|
// Executed at end of loop() processing when > 0 bytes available in the Serial port
|
||||||
|
void serialEventRun(void)
|
||||||
|
{
|
||||||
|
if (serialEvent && Serial.available()) {
|
||||||
|
serialEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL1)
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL1)
|
||||||
HardwareSerial Serial1(UART1);
|
HardwareSerial Serial1(UART1);
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
#define HardwareSerial_h
|
#define HardwareSerial_h
|
||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <time.h>
|
#include <../include/time.h> // See issue #6714
|
||||||
#include "Stream.h"
|
#include "Stream.h"
|
||||||
#include "uart.h"
|
#include "uart.h"
|
||||||
|
|
||||||
@ -73,52 +73,59 @@ public:
|
|||||||
|
|
||||||
void begin(unsigned long baud)
|
void begin(unsigned long baud)
|
||||||
{
|
{
|
||||||
begin(baud, SERIAL_8N1, SERIAL_FULL, 1);
|
begin(baud, SERIAL_8N1, SERIAL_FULL, 1, false);
|
||||||
}
|
}
|
||||||
void begin(unsigned long baud, SerialConfig config)
|
void begin(unsigned long baud, SerialConfig config)
|
||||||
{
|
{
|
||||||
begin(baud, config, SERIAL_FULL, 1);
|
begin(baud, config, SERIAL_FULL, 1, false);
|
||||||
}
|
}
|
||||||
void begin(unsigned long baud, SerialConfig config, SerialMode mode)
|
void begin(unsigned long baud, SerialConfig config, SerialMode mode)
|
||||||
{
|
{
|
||||||
begin(baud, config, mode, 1);
|
begin(baud, config, mode, 1, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin);
|
void begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin)
|
||||||
|
{
|
||||||
|
begin(baud, config, mode, tx_pin, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin, bool invert);
|
||||||
|
|
||||||
void end();
|
void end();
|
||||||
|
|
||||||
|
void updateBaudRate(unsigned long baud);
|
||||||
|
|
||||||
size_t setRxBufferSize(size_t size);
|
size_t setRxBufferSize(size_t size);
|
||||||
size_t getRxBufferSize()
|
size_t getRxBufferSize()
|
||||||
{
|
{
|
||||||
return uart_get_rx_buffer_size(_uart);
|
return uart_get_rx_buffer_size(_uart);
|
||||||
}
|
}
|
||||||
|
|
||||||
void swap()
|
bool swap()
|
||||||
{
|
{
|
||||||
swap(1);
|
return swap(1);
|
||||||
}
|
}
|
||||||
void swap(uint8_t tx_pin) //toggle between use of GPIO13/GPIO15 or GPIO3/GPIO(1/2) as RX and TX
|
bool swap(uint8_t tx_pin) //toggle between use of GPIO13/GPIO15 or GPIO3/GPIO(1/2) as RX and TX
|
||||||
{
|
{
|
||||||
uart_swap(_uart, tx_pin);
|
return uart_swap(_uart, tx_pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Toggle between use of GPIO1 and GPIO2 as TX on UART 0.
|
* Toggle between use of GPIO1 and GPIO2 as TX on UART 0.
|
||||||
* Note: UART 1 can't be used if GPIO2 is used with UART 0!
|
* Note: UART 1 can't be used if GPIO2 is used with UART 0!
|
||||||
*/
|
*/
|
||||||
void set_tx(uint8_t tx_pin)
|
bool set_tx(uint8_t tx_pin)
|
||||||
{
|
{
|
||||||
uart_set_tx(_uart, tx_pin);
|
return uart_set_tx(_uart, tx_pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* UART 0 possible options are (1, 3), (2, 3) or (15, 13)
|
* UART 0 possible options are (1, 3), (2, 3) or (15, 13)
|
||||||
* UART 1 allows only TX on 2 if UART 0 is not (2, 3)
|
* UART 1 allows only TX on 2 if UART 0 is not (2, 3)
|
||||||
*/
|
*/
|
||||||
void pins(uint8_t tx, uint8_t rx)
|
bool pins(uint8_t tx, uint8_t rx)
|
||||||
{
|
{
|
||||||
uart_set_pins(_uart, tx, rx);
|
return uart_set_pins(_uart, tx, rx);
|
||||||
}
|
}
|
||||||
|
|
||||||
int available(void) override;
|
int available(void) override;
|
||||||
@ -128,22 +135,51 @@ public:
|
|||||||
// return -1 when data is unvailable (arduino api)
|
// return -1 when data is unvailable (arduino api)
|
||||||
return uart_peek_char(_uart);
|
return uart_peek_char(_uart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool hasPeekBufferAPI () const override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a pointer to available data buffer (size = available())
|
||||||
|
// semantic forbids any kind of read() before calling peekConsume()
|
||||||
|
const char* peekBuffer () override
|
||||||
|
{
|
||||||
|
return uart_peek_buffer(_uart);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return number of byte accessible by peekBuffer()
|
||||||
|
size_t peekAvailable () override
|
||||||
|
{
|
||||||
|
return uart_peek_available(_uart);
|
||||||
|
}
|
||||||
|
|
||||||
|
// consume bytes after use (see peekBuffer)
|
||||||
|
void peekConsume (size_t consume) override
|
||||||
|
{
|
||||||
|
return uart_peek_consume(_uart, consume);
|
||||||
|
}
|
||||||
|
|
||||||
int read(void) override
|
int read(void) override
|
||||||
{
|
{
|
||||||
// return -1 when data is unvailable (arduino api)
|
// return -1 when data is unvailable (arduino api)
|
||||||
return uart_read_char(_uart);
|
return uart_read_char(_uart);
|
||||||
}
|
}
|
||||||
// ::read(buffer, size): same as readBytes without timeout
|
// ::read(buffer, size): same as readBytes without timeout
|
||||||
size_t read(char* buffer, size_t size)
|
int read(char* buffer, size_t size)
|
||||||
{
|
{
|
||||||
return uart_read(_uart, buffer, size);
|
return uart_read(_uart, buffer, size);
|
||||||
}
|
}
|
||||||
|
int read(uint8_t* buffer, size_t size) override
|
||||||
|
{
|
||||||
|
return uart_read(_uart, (char*)buffer, size);
|
||||||
|
}
|
||||||
size_t readBytes(char* buffer, size_t size) override;
|
size_t readBytes(char* buffer, size_t size) override;
|
||||||
size_t readBytes(uint8_t* buffer, size_t size) override
|
size_t readBytes(uint8_t* buffer, size_t size) override
|
||||||
{
|
{
|
||||||
return readBytes((char*)buffer, size);
|
return readBytes((char*)buffer, size);
|
||||||
}
|
}
|
||||||
int availableForWrite(void)
|
int availableForWrite(void) override
|
||||||
{
|
{
|
||||||
return static_cast<int>(uart_tx_free(_uart));
|
return static_cast<int>(uart_tx_free(_uart));
|
||||||
}
|
}
|
||||||
@ -200,4 +236,6 @@ protected:
|
|||||||
extern HardwareSerial Serial;
|
extern HardwareSerial Serial;
|
||||||
extern HardwareSerial Serial1;
|
extern HardwareSerial Serial1;
|
||||||
|
|
||||||
|
extern void serialEventRun(void) __attribute__((weak));
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -32,7 +32,7 @@ IPAddress::IPAddress() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IPAddress::isSet () const {
|
bool IPAddress::isSet () const {
|
||||||
return !ip_addr_isany(&_ip);
|
return !ip_addr_isany(&_ip) && ((*this) != IPADDR_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) {
|
IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) {
|
||||||
@ -180,9 +180,13 @@ bool IPAddress::isValid(const char* arg) {
|
|||||||
return IPAddress().fromString(arg);
|
return IPAddress().fromString(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
CONST IPAddress INADDR_ANY; // generic "0.0.0.0" for IPv4 & IPv6
|
const IPAddress INADDR_ANY; // generic "0.0.0.0" for IPv4 & IPv6
|
||||||
const IPAddress INADDR_NONE(255,255,255,255);
|
const IPAddress INADDR_NONE(255,255,255,255);
|
||||||
|
|
||||||
|
void IPAddress::clear() {
|
||||||
|
(*this) = INADDR_ANY;
|
||||||
|
}
|
||||||
|
|
||||||
/**************************************/
|
/**************************************/
|
||||||
|
|
||||||
#if LWIP_IPV6
|
#if LWIP_IPV6
|
||||||
|
@ -26,27 +26,18 @@
|
|||||||
|
|
||||||
#include <lwip/init.h>
|
#include <lwip/init.h>
|
||||||
#include <lwip/ip_addr.h>
|
#include <lwip/ip_addr.h>
|
||||||
|
#include <lwip/ip4_addr.h>
|
||||||
|
|
||||||
#if LWIP_VERSION_MAJOR == 1
|
|
||||||
// compatibility macros to make lwIP-v1 compiling lwIP-v2 API
|
|
||||||
#define LWIP_IPV6_NUM_ADDRESSES 0
|
|
||||||
#define ip_2_ip4(x) (x)
|
|
||||||
#define ipv4_addr ip_addr
|
|
||||||
#define IP_IS_V4_VAL(x) (1)
|
|
||||||
#define IP_SET_TYPE_VAL(x,y) do { (void)0; } while (0)
|
|
||||||
#define IP_ANY_TYPE (&ip_addr_any)
|
|
||||||
#define IP4_ADDR_ANY IPADDR_ANY
|
|
||||||
#define IP4_ADDR_ANY4 IPADDR_ANY
|
|
||||||
#define IPADDR4_INIT(x) { x }
|
|
||||||
#define CONST /* nothing: lwIP-v1 does not use const */
|
|
||||||
#define ip4_addr_netcmp ip_addr_netcmp
|
|
||||||
#define netif_dhcp_data(netif) ((netif)->dhcp)
|
|
||||||
#else // lwIP-v2+
|
|
||||||
#define CONST const
|
|
||||||
#if !LWIP_IPV6
|
#if !LWIP_IPV6
|
||||||
struct ip_addr: ipv4_addr { };
|
struct ip_addr: ipv4_addr { };
|
||||||
#endif // !LWIP_IPV6
|
#endif // !LWIP_IPV6
|
||||||
#endif // lwIP-v2+
|
|
||||||
|
// to display a netif id with printf:
|
||||||
|
#define NETIFID_STR "%c%c%u"
|
||||||
|
#define NETIFID_VAL(netif) \
|
||||||
|
((netif)? (netif)->name[0]: '-'), \
|
||||||
|
((netif)? (netif)->name[1]: '-'), \
|
||||||
|
((netif)? netif_get_index(netif): 42)
|
||||||
|
|
||||||
// A class to make it easier to handle and pass around IP addresses
|
// A class to make it easier to handle and pass around IP addresses
|
||||||
// IPv6 update:
|
// IPv6 update:
|
||||||
@ -136,10 +127,13 @@ class IPAddress: public Printable {
|
|||||||
// Overloaded copy operators to allow initialisation of IPAddress objects from other types
|
// Overloaded copy operators to allow initialisation of IPAddress objects from other types
|
||||||
IPAddress& operator=(const uint8_t *address);
|
IPAddress& operator=(const uint8_t *address);
|
||||||
IPAddress& operator=(uint32_t address);
|
IPAddress& operator=(uint32_t address);
|
||||||
|
IPAddress& operator=(const IPAddress&) = default;
|
||||||
|
|
||||||
virtual size_t printTo(Print& p) const;
|
virtual size_t printTo(Print& p) const;
|
||||||
String toString() const;
|
String toString() const;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
check if input string(arg) is a valid IPV4 address or not.
|
check if input string(arg) is a valid IPV4 address or not.
|
||||||
return true on valid.
|
return true on valid.
|
||||||
@ -218,7 +212,7 @@ class IPAddress: public Printable {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern CONST IPAddress INADDR_ANY;
|
extern const IPAddress INADDR_ANY;
|
||||||
extern const IPAddress INADDR_NONE;
|
extern const IPAddress INADDR_NONE;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
64
cores/esp8266/LwipDhcpServer-NonOS.cpp
Normal file
64
cores/esp8266/LwipDhcpServer-NonOS.cpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
lwIPDhcpServer-NonOS.cpp - DHCP server wrapper
|
||||||
|
|
||||||
|
Copyright (c) 2020 esp8266 arduino. 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
// STARTS/STOPS DHCP SERVER ON WIFI AP INTERFACE
|
||||||
|
// these functions must exists as-is with "C" interface,
|
||||||
|
// nonos-sdk calls them at boot time and later
|
||||||
|
|
||||||
|
#include <lwip/init.h> // LWIP_VERSION
|
||||||
|
|
||||||
|
#include <lwip/netif.h>
|
||||||
|
#include "LwipDhcpServer.h"
|
||||||
|
|
||||||
|
extern netif netif_git[2];
|
||||||
|
|
||||||
|
// global DHCP instance for softAP interface
|
||||||
|
DhcpServer dhcpSoftAP(&netif_git[SOFTAP_IF]);
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
|
||||||
|
void dhcps_start(struct ip_info *info, netif* apnetif)
|
||||||
|
{
|
||||||
|
// apnetif is esp interface, replaced by lwip2's
|
||||||
|
// netif_git[SOFTAP_IF] interface in constructor
|
||||||
|
(void)apnetif;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// can't use C++ now, global ctors are not initialized yet
|
||||||
|
dhcpSoftAP.begin(info);
|
||||||
|
#else
|
||||||
|
(void)info;
|
||||||
|
// initial version: emulate nonos-sdk in DhcpServer class before
|
||||||
|
// trying to change legacy behavor
|
||||||
|
// `fw_has_started_softap_dhcps` will be read in DhcpServer::DhcpServer
|
||||||
|
// which is called when c++ ctors are initialized, specifically
|
||||||
|
// dhcpSoftAP intialized with AP interface number above.
|
||||||
|
fw_has_started_softap_dhcps = 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void dhcps_stop()
|
||||||
|
{
|
||||||
|
dhcpSoftAP.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
1606
cores/esp8266/LwipDhcpServer.cpp
Normal file
1606
cores/esp8266/LwipDhcpServer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
124
cores/esp8266/LwipDhcpServer.h
Normal file
124
cores/esp8266/LwipDhcpServer.h
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
lwIPDhcpServer.h - DHCP server
|
||||||
|
|
||||||
|
Copyright (c) 2016 Espressif. All rights reserved.
|
||||||
|
Copyright (c) 2020 esp8266 arduino. 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
|
||||||
|
|
||||||
|
// original sources (no license provided)
|
||||||
|
// ESP8266_NONOS_SDK/third_party/lwip/app/dhcpserver.c
|
||||||
|
// ESP8266_NONOS_SDK/third_party/include/lwip/app/dhcpserver.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
// lwIPDhcpServer.{cc,h} encapsulate original nonos-sdk dhcp server
|
||||||
|
// nearly as-is. This is an initial version to guaranty legacy behavior
|
||||||
|
// with same default values.
|
||||||
|
|
||||||
|
#ifndef __DHCPS_H__
|
||||||
|
#define __DHCPS_H__
|
||||||
|
|
||||||
|
#include <lwip/init.h> // LWIP_VERSION
|
||||||
|
|
||||||
|
class DhcpServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
DhcpServer(netif* netif);
|
||||||
|
~DhcpServer();
|
||||||
|
|
||||||
|
void setDns(int num, const ipv4_addr_t* dns);
|
||||||
|
|
||||||
|
bool begin(ip_info* info);
|
||||||
|
void end();
|
||||||
|
bool isRunning();
|
||||||
|
|
||||||
|
// this is the C interface encapsulated in a class
|
||||||
|
// (originally dhcpserver.c in lwIP-v1.4 in NonOS-SDK)
|
||||||
|
// (not changing everything at once)
|
||||||
|
// the API below is subject to change
|
||||||
|
|
||||||
|
// legacy public C structure and API to eventually turn into C++
|
||||||
|
|
||||||
|
void init_dhcps_lease(uint32 ip);
|
||||||
|
bool set_dhcps_lease(struct dhcps_lease *please);
|
||||||
|
bool get_dhcps_lease(struct dhcps_lease *please);
|
||||||
|
bool set_dhcps_offer_option(uint8 level, void* optarg);
|
||||||
|
bool set_dhcps_lease_time(uint32 minute);
|
||||||
|
bool reset_dhcps_lease_time(void);
|
||||||
|
uint32 get_dhcps_lease_time(void);
|
||||||
|
bool add_dhcps_lease(uint8 *macaddr);
|
||||||
|
|
||||||
|
void dhcps_set_dns(int num, const ipv4_addr_t* dns);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// legacy C structure and API to eventually turn into C++
|
||||||
|
|
||||||
|
typedef struct _list_node
|
||||||
|
{
|
||||||
|
void *pnode;
|
||||||
|
struct _list_node *pnext;
|
||||||
|
} list_node;
|
||||||
|
|
||||||
|
void node_insert_to_list(list_node **phead, list_node* pinsert);
|
||||||
|
void node_remove_from_list(list_node **phead, list_node* pdelete);
|
||||||
|
uint8_t* add_msg_type(uint8_t *optptr, uint8_t type);
|
||||||
|
uint8_t* add_offer_options(uint8_t *optptr);
|
||||||
|
uint8_t* add_end(uint8_t *optptr);
|
||||||
|
void create_msg(struct dhcps_msg *m);
|
||||||
|
void send_offer(struct dhcps_msg *m);
|
||||||
|
void send_nak(struct dhcps_msg *m);
|
||||||
|
void send_ack(struct dhcps_msg *m);
|
||||||
|
uint8_t parse_options(uint8_t *optptr, sint16_t len);
|
||||||
|
sint16_t parse_msg(struct dhcps_msg *m, u16_t len);
|
||||||
|
static void S_handle_dhcp(void *arg,
|
||||||
|
struct udp_pcb *pcb,
|
||||||
|
struct pbuf *p,
|
||||||
|
const ip_addr_t *addr,
|
||||||
|
uint16_t port);
|
||||||
|
void handle_dhcp(
|
||||||
|
struct udp_pcb *pcb,
|
||||||
|
struct pbuf *p,
|
||||||
|
const ip_addr_t *addr,
|
||||||
|
uint16_t port);
|
||||||
|
void kill_oldest_dhcps_pool(void);
|
||||||
|
void dhcps_coarse_tmr(void); // CURRENTLY NOT CALLED
|
||||||
|
void dhcps_client_leave(u8 *bssid, struct ipv4_addr *ip, bool force);
|
||||||
|
uint32 dhcps_client_update(u8 *bssid, struct ipv4_addr *ip);
|
||||||
|
|
||||||
|
netif* _netif;
|
||||||
|
|
||||||
|
struct udp_pcb *pcb_dhcps;
|
||||||
|
ip_addr_t broadcast_dhcps;
|
||||||
|
struct ipv4_addr server_address;
|
||||||
|
struct ipv4_addr client_address;
|
||||||
|
struct ipv4_addr dns_address;
|
||||||
|
uint32 dhcps_lease_time;
|
||||||
|
|
||||||
|
struct dhcps_lease dhcps_lease;
|
||||||
|
list_node *plist;
|
||||||
|
uint8 offer;
|
||||||
|
bool renew;
|
||||||
|
|
||||||
|
static const uint32 magic_cookie;
|
||||||
|
};
|
||||||
|
|
||||||
|
// SoftAP DHCP server always exists and is started on boot
|
||||||
|
extern DhcpServer dhcpSoftAP;
|
||||||
|
extern "C" int fw_has_started_softap_dhcps;
|
||||||
|
|
||||||
|
#endif // __DHCPS_H__
|
165
cores/esp8266/LwipIntf.cpp
Normal file
165
cores/esp8266/LwipIntf.cpp
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "lwip/err.h"
|
||||||
|
#include "lwip/ip_addr.h"
|
||||||
|
#include "lwip/dns.h"
|
||||||
|
#include "lwip/dhcp.h"
|
||||||
|
#include "lwip/init.h" // LWIP_VERSION_
|
||||||
|
#if LWIP_IPV6
|
||||||
|
#include "lwip/netif.h" // struct netif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <user_interface.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
#include "LwipIntf.h"
|
||||||
|
|
||||||
|
// args | esp order arduino order
|
||||||
|
// ---- + --------- -------------
|
||||||
|
// local_ip | local_ip local_ip
|
||||||
|
// arg1 | gateway dns1
|
||||||
|
// arg2 | netmask gateway
|
||||||
|
// arg3 | dns1 netmask
|
||||||
|
//
|
||||||
|
// result stored into gateway/netmask/dns1
|
||||||
|
|
||||||
|
bool LwipIntf::ipAddressReorder(const IPAddress& local_ip, const IPAddress& arg1, const IPAddress& arg2, const IPAddress& arg3,
|
||||||
|
IPAddress& gateway, IPAddress& netmask, IPAddress& dns1)
|
||||||
|
{
|
||||||
|
//To allow compatibility, check first octet of 3rd arg. If 255, interpret as ESP order, otherwise Arduino order.
|
||||||
|
gateway = arg1;
|
||||||
|
netmask = arg2;
|
||||||
|
dns1 = arg3;
|
||||||
|
|
||||||
|
if (netmask[0] != 255)
|
||||||
|
{
|
||||||
|
//octet is not 255 => interpret as Arduino order
|
||||||
|
gateway = arg2;
|
||||||
|
netmask = arg3[0] == 0 ? IPAddress(255, 255, 255, 0) : arg3; //arg order is arduino and 4th arg not given => assign it arduino default
|
||||||
|
dns1 = arg1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check whether all is IPv4 (or gateway not set)
|
||||||
|
if (!(local_ip.isV4() && netmask.isV4() && (!gateway.isSet() || gateway.isV4())))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ip and gateway must be in the same netmask
|
||||||
|
if (gateway.isSet() && (local_ip.v4() & netmask.v4()) != (gateway.v4() & netmask.v4()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get ESP8266 station DHCP hostname
|
||||||
|
@return hostname
|
||||||
|
*/
|
||||||
|
String LwipIntf::hostname(void)
|
||||||
|
{
|
||||||
|
return wifi_station_get_hostname();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get ESP8266 station DHCP hostname
|
||||||
|
@return hostname
|
||||||
|
*/
|
||||||
|
const char* LwipIntf::getHostname(void)
|
||||||
|
{
|
||||||
|
return wifi_station_get_hostname();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set ESP8266 station DHCP hostname
|
||||||
|
@param aHostname max length:24
|
||||||
|
@return ok
|
||||||
|
*/
|
||||||
|
bool LwipIntf::hostname(const char* aHostname)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
vvvv RFC952 vvvv
|
||||||
|
ASSUMPTIONS
|
||||||
|
1. A "name" (Net, Host, Gateway, or Domain name) is a text string up
|
||||||
|
to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus
|
||||||
|
sign (-), and period (.). Note that periods are only allowed when
|
||||||
|
they serve to delimit components of "domain style names". (See
|
||||||
|
RFC-921, "Domain Name System Implementation Schedule", for
|
||||||
|
background). No blank or space characters are permitted as part of a
|
||||||
|
name. No distinction is made between upper and lower case. The first
|
||||||
|
character must be an alpha character. The last character must not be
|
||||||
|
a minus sign or period. A host which serves as a GATEWAY should have
|
||||||
|
"-GATEWAY" or "-GW" as part of its name. Hosts which do not serve as
|
||||||
|
Internet gateways should not use "-GATEWAY" and "-GW" as part of
|
||||||
|
their names. A host which is a TAC should have "-TAC" as the last
|
||||||
|
part of its host name, if it is a DoD host. Single character names
|
||||||
|
or nicknames are not allowed.
|
||||||
|
^^^^ RFC952 ^^^^
|
||||||
|
|
||||||
|
- 24 chars max
|
||||||
|
- only a..z A..Z 0..9 '-'
|
||||||
|
- no '-' as last char
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t len = strlen(aHostname);
|
||||||
|
|
||||||
|
if (len == 0 || len > 32)
|
||||||
|
{
|
||||||
|
// nonos-sdk limit is 32
|
||||||
|
// (dhcp hostname option minimum size is ~60)
|
||||||
|
DEBUGV("WiFi.(set)hostname(): empty or large(>32) name\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check RFC compliance
|
||||||
|
bool compliant = (len <= 24);
|
||||||
|
for (size_t i = 0; compliant && i < len; i++)
|
||||||
|
if (!isalnum(aHostname[i]) && aHostname[i] != '-')
|
||||||
|
{
|
||||||
|
compliant = false;
|
||||||
|
}
|
||||||
|
if (aHostname[len - 1] == '-')
|
||||||
|
{
|
||||||
|
compliant = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!compliant)
|
||||||
|
{
|
||||||
|
DEBUGV("hostname '%s' is not compliant with RFC952\n", aHostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = wifi_station_set_hostname(aHostname);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
DEBUGV("WiFi.hostname(%s): wifi_station_set_hostname() failed\n", aHostname);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we should inform dhcp server for this change, using lwip_renew()
|
||||||
|
// looping through all existing interface
|
||||||
|
// harmless for AP, also compatible with ethernet adapters (to come)
|
||||||
|
for (netif* intf = netif_list; intf; intf = intf->next)
|
||||||
|
{
|
||||||
|
|
||||||
|
// unconditionally update all known interfaces
|
||||||
|
intf->hostname = wifi_station_get_hostname();
|
||||||
|
|
||||||
|
if (netif_dhcp_data(intf) != nullptr)
|
||||||
|
{
|
||||||
|
// renew already started DHCP leases
|
||||||
|
err_t lwipret = dhcp_renew(intf);
|
||||||
|
if (lwipret != ERR_OK)
|
||||||
|
{
|
||||||
|
DEBUGV("WiFi.hostname(%s): lwIP error %d on interface %c%c (index %d)\n",
|
||||||
|
intf->hostname, (int)lwipret, intf->name[0], intf->name[1], intf->num);
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret && compliant;
|
||||||
|
}
|
||||||
|
|
49
cores/esp8266/LwipIntf.h
Normal file
49
cores/esp8266/LwipIntf.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
#ifndef _LWIPINTF_H
|
||||||
|
#define _LWIPINTF_H
|
||||||
|
|
||||||
|
#include <lwip/netif.h>
|
||||||
|
#include <IPAddress.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class LwipIntf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using CBType = std::function <void(netif*)>;
|
||||||
|
|
||||||
|
static bool stateUpCB(LwipIntf::CBType&& cb);
|
||||||
|
|
||||||
|
// reorder WiFi.config() parameters for a esp8266/official Arduino dual-compatibility API
|
||||||
|
// args | esp order arduino order
|
||||||
|
// ---- + --------- -------------
|
||||||
|
// local_ip | local_ip local_ip
|
||||||
|
// arg1 | gateway dns1
|
||||||
|
// arg2 | netmask [Agateway
|
||||||
|
// arg3 | dns1 netmask
|
||||||
|
//
|
||||||
|
// result stored into gateway/netmask/dns1
|
||||||
|
static
|
||||||
|
bool ipAddressReorder(const IPAddress& local_ip, const IPAddress& arg1, const IPAddress& arg2, const IPAddress& arg3,
|
||||||
|
IPAddress& gateway, IPAddress& netmask, IPAddress& dns1);
|
||||||
|
|
||||||
|
String hostname();
|
||||||
|
bool hostname(const String& aHostname)
|
||||||
|
{
|
||||||
|
return hostname(aHostname.c_str());
|
||||||
|
}
|
||||||
|
bool hostname(const char* aHostname);
|
||||||
|
// ESP32 API compatibility
|
||||||
|
bool setHostname(const char* aHostName)
|
||||||
|
{
|
||||||
|
return hostname(aHostName);
|
||||||
|
}
|
||||||
|
const char* getHostname();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
static bool stateChangeSysCB(LwipIntf::CBType&& cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _LWIPINTF_H
|
44
cores/esp8266/LwipIntfCB.cpp
Normal file
44
cores/esp8266/LwipIntfCB.cpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
#include <LwipIntf.h>
|
||||||
|
#include <Schedule.h>
|
||||||
|
#include <debug.h>
|
||||||
|
|
||||||
|
#define NETIF_STATUS_CB_SIZE 3
|
||||||
|
|
||||||
|
static int netifStatusChangeListLength = 0;
|
||||||
|
LwipIntf::CBType netifStatusChangeList [NETIF_STATUS_CB_SIZE];
|
||||||
|
|
||||||
|
extern "C" void netif_status_changed(struct netif* netif)
|
||||||
|
{
|
||||||
|
// override the default empty weak function
|
||||||
|
for (int i = 0; i < netifStatusChangeListLength; i++)
|
||||||
|
{
|
||||||
|
netifStatusChangeList[i](netif);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LwipIntf::stateChangeSysCB(LwipIntf::CBType&& cb)
|
||||||
|
{
|
||||||
|
if (netifStatusChangeListLength >= NETIF_STATUS_CB_SIZE)
|
||||||
|
{
|
||||||
|
#if defined(DEBUG_ESP_CORE)
|
||||||
|
DEBUGV("NETIF_STATUS_CB_SIZE is too low\n");
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
netifStatusChangeList[netifStatusChangeListLength++] = cb;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LwipIntf::stateUpCB(LwipIntf::CBType&& cb)
|
||||||
|
{
|
||||||
|
return stateChangeSysCB([cb](netif * nif)
|
||||||
|
{
|
||||||
|
if (netif_is_up(nif))
|
||||||
|
schedule_function([cb, nif]()
|
||||||
|
{
|
||||||
|
cb(nif);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
386
cores/esp8266/LwipIntfDev.h
Normal file
386
cores/esp8266/LwipIntfDev.h
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
|
||||||
|
#ifndef _LWIPINTFDEV_H
|
||||||
|
#define _LWIPINTFDEV_H
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// remove all Serial.print
|
||||||
|
// unchain pbufs
|
||||||
|
|
||||||
|
#include <netif/ethernet.h>
|
||||||
|
#include <lwip/init.h>
|
||||||
|
#include <lwip/netif.h>
|
||||||
|
#include <lwip/etharp.h>
|
||||||
|
#include <lwip/dhcp.h>
|
||||||
|
#include <lwip/apps/sntp.h>
|
||||||
|
|
||||||
|
#include <user_interface.h> // wifi_get_macaddr()
|
||||||
|
|
||||||
|
#include "SPI.h"
|
||||||
|
#include "Schedule.h"
|
||||||
|
#include "LwipIntf.h"
|
||||||
|
#include "wl_definitions.h"
|
||||||
|
|
||||||
|
#ifndef DEFAULT_MTU
|
||||||
|
#define DEFAULT_MTU 1500
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
class LwipIntfDev: public LwipIntf, public RawDev
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
LwipIntfDev(int8_t cs = SS, SPIClass& spi = SPI, int8_t intr = -1):
|
||||||
|
RawDev(cs, spi, intr),
|
||||||
|
_mtu(DEFAULT_MTU),
|
||||||
|
_intrPin(intr),
|
||||||
|
_started(false),
|
||||||
|
_default(false)
|
||||||
|
{
|
||||||
|
memset(&_netif, 0, sizeof(_netif));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean config(const IPAddress& local_ip, const IPAddress& arg1, const IPAddress& arg2, const IPAddress& arg3, const IPAddress& dns2);
|
||||||
|
|
||||||
|
// default mac-address is inferred from esp8266's STA interface
|
||||||
|
boolean begin(const uint8_t *macAddress = nullptr, const uint16_t mtu = DEFAULT_MTU);
|
||||||
|
|
||||||
|
const netif* getNetIf() const
|
||||||
|
{
|
||||||
|
return &_netif;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress localIP() const
|
||||||
|
{
|
||||||
|
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.ip_addr)));
|
||||||
|
}
|
||||||
|
IPAddress subnetMask() const
|
||||||
|
{
|
||||||
|
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.netmask)));
|
||||||
|
}
|
||||||
|
IPAddress gatewayIP() const
|
||||||
|
{
|
||||||
|
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.gw)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDefault();
|
||||||
|
|
||||||
|
bool connected()
|
||||||
|
{
|
||||||
|
return !!ip4_addr_get_u32(ip_2_ip4(&_netif.ip_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESP8266WiFi API compatibility
|
||||||
|
|
||||||
|
wl_status_t status();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
err_t netif_init();
|
||||||
|
void netif_status_callback();
|
||||||
|
|
||||||
|
static err_t netif_init_s(netif* netif);
|
||||||
|
static err_t linkoutput_s(netif *netif, struct pbuf *p);
|
||||||
|
static void netif_status_callback_s(netif* netif);
|
||||||
|
|
||||||
|
// called on a regular basis or on interrupt
|
||||||
|
err_t handlePackets();
|
||||||
|
|
||||||
|
// members
|
||||||
|
|
||||||
|
netif _netif;
|
||||||
|
|
||||||
|
uint16_t _mtu;
|
||||||
|
int8_t _intrPin;
|
||||||
|
uint8_t _macAddress[6];
|
||||||
|
bool _started;
|
||||||
|
bool _default;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
boolean LwipIntfDev<RawDev>::config(const IPAddress& localIP, const IPAddress& gateway, const IPAddress& netmask, const IPAddress& dns1, const IPAddress& dns2)
|
||||||
|
{
|
||||||
|
if (_started)
|
||||||
|
{
|
||||||
|
DEBUGV("LwipIntfDev: use config() then begin()\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress realGateway, realNetmask, realDns1;
|
||||||
|
if (!ipAddressReorder(localIP, gateway, netmask, dns1, realGateway, realNetmask, realDns1))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ip4_addr_set_u32(ip_2_ip4(&_netif.ip_addr), localIP.v4());
|
||||||
|
ip4_addr_set_u32(ip_2_ip4(&_netif.gw), realGateway.v4());
|
||||||
|
ip4_addr_set_u32(ip_2_ip4(&_netif.netmask), realNetmask.v4());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
boolean LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu)
|
||||||
|
{
|
||||||
|
if (mtu)
|
||||||
|
{
|
||||||
|
_mtu = mtu;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (macAddress)
|
||||||
|
{
|
||||||
|
memcpy(_macAddress, macAddress, 6);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_netif.num = 2;
|
||||||
|
for (auto n = netif_list; n; n = n->next)
|
||||||
|
if (n->num >= _netif.num)
|
||||||
|
{
|
||||||
|
_netif.num = n->num + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
// forge a new mac-address from the esp's wifi sta one
|
||||||
|
// I understand this is cheating with an official mac-address
|
||||||
|
wifi_get_macaddr(STATION_IF, (uint8*)_macAddress);
|
||||||
|
#else
|
||||||
|
// https://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
|
||||||
|
memset(_macAddress, 0, 6);
|
||||||
|
_macAddress[0] = 0xEE;
|
||||||
|
#endif
|
||||||
|
_macAddress[3] += _netif.num; // alter base mac address
|
||||||
|
_macAddress[0] &= 0xfe; // set as locally administered, unicast, per
|
||||||
|
_macAddress[0] |= 0x02; // https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!RawDev::begin(_macAddress))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup lwIP netif
|
||||||
|
|
||||||
|
_netif.hwaddr_len = sizeof _macAddress;
|
||||||
|
memcpy(_netif.hwaddr, _macAddress, sizeof _macAddress);
|
||||||
|
|
||||||
|
// due to netif_add() api: ...
|
||||||
|
ip_addr_t ip_addr, netmask, gw;
|
||||||
|
ip_addr_copy(ip_addr, _netif.ip_addr);
|
||||||
|
ip_addr_copy(netmask, _netif.netmask);
|
||||||
|
ip_addr_copy(gw, _netif.gw);
|
||||||
|
|
||||||
|
if (!netif_add(&_netif, ip_2_ip4(&ip_addr), ip_2_ip4(&netmask), ip_2_ip4(&gw), this, netif_init_s, ethernet_input))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_netif.flags |= NETIF_FLAG_UP;
|
||||||
|
|
||||||
|
if (localIP().v4() == 0)
|
||||||
|
{
|
||||||
|
switch (dhcp_start(&_netif))
|
||||||
|
{
|
||||||
|
case ERR_OK:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ERR_IF:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
netif_remove(&_netif);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_started = true;
|
||||||
|
|
||||||
|
if (_intrPin >= 0)
|
||||||
|
{
|
||||||
|
if (RawDev::interruptIsPossible())
|
||||||
|
{
|
||||||
|
//attachInterrupt(_intrPin, [&]() { this->handlePackets(); }, FALLING);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
::printf((PGM_P)F("lwIP_Intf: Interrupt not implemented yet, enabling transparent polling\r\n"));
|
||||||
|
_intrPin = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_intrPin < 0 && !schedule_recurrent_function_us([&]()
|
||||||
|
{
|
||||||
|
this->handlePackets();
|
||||||
|
return true;
|
||||||
|
}, 100))
|
||||||
|
{
|
||||||
|
netif_remove(&_netif);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
wl_status_t LwipIntfDev<RawDev>::status()
|
||||||
|
{
|
||||||
|
return _started ? (connected() ? WL_CONNECTED : WL_DISCONNECTED) : WL_NO_SHIELD;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
err_t LwipIntfDev<RawDev>::linkoutput_s(netif *netif, struct pbuf *pbuf)
|
||||||
|
{
|
||||||
|
LwipIntfDev* ths = (LwipIntfDev*)netif->state;
|
||||||
|
|
||||||
|
if (pbuf->len != pbuf->tot_len || pbuf->next)
|
||||||
|
{
|
||||||
|
Serial.println("ERRTOT\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t len = ths->sendFrame((const uint8_t*)pbuf->payload, pbuf->len);
|
||||||
|
|
||||||
|
#if PHY_HAS_CAPTURE
|
||||||
|
if (phy_capture)
|
||||||
|
{
|
||||||
|
phy_capture(ths->_netif.num, (const char*)pbuf->payload, pbuf->len, /*out*/1, /*success*/len == pbuf->len);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return len == pbuf->len ? ERR_OK : ERR_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
err_t LwipIntfDev<RawDev>::netif_init_s(struct netif* netif)
|
||||||
|
{
|
||||||
|
return ((LwipIntfDev*)netif->state)->netif_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
void LwipIntfDev<RawDev>::netif_status_callback_s(struct netif* netif)
|
||||||
|
{
|
||||||
|
((LwipIntfDev*)netif->state)->netif_status_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
err_t LwipIntfDev<RawDev>::netif_init()
|
||||||
|
{
|
||||||
|
_netif.name[0] = 'e';
|
||||||
|
_netif.name[1] = '0' + _netif.num;
|
||||||
|
_netif.mtu = _mtu;
|
||||||
|
_netif.chksum_flags = NETIF_CHECKSUM_ENABLE_ALL;
|
||||||
|
_netif.flags =
|
||||||
|
NETIF_FLAG_ETHARP
|
||||||
|
| NETIF_FLAG_IGMP
|
||||||
|
| NETIF_FLAG_BROADCAST
|
||||||
|
| NETIF_FLAG_LINK_UP;
|
||||||
|
|
||||||
|
// lwIP's doc: This function typically first resolves the hardware
|
||||||
|
// address, then sends the packet. For ethernet physical layer, this is
|
||||||
|
// usually lwIP's etharp_output()
|
||||||
|
_netif.output = etharp_output;
|
||||||
|
|
||||||
|
// lwIP's doc: This function outputs the pbuf as-is on the link medium
|
||||||
|
// (this must points to the raw ethernet driver, meaning: us)
|
||||||
|
_netif.linkoutput = linkoutput_s;
|
||||||
|
|
||||||
|
_netif.status_callback = netif_status_callback_s;
|
||||||
|
|
||||||
|
return ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
void LwipIntfDev<RawDev>::netif_status_callback()
|
||||||
|
{
|
||||||
|
if (connected())
|
||||||
|
{
|
||||||
|
if (_default)
|
||||||
|
{
|
||||||
|
netif_set_default(&_netif);
|
||||||
|
}
|
||||||
|
sntp_stop();
|
||||||
|
sntp_init();
|
||||||
|
}
|
||||||
|
else if (netif_default == &_netif)
|
||||||
|
{
|
||||||
|
netif_set_default(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
err_t LwipIntfDev<RawDev>::handlePackets()
|
||||||
|
{
|
||||||
|
int pkt = 0;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
if (++pkt == 10)
|
||||||
|
// prevent starvation
|
||||||
|
{
|
||||||
|
return ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t tot_len = RawDev::readFrameSize();
|
||||||
|
if (!tot_len)
|
||||||
|
{
|
||||||
|
return ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// from doc: use PBUF_RAM for TX, PBUF_POOL from RX
|
||||||
|
// however:
|
||||||
|
// PBUF_POOL can return chained pbuf (not in one piece)
|
||||||
|
// and WiznetDriver does not have the proper API to deal with that
|
||||||
|
// so in the meantime, we use PBUF_RAM instead which is currently
|
||||||
|
// guarantying to deliver a continuous chunk of memory.
|
||||||
|
// TODO: tweak the wiznet driver to allow copying partial chunk
|
||||||
|
// of received data and use PBUF_POOL.
|
||||||
|
pbuf* pbuf = pbuf_alloc(PBUF_RAW, tot_len, PBUF_RAM);
|
||||||
|
if (!pbuf || pbuf->len < tot_len)
|
||||||
|
{
|
||||||
|
if (pbuf)
|
||||||
|
{
|
||||||
|
pbuf_free(pbuf);
|
||||||
|
}
|
||||||
|
RawDev::discardFrame(tot_len);
|
||||||
|
return ERR_BUF;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t len = RawDev::readFrameData((uint8_t*)pbuf->payload, tot_len);
|
||||||
|
if (len != tot_len)
|
||||||
|
{
|
||||||
|
// tot_len is given by readFrameSize()
|
||||||
|
// and is supposed to be honoured by readFrameData()
|
||||||
|
// todo: ensure this test is unneeded, remove the print
|
||||||
|
Serial.println("read error?\r\n");
|
||||||
|
pbuf_free(pbuf);
|
||||||
|
return ERR_BUF;
|
||||||
|
}
|
||||||
|
|
||||||
|
err_t err = _netif.input(pbuf, &_netif);
|
||||||
|
|
||||||
|
#if PHY_HAS_CAPTURE
|
||||||
|
if (phy_capture)
|
||||||
|
{
|
||||||
|
phy_capture(_netif.num, (const char*)pbuf->payload, tot_len, /*out*/0, /*success*/err == ERR_OK);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (err != ERR_OK)
|
||||||
|
{
|
||||||
|
pbuf_free(pbuf);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
// (else) allocated pbuf is now lwIP's responsibility
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
void LwipIntfDev<RawDev>::setDefault()
|
||||||
|
{
|
||||||
|
_default = true;
|
||||||
|
if (connected())
|
||||||
|
{
|
||||||
|
netif_set_default(&_netif);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // _LWIPINTFDEV_H
|
@ -1,5 +1,6 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <MD5Builder.h>
|
#include <MD5Builder.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
uint8_t hex_char_to_byte(uint8_t c) {
|
uint8_t hex_char_to_byte(uint8_t c) {
|
||||||
return (c >= 'a' && c <= 'f') ? (c - ((uint8_t)'a' - 0xa)) :
|
return (c >= 'a' && c <= 'f') ? (c - ((uint8_t)'a' - 0xa)) :
|
||||||
@ -18,23 +19,25 @@ void MD5Builder::add(const uint8_t * data, const uint16_t len){
|
|||||||
|
|
||||||
void MD5Builder::addHexString(const char * data){
|
void MD5Builder::addHexString(const char * data){
|
||||||
uint16_t i, len = strlen(data);
|
uint16_t i, len = strlen(data);
|
||||||
uint8_t * tmp = (uint8_t*)malloc(len/2);
|
auto tmp = std::unique_ptr<uint8_t[]>{new(std::nothrow) uint8_t[len / 2]};
|
||||||
if(tmp == NULL) {
|
|
||||||
|
if (!tmp) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(i=0; i<len; i+=2) {
|
for(i=0; i<len; i+=2) {
|
||||||
uint8_t high = hex_char_to_byte(data[i]);
|
uint8_t high = hex_char_to_byte(data[i]);
|
||||||
uint8_t low = hex_char_to_byte(data[i+1]);
|
uint8_t low = hex_char_to_byte(data[i+1]);
|
||||||
tmp[i/2] = (high & 0x0F) << 4 | (low & 0x0F);
|
tmp[i/2] = (high & 0x0F) << 4 | (low & 0x0F);
|
||||||
}
|
}
|
||||||
add(tmp, len/2);
|
add(tmp.get(), len/2);
|
||||||
free(tmp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MD5Builder::addStream(Stream &stream, const size_t maxLen) {
|
bool MD5Builder::addStream(Stream &stream, const size_t maxLen) {
|
||||||
const int buf_size = 512;
|
const int buf_size = 512;
|
||||||
int maxLengthLeft = maxLen;
|
int maxLengthLeft = maxLen;
|
||||||
uint8_t * buf = (uint8_t*) malloc(buf_size);
|
|
||||||
|
auto buf = std::unique_ptr<uint8_t[]>{new(std::nothrow) uint8_t[buf_size]};
|
||||||
|
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
return false;
|
return false;
|
||||||
@ -53,13 +56,13 @@ bool MD5Builder::addStream(Stream & stream, const size_t maxLen){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// read data and check if we got something
|
// read data and check if we got something
|
||||||
int numBytesRead = stream.readBytes(buf, readBytes);
|
int numBytesRead = stream.readBytes(buf.get(), readBytes);
|
||||||
if (numBytesRead < 1) {
|
if (numBytesRead < 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update MD5 with buffer payload
|
// Update MD5 with buffer payload
|
||||||
MD5Update(&_ctx, buf, numBytesRead);
|
MD5Update(&_ctx, buf.get(), numBytesRead);
|
||||||
|
|
||||||
yield(); // time for network streams
|
yield(); // time for network streams
|
||||||
|
|
||||||
@ -67,7 +70,7 @@ bool MD5Builder::addStream(Stream & stream, const size_t maxLen){
|
|||||||
maxLengthLeft -= numBytesRead;
|
maxLengthLeft -= numBytesRead;
|
||||||
bytesAvailable = stream.available();
|
bytesAvailable = stream.available();
|
||||||
}
|
}
|
||||||
free(buf);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,17 +78,17 @@ void MD5Builder::calculate(void){
|
|||||||
MD5Final(_buf, &_ctx);
|
MD5Final(_buf, &_ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MD5Builder::getBytes(uint8_t * output){
|
void MD5Builder::getBytes(uint8_t * output) const {
|
||||||
memcpy(output, _buf, 16);
|
memcpy(output, _buf, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MD5Builder::getChars(char * output){
|
void MD5Builder::getChars(char * output) const {
|
||||||
for (uint8_t i=0; i<16; i++){
|
for (uint8_t i=0; i<16; i++){
|
||||||
sprintf(output + (i * 2), "%02x", _buf[i]);
|
sprintf(output + (i * 2), "%02x", _buf[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String MD5Builder::toString(void){
|
String MD5Builder::toString(void) const {
|
||||||
char out[33];
|
char out[33];
|
||||||
getChars(out);
|
getChars(out);
|
||||||
return String(out);
|
return String(out);
|
||||||
|
@ -40,9 +40,9 @@ class MD5Builder {
|
|||||||
void addHexString(const String& data){ addHexString(data.c_str()); }
|
void addHexString(const String& data){ addHexString(data.c_str()); }
|
||||||
bool addStream(Stream & stream, const size_t maxLen);
|
bool addStream(Stream & stream, const size_t maxLen);
|
||||||
void calculate(void);
|
void calculate(void);
|
||||||
void getBytes(uint8_t * output);
|
void getBytes(uint8_t * output) const;
|
||||||
void getChars(char * output);
|
void getChars(char * output) const;
|
||||||
String toString(void);
|
String toString(void) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,9 +23,10 @@
|
|||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <limits>
|
#include <c_types.h> // IRAM_ATTR
|
||||||
|
#include <limits> // std::numeric_limits
|
||||||
#include <Arduino.h>
|
#include <type_traits> // std::is_unsigned
|
||||||
|
#include <core_esp8266_features.h>
|
||||||
|
|
||||||
namespace esp8266
|
namespace esp8266
|
||||||
{
|
{
|
||||||
@ -70,13 +71,13 @@ struct TimeSourceMillis
|
|||||||
|
|
||||||
struct TimeSourceCycles
|
struct TimeSourceCycles
|
||||||
{
|
{
|
||||||
// time policy based on ESP.getCycleCount()
|
// time policy based on esp_get_cycle_count()
|
||||||
// this particular time measurement is intended to be called very often
|
// this particular time measurement is intended to be called very often
|
||||||
// (every loop, every yield)
|
// (every loop, every yield)
|
||||||
|
|
||||||
using timeType = decltype(ESP.getCycleCount());
|
using timeType = decltype(esp_get_cycle_count());
|
||||||
static timeType time() {return ESP.getCycleCount();}
|
static timeType time() {return esp_get_cycle_count();}
|
||||||
static constexpr timeType ticksPerSecond = F_CPU; // 80'000'000 or 160'000'000 Hz
|
static constexpr timeType ticksPerSecond = esp_get_cpu_freq_mhz() * 1000000UL; // 80'000'000 or 160'000'000 Hz
|
||||||
static constexpr timeType ticksPerSecondMax = 160000000; // 160MHz
|
static constexpr timeType ticksPerSecondMax = 160000000; // 160MHz
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -153,7 +154,7 @@ public:
|
|||||||
reset(userTimeout);
|
reset(userTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
ICACHE_RAM_ATTR
|
IRAM_ATTR // fast
|
||||||
bool expired()
|
bool expired()
|
||||||
{
|
{
|
||||||
YieldPolicyT::execute(); //in case of DoNothing: gets optimized away
|
YieldPolicyT::execute(); //in case of DoNothing: gets optimized away
|
||||||
@ -162,7 +163,7 @@ public:
|
|||||||
return expiredOneShot();
|
return expiredOneShot();
|
||||||
}
|
}
|
||||||
|
|
||||||
ICACHE_RAM_ATTR
|
IRAM_ATTR // fast
|
||||||
operator bool()
|
operator bool()
|
||||||
{
|
{
|
||||||
return expired();
|
return expired();
|
||||||
@ -178,6 +179,8 @@ public:
|
|||||||
return _timeout != alwaysExpired;
|
return _timeout != alwaysExpired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resets, will trigger after this new timeout.
|
||||||
|
IRAM_ATTR // called from ISR
|
||||||
void reset(const timeType newUserTimeout)
|
void reset(const timeType newUserTimeout)
|
||||||
{
|
{
|
||||||
reset();
|
reset();
|
||||||
@ -185,11 +188,30 @@ public:
|
|||||||
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
|
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resets, will trigger after the timeout previously set.
|
||||||
|
IRAM_ATTR // called from ISR
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
_start = TimePolicyT::time();
|
_start = TimePolicyT::time();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resets to just expired so that on next poll the check will immediately trigger for the user,
|
||||||
|
// also change timeout (after next immediate trigger).
|
||||||
|
IRAM_ATTR // called from ISR
|
||||||
|
void resetAndSetExpired (const timeType newUserTimeout)
|
||||||
|
{
|
||||||
|
reset(newUserTimeout);
|
||||||
|
_start -= _timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resets to just expired so that on next poll the check will immediately trigger for the user.
|
||||||
|
IRAM_ATTR // called from ISR
|
||||||
|
void resetAndSetExpired ()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
_start -= _timeout;
|
||||||
|
}
|
||||||
|
|
||||||
void resetToNeverExpires ()
|
void resetToNeverExpires ()
|
||||||
{
|
{
|
||||||
_timeout = alwaysExpired + 1; // because canWait() has precedence
|
_timeout = alwaysExpired + 1; // because canWait() has precedence
|
||||||
@ -208,7 +230,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
ICACHE_RAM_ATTR
|
IRAM_ATTR // fast
|
||||||
bool checkExpired(const timeType internalUnit) const
|
bool checkExpired(const timeType internalUnit) const
|
||||||
{
|
{
|
||||||
// canWait() is not checked here
|
// canWait() is not checked here
|
||||||
@ -218,7 +240,7 @@ private:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
ICACHE_RAM_ATTR
|
IRAM_ATTR // fast
|
||||||
bool expiredRetrigger()
|
bool expiredRetrigger()
|
||||||
{
|
{
|
||||||
if (!canWait())
|
if (!canWait())
|
||||||
@ -234,7 +256,7 @@ protected:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ICACHE_RAM_ATTR
|
IRAM_ATTR // fast
|
||||||
bool expiredOneShot() const
|
bool expiredOneShot() const
|
||||||
{
|
{
|
||||||
// returns "always expired" or "has expired"
|
// returns "always expired" or "has expired"
|
||||||
@ -257,14 +279,14 @@ using periodic = polledTimeout::timeoutTemplate<true> /*__attribute__((deprecate
|
|||||||
using oneShotMs = polledTimeout::timeoutTemplate<false>;
|
using oneShotMs = polledTimeout::timeoutTemplate<false>;
|
||||||
using periodicMs = polledTimeout::timeoutTemplate<true>;
|
using periodicMs = polledTimeout::timeoutTemplate<true>;
|
||||||
|
|
||||||
// Time policy based on ESP.getCycleCount(), and intended to be called very often:
|
// Time policy based on esp_get_cycle_count(), and intended to be called very often:
|
||||||
// "Fast" versions sacrifices time range for improved precision and reduced execution time (by 86%)
|
// "Fast" versions sacrifices time range for improved precision and reduced execution time (by 86%)
|
||||||
// (cpu cycles for ::expired(): 372 (millis()) vs 52 (ESP.getCycleCount()))
|
// (cpu cycles for ::expired(): 372 (millis()) vs 52 (esp_get_cycle_count()))
|
||||||
// timeMax() values:
|
// timeMax() values:
|
||||||
// Ms: max is 26843 ms (26.8 s)
|
// Ms: max is 26843 ms (26.8 s)
|
||||||
// Us: max is 26843545 us (26.8 s)
|
// Us: max is 26843545 us (26.8 s)
|
||||||
// Ns: max is 1073741823 ns ( 1.07 s)
|
// Ns: max is 1073741823 ns ( 1.07 s)
|
||||||
// (time policy based on ESP.getCycleCount() is intended to be called very often)
|
// (time policy based on esp_get_cycle_count() is intended to be called very often)
|
||||||
|
|
||||||
using oneShotFastMs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
|
using oneShotFastMs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
|
||||||
using periodicFastMs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
|
using periodicFastMs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
|
||||||
|
@ -33,19 +33,11 @@
|
|||||||
|
|
||||||
/* default implementation: may be overridden */
|
/* default implementation: may be overridden */
|
||||||
size_t Print::write(const uint8_t *buffer, size_t size) {
|
size_t Print::write(const uint8_t *buffer, size_t size) {
|
||||||
|
IAMSLOW();
|
||||||
#ifdef DEBUG_ESP_CORE
|
|
||||||
static char not_the_best_way [] PROGMEM STORE_ATTR = "Print::write(data,len) should be overridden for better efficiency\r\n";
|
|
||||||
static bool once = false;
|
|
||||||
if (!once) {
|
|
||||||
once = true;
|
|
||||||
os_printf_plus(not_the_best_way);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
while (size--) {
|
while (size--) {
|
||||||
size_t ret = write(*buffer++);
|
size_t ret = write(pgm_read_byte(buffer++));
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
// Write of last byte didn't complete, abort additional processing
|
// Write of last byte didn't complete, abort additional processing
|
||||||
break;
|
break;
|
||||||
@ -63,7 +55,7 @@ size_t Print::printf(const char *format, ...) {
|
|||||||
size_t len = vsnprintf(temp, sizeof(temp), format, arg);
|
size_t len = vsnprintf(temp, sizeof(temp), format, arg);
|
||||||
va_end(arg);
|
va_end(arg);
|
||||||
if (len > sizeof(temp) - 1) {
|
if (len > sizeof(temp) - 1) {
|
||||||
buffer = new char[len + 1];
|
buffer = new (std::nothrow) char[len + 1];
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -86,7 +78,7 @@ size_t Print::printf_P(PGM_P format, ...) {
|
|||||||
size_t len = vsnprintf_P(temp, sizeof(temp), format, arg);
|
size_t len = vsnprintf_P(temp, sizeof(temp), format, arg);
|
||||||
va_end(arg);
|
va_end(arg);
|
||||||
if (len > sizeof(temp) - 1) {
|
if (len > sizeof(temp) - 1) {
|
||||||
buffer = new char[len + 1];
|
buffer = new (std::nothrow) char[len + 1];
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -104,11 +96,19 @@ size_t Print::printf_P(PGM_P format, ...) {
|
|||||||
size_t Print::print(const __FlashStringHelper *ifsh) {
|
size_t Print::print(const __FlashStringHelper *ifsh) {
|
||||||
PGM_P p = reinterpret_cast<PGM_P>(ifsh);
|
PGM_P p = reinterpret_cast<PGM_P>(ifsh);
|
||||||
|
|
||||||
|
char buff[128] __attribute__ ((aligned(4)));
|
||||||
|
auto len = strlen_P(p);
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
while (1) {
|
while (n < len) {
|
||||||
uint8_t c = pgm_read_byte(p++);
|
int to_write = std::min(sizeof(buff), len - n);
|
||||||
if (c == 0) break;
|
memcpy_P(buff, p, to_write);
|
||||||
n += write(c);
|
auto written = write(buff, to_write);
|
||||||
|
n += written;
|
||||||
|
p += written;
|
||||||
|
if (!written) {
|
||||||
|
// Some error, write() should write at least 1 byte before returning
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
@ -138,35 +138,39 @@ size_t Print::print(unsigned int n, int base) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::print(long n, int base) {
|
size_t Print::print(long n, int base) {
|
||||||
if(base == 0) {
|
int t = 0;
|
||||||
return write(n);
|
if (base == 10 && n < 0) {
|
||||||
} else if(base == 10) {
|
t = print('-');
|
||||||
if(n < 0) {
|
|
||||||
int t = print('-');
|
|
||||||
n = -n;
|
n = -n;
|
||||||
return printNumber(n, 10) + t;
|
|
||||||
}
|
|
||||||
return printNumber(n, 10);
|
|
||||||
} else {
|
|
||||||
return printNumber(n, base);
|
|
||||||
}
|
}
|
||||||
|
return printNumber(static_cast<unsigned long>(n), base) + t;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::print(unsigned long n, int base) {
|
size_t Print::print(unsigned long n, int base) {
|
||||||
if(base == 0)
|
if (base == 0) {
|
||||||
return write(n);
|
return write(n);
|
||||||
else
|
}
|
||||||
|
return printNumber(n, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(long long n, int base) {
|
||||||
|
int t = 0;
|
||||||
|
if (base == 10 && n < 0) {
|
||||||
|
t = print('-');
|
||||||
|
n = -n;
|
||||||
|
}
|
||||||
|
return printNumber(static_cast<unsigned long long>(n), base) + t;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(unsigned long long n, int base) {
|
||||||
|
if (base == 0) {
|
||||||
|
return write(n);
|
||||||
|
}
|
||||||
return printNumber(n, base);
|
return printNumber(n, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::print(double n, int digits) {
|
size_t Print::print(double n, int digits) {
|
||||||
return printFloat(n, digits);
|
return printNumber(n, digits);
|
||||||
}
|
|
||||||
|
|
||||||
size_t Print::println(const __FlashStringHelper *ifsh) {
|
|
||||||
size_t n = print(ifsh);
|
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::print(const Printable& x) {
|
size_t Print::print(const Printable& x) {
|
||||||
@ -177,130 +181,90 @@ size_t Print::println(void) {
|
|||||||
return print("\r\n");
|
return print("\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t Print::println(const __FlashStringHelper* ifsh) {
|
||||||
|
return _println<const __FlashStringHelper*>(ifsh);
|
||||||
|
}
|
||||||
|
|
||||||
size_t Print::println(const String &s) {
|
size_t Print::println(const String &s) {
|
||||||
size_t n = print(s);
|
return _println(s);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(const char c[]) {
|
size_t Print::println(const char c[]) {
|
||||||
size_t n = print(c);
|
return _println(c);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(char c) {
|
size_t Print::println(char c) {
|
||||||
size_t n = print(c);
|
return _println(c);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(unsigned char b, int base) {
|
size_t Print::println(unsigned char b, int base) {
|
||||||
size_t n = print(b, base);
|
return _println(b, base);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(int num, int base) {
|
size_t Print::println(int num, int base) {
|
||||||
size_t n = print(num, base);
|
return _println(num, base);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(unsigned int num, int base) {
|
size_t Print::println(unsigned int num, int base) {
|
||||||
size_t n = print(num, base);
|
return _println(num, base);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(long num, int base) {
|
size_t Print::println(long num, int base) {
|
||||||
size_t n = print(num, base);
|
return _println(num, base);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(unsigned long num, int base) {
|
size_t Print::println(unsigned long num, int base) {
|
||||||
size_t n = print(num, base);
|
return _println(num, base);
|
||||||
n += println();
|
}
|
||||||
return n;
|
|
||||||
|
size_t Print::println(long long num, int base) {
|
||||||
|
return _println(num, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(unsigned long long num, int base) {
|
||||||
|
return _println(num, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(double num, int digits) {
|
size_t Print::println(double num, int digits) {
|
||||||
size_t n = print(num, digits);
|
return _println(num, digits);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(const Printable& x) {
|
size_t Print::println(const Printable& x) {
|
||||||
size_t n = print(x);
|
return _println<const Printable&>(x);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private Methods /////////////////////////////////////////////////////////////
|
// Private Methods /////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
size_t Print::printNumber(unsigned long n, uint8_t base) {
|
template<typename T, typename... P> inline size_t Print::_println(T v, P... args)
|
||||||
char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte.
|
{
|
||||||
|
size_t n = print(v, args...);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T> size_t Print::printNumber(T n, uint8_t base) {
|
||||||
|
char buf[8 * sizeof(n) + 1]; // Assumes 8-bit chars plus zero byte.
|
||||||
char* str = &buf[sizeof(buf) - 1];
|
char* str = &buf[sizeof(buf) - 1];
|
||||||
|
|
||||||
*str = '\0';
|
*str = '\0';
|
||||||
|
|
||||||
// prevent crash if called with base == 1
|
// prevent crash if called with base == 1
|
||||||
if(base < 2)
|
if (base < 2) {
|
||||||
base = 10;
|
base = 10;
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
unsigned long m = n;
|
auto m = n;
|
||||||
n /= base;
|
n /= base;
|
||||||
char c = m - base * n;
|
char c = m - base * n;
|
||||||
|
|
||||||
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
||||||
} while (n);
|
} while (n);
|
||||||
|
|
||||||
return write(str);
|
return write(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::printFloat(double number, uint8_t digits) {
|
template<> size_t Print::printNumber(double number, uint8_t digits) {
|
||||||
size_t n = 0;
|
char buf[40];
|
||||||
|
return write(dtostrf(number, 0, digits, buf));
|
||||||
if(isnan(number))
|
|
||||||
return print("nan");
|
|
||||||
if(isinf(number))
|
|
||||||
return print("inf");
|
|
||||||
if(number > 4294967040.0)
|
|
||||||
return print("ovf"); // constant determined empirically
|
|
||||||
if(number < -4294967040.0)
|
|
||||||
return print("ovf"); // constant determined empirically
|
|
||||||
|
|
||||||
// Handle negative numbers
|
|
||||||
if(number < 0.0) {
|
|
||||||
n += print('-');
|
|
||||||
number = -number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Round correctly so that print(1.999, 2) prints as "2.00"
|
|
||||||
double rounding = 0.5;
|
|
||||||
for(uint8_t i = 0; i < digits; ++i)
|
|
||||||
rounding /= 10.0;
|
|
||||||
|
|
||||||
number += rounding;
|
|
||||||
|
|
||||||
// Extract the integer part of the number and print it
|
|
||||||
unsigned long int_part = (unsigned long) number;
|
|
||||||
double remainder = number - (double) int_part;
|
|
||||||
n += print(int_part);
|
|
||||||
|
|
||||||
// Print the decimal point, but only if there are digits beyond
|
|
||||||
if(digits > 0) {
|
|
||||||
n += print(".");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract digits from the remainder one at a time
|
|
||||||
while(digits-- > 0) {
|
|
||||||
remainder *= 10.0;
|
|
||||||
int toPrint = int(remainder);
|
|
||||||
n += print(toPrint);
|
|
||||||
remainder -= toPrint;
|
|
||||||
}
|
|
||||||
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
#include "WString.h"
|
#include "WString.h"
|
||||||
#include "Printable.h"
|
#include "Printable.h"
|
||||||
|
|
||||||
|
#include "stdlib_noniso.h"
|
||||||
|
|
||||||
#define DEC 10
|
#define DEC 10
|
||||||
#define HEX 16
|
#define HEX 16
|
||||||
#define OCT 8
|
#define OCT 8
|
||||||
@ -33,17 +35,15 @@
|
|||||||
|
|
||||||
class Print {
|
class Print {
|
||||||
private:
|
private:
|
||||||
int write_error;
|
int write_error = 0;
|
||||||
size_t printNumber(unsigned long, uint8_t);
|
template<typename T> size_t printNumber(T n, uint8_t base);
|
||||||
size_t printFloat(double, uint8_t);
|
template<typename T, typename... P> inline size_t _println(T v, P... args);
|
||||||
protected:
|
protected:
|
||||||
void setWriteError(int err = 1) {
|
void setWriteError(int err = 1) {
|
||||||
write_error = err;
|
write_error = err;
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
Print() :
|
Print() {}
|
||||||
write_error(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
int getWriteError() {
|
int getWriteError() {
|
||||||
return write_error;
|
return write_error;
|
||||||
@ -56,7 +56,7 @@ class Print {
|
|||||||
size_t write(const char *str) {
|
size_t write(const char *str) {
|
||||||
if(str == NULL)
|
if(str == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
return write((const uint8_t *) str, strlen(str));
|
return write((const uint8_t *) str, strlen_P(str));
|
||||||
}
|
}
|
||||||
virtual size_t write(const uint8_t *buffer, size_t size);
|
virtual size_t write(const uint8_t *buffer, size_t size);
|
||||||
size_t write(const char *buffer, size_t size) {
|
size_t write(const char *buffer, size_t size) {
|
||||||
@ -69,10 +69,16 @@ class Print {
|
|||||||
inline size_t write(unsigned int t) { return write((uint8_t)t); }
|
inline size_t write(unsigned int t) { return write((uint8_t)t); }
|
||||||
inline size_t write(long t) { return write((uint8_t)t); }
|
inline size_t write(long t) { return write((uint8_t)t); }
|
||||||
inline size_t write(unsigned long t) { return write((uint8_t)t); }
|
inline size_t write(unsigned long t) { return write((uint8_t)t); }
|
||||||
|
inline size_t write(long long t) { return write((uint8_t)t); }
|
||||||
|
inline size_t write(unsigned long long t) { return write((uint8_t)t); }
|
||||||
// Enable write(char) to fall through to write(uint8_t)
|
// Enable write(char) to fall through to write(uint8_t)
|
||||||
inline size_t write(char c) { return write((uint8_t) c); }
|
inline size_t write(char c) { return write((uint8_t) c); }
|
||||||
inline size_t write(int8_t c) { return write((uint8_t) c); }
|
inline size_t write(int8_t c) { return write((uint8_t) c); }
|
||||||
|
|
||||||
|
// default to zero, meaning "a single write may block"
|
||||||
|
// should be overriden by subclasses with buffering
|
||||||
|
virtual int availableForWrite() { return 0; }
|
||||||
|
|
||||||
size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));
|
size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));
|
||||||
size_t printf_P(PGM_P format, ...) __attribute__((format(printf, 2, 3)));
|
size_t printf_P(PGM_P format, ...) __attribute__((format(printf, 2, 3)));
|
||||||
size_t print(const __FlashStringHelper *);
|
size_t print(const __FlashStringHelper *);
|
||||||
@ -84,6 +90,8 @@ class Print {
|
|||||||
size_t print(unsigned int, int = DEC);
|
size_t print(unsigned int, int = DEC);
|
||||||
size_t print(long, int = DEC);
|
size_t print(long, int = DEC);
|
||||||
size_t print(unsigned long, int = DEC);
|
size_t print(unsigned long, int = DEC);
|
||||||
|
size_t print(long long, int = DEC);
|
||||||
|
size_t print(unsigned long long, int = DEC);
|
||||||
size_t print(double, int = 2);
|
size_t print(double, int = 2);
|
||||||
size_t print(const Printable&);
|
size_t print(const Printable&);
|
||||||
|
|
||||||
@ -96,11 +104,19 @@ class Print {
|
|||||||
size_t println(unsigned int, int = DEC);
|
size_t println(unsigned int, int = DEC);
|
||||||
size_t println(long, int = DEC);
|
size_t println(long, int = DEC);
|
||||||
size_t println(unsigned long, int = DEC);
|
size_t println(unsigned long, int = DEC);
|
||||||
|
size_t println(long long, int = DEC);
|
||||||
|
size_t println(unsigned long long, int = DEC);
|
||||||
size_t println(double, int = 2);
|
size_t println(double, int = 2);
|
||||||
size_t println(const Printable&);
|
size_t println(const Printable&);
|
||||||
size_t println(void);
|
size_t println(void);
|
||||||
|
|
||||||
virtual void flush() { /* Empty implementation for backward compatibility */ }
|
virtual void flush() { /* Empty implementation for backward compatibility */ }
|
||||||
|
|
||||||
|
// by default write timeout is possible (outgoing data from network,serial..)
|
||||||
|
// (children can override to false (like String))
|
||||||
|
virtual bool outputCanTimeout () { return true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<> size_t Print::printNumber(double number, uint8_t digits);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,78 +1,251 @@
|
|||||||
#include "Schedule.h"
|
/*
|
||||||
|
Schedule.cpp - Scheduled functions.
|
||||||
|
Copyright (c) 2020 esp8266/Arduino
|
||||||
|
|
||||||
|
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 <assert.h>
|
||||||
|
|
||||||
|
#include "Schedule.h"
|
||||||
|
#include "PolledTimeout.h"
|
||||||
|
#include "interrupts.h"
|
||||||
|
#include "coredecls.h"
|
||||||
|
|
||||||
|
typedef std::function<void(void)> mSchedFuncT;
|
||||||
struct scheduled_fn_t
|
struct scheduled_fn_t
|
||||||
{
|
{
|
||||||
scheduled_fn_t* mNext;
|
scheduled_fn_t* mNext = nullptr;
|
||||||
std::function<void(void)> mFunc;
|
mSchedFuncT mFunc;
|
||||||
};
|
};
|
||||||
|
|
||||||
static scheduled_fn_t* sFirst = 0;
|
static scheduled_fn_t* sFirst = nullptr;
|
||||||
static scheduled_fn_t* sLast = 0;
|
static scheduled_fn_t* sLast = nullptr;
|
||||||
|
static scheduled_fn_t* sUnused = nullptr;
|
||||||
static scheduled_fn_t* sFirstUnused = 0;
|
|
||||||
static scheduled_fn_t* sLastUnused = 0;
|
|
||||||
|
|
||||||
static int sCount = 0;
|
static int sCount = 0;
|
||||||
|
|
||||||
static scheduled_fn_t* get_fn() {
|
typedef std::function<bool(void)> mRecFuncT;
|
||||||
scheduled_fn_t* result = NULL;
|
struct recurrent_fn_t
|
||||||
|
{
|
||||||
|
recurrent_fn_t* mNext = nullptr;
|
||||||
|
mRecFuncT mFunc;
|
||||||
|
esp8266::polledTimeout::periodicFastUs callNow;
|
||||||
|
std::function<bool(void)> alarm = nullptr;
|
||||||
|
recurrent_fn_t(esp8266::polledTimeout::periodicFastUs interval) : callNow(interval) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
static recurrent_fn_t* rFirst = nullptr;
|
||||||
|
static recurrent_fn_t* rLast = nullptr;
|
||||||
|
|
||||||
|
// Returns a pointer to an unused sched_fn_t,
|
||||||
|
// or if none are available allocates a new one,
|
||||||
|
// or nullptr if limit is reached
|
||||||
|
IRAM_ATTR // called from ISR
|
||||||
|
static scheduled_fn_t* get_fn_unsafe()
|
||||||
|
{
|
||||||
|
scheduled_fn_t* result = nullptr;
|
||||||
// try to get an item from unused items list
|
// try to get an item from unused items list
|
||||||
if (sFirstUnused) {
|
if (sUnused)
|
||||||
result = sFirstUnused;
|
{
|
||||||
sFirstUnused = result->mNext;
|
result = sUnused;
|
||||||
if (sFirstUnused == NULL) {
|
sUnused = sUnused->mNext;
|
||||||
sLastUnused = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// if no unused items, and count not too high, allocate a new one
|
// if no unused items, and count not too high, allocate a new one
|
||||||
else if (sCount != SCHEDULED_FN_MAX_COUNT) {
|
else if (sCount < SCHEDULED_FN_MAX_COUNT)
|
||||||
result = new scheduled_fn_t;
|
{
|
||||||
result->mNext = NULL;
|
result = new (std::nothrow) scheduled_fn_t;
|
||||||
|
if (result)
|
||||||
++sCount;
|
++sCount;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void recycle_fn(scheduled_fn_t* fn)
|
static void recycle_fn_unsafe(scheduled_fn_t* fn)
|
||||||
{
|
{
|
||||||
if (!sLastUnused) {
|
fn->mFunc = nullptr; // special overload in c++ std lib
|
||||||
sFirstUnused = fn;
|
fn->mNext = sUnused;
|
||||||
}
|
sUnused = fn;
|
||||||
else {
|
|
||||||
sLastUnused->mNext = fn;
|
|
||||||
}
|
|
||||||
fn->mNext = NULL;
|
|
||||||
sLastUnused = fn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool schedule_function(std::function<void(void)> fn)
|
IRAM_ATTR // (not only) called from ISR
|
||||||
|
bool schedule_function(const std::function<void(void)>& fn)
|
||||||
{
|
{
|
||||||
scheduled_fn_t* item = get_fn();
|
if (!fn)
|
||||||
if (!item) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
esp8266::InterruptLock lockAllInterruptsInThisScope;
|
||||||
|
|
||||||
|
scheduled_fn_t* item = get_fn_unsafe();
|
||||||
|
if (!item)
|
||||||
|
return false;
|
||||||
|
|
||||||
item->mFunc = fn;
|
item->mFunc = fn;
|
||||||
item->mNext = NULL;
|
item->mNext = nullptr;
|
||||||
if (!sFirst) {
|
|
||||||
sFirst = item;
|
if (sFirst)
|
||||||
}
|
|
||||||
else {
|
|
||||||
sLast->mNext = item;
|
sLast->mNext = item;
|
||||||
}
|
else
|
||||||
|
sFirst = item;
|
||||||
sLast = item;
|
sLast = item;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
IRAM_ATTR // (not only) called from ISR
|
||||||
|
bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
|
||||||
|
uint32_t repeat_us, const std::function<bool(void)>& alarm)
|
||||||
|
{
|
||||||
|
assert(repeat_us < decltype(recurrent_fn_t::callNow)::neverExpires); //~26800000us (26.8s)
|
||||||
|
|
||||||
|
if (!fn)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
recurrent_fn_t* item = new (std::nothrow) recurrent_fn_t(repeat_us);
|
||||||
|
if (!item)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
item->mFunc = fn;
|
||||||
|
item->alarm = alarm;
|
||||||
|
|
||||||
|
esp8266::InterruptLock lockAllInterruptsInThisScope;
|
||||||
|
|
||||||
|
if (rLast)
|
||||||
|
{
|
||||||
|
rLast->mNext = item;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rFirst = item;
|
||||||
|
}
|
||||||
|
rLast = item;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void run_scheduled_functions()
|
void run_scheduled_functions()
|
||||||
{
|
{
|
||||||
scheduled_fn_t* rFirst = sFirst;
|
esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
|
||||||
sFirst = NULL;
|
|
||||||
sLast = NULL;
|
// prevent scheduling of new functions during this run
|
||||||
while (rFirst) {
|
auto stop = sLast;
|
||||||
scheduled_fn_t* item = rFirst;
|
bool done = false;
|
||||||
rFirst = item->mNext;
|
while (sFirst && !done)
|
||||||
item->mFunc();
|
{
|
||||||
item->mFunc = std::function<void(void)>();
|
done = sFirst == stop;
|
||||||
recycle_fn(item);
|
|
||||||
|
sFirst->mFunc();
|
||||||
|
|
||||||
|
{
|
||||||
|
// remove function from stack
|
||||||
|
esp8266::InterruptLock lockAllInterruptsInThisScope;
|
||||||
|
|
||||||
|
auto to_recycle = sFirst;
|
||||||
|
|
||||||
|
// removing rLast
|
||||||
|
if (sLast == sFirst)
|
||||||
|
sLast = nullptr;
|
||||||
|
|
||||||
|
sFirst = sFirst->mNext;
|
||||||
|
|
||||||
|
recycle_fn_unsafe(to_recycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yieldNow)
|
||||||
|
{
|
||||||
|
// because scheduled functions might last too long for watchdog etc,
|
||||||
|
// this is yield() in cont stack:
|
||||||
|
esp_schedule();
|
||||||
|
cont_yield(g_pcont);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void run_scheduled_recurrent_functions()
|
||||||
|
{
|
||||||
|
esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
|
||||||
|
|
||||||
|
// Note to the reader:
|
||||||
|
// There is no exposed API to remove a scheduled function:
|
||||||
|
// Scheduled functions are removed only from this function, and
|
||||||
|
// its purpose is that it is never called from an interrupt
|
||||||
|
// (always on cont stack).
|
||||||
|
|
||||||
|
auto current = rFirst;
|
||||||
|
if (!current)
|
||||||
|
return;
|
||||||
|
|
||||||
|
static bool fence = false;
|
||||||
|
{
|
||||||
|
// fence is like a mutex but as we are never called from ISR,
|
||||||
|
// locking is useless here. Leaving comment for reference.
|
||||||
|
//esp8266::InterruptLock lockAllInterruptsInThisScope;
|
||||||
|
|
||||||
|
if (fence)
|
||||||
|
// prevent recursive calls from yield()
|
||||||
|
// (even if they are not allowed)
|
||||||
|
return;
|
||||||
|
fence = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
recurrent_fn_t* prev = nullptr;
|
||||||
|
// prevent scheduling of new functions during this run
|
||||||
|
auto stop = rLast;
|
||||||
|
|
||||||
|
bool done;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
done = current == stop;
|
||||||
|
const bool wakeup = current->alarm && current->alarm();
|
||||||
|
bool callNow = current->callNow;
|
||||||
|
|
||||||
|
if ((wakeup || callNow) && !current->mFunc())
|
||||||
|
{
|
||||||
|
// remove function from stack
|
||||||
|
esp8266::InterruptLock lockAllInterruptsInThisScope;
|
||||||
|
|
||||||
|
auto to_ditch = current;
|
||||||
|
|
||||||
|
// removing rLast
|
||||||
|
if (rLast == current)
|
||||||
|
rLast = prev;
|
||||||
|
|
||||||
|
current = current->mNext;
|
||||||
|
if (prev)
|
||||||
|
{
|
||||||
|
prev->mNext = current;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rFirst = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(to_ditch);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prev = current;
|
||||||
|
current = current->mNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yieldNow)
|
||||||
|
{
|
||||||
|
// because scheduled functions might last too long for watchdog etc,
|
||||||
|
// this is yield() in cont stack:
|
||||||
|
esp_schedule();
|
||||||
|
cont_yield(g_pcont);
|
||||||
|
}
|
||||||
|
} while (current && !done);
|
||||||
|
|
||||||
|
fence = false;
|
||||||
|
}
|
||||||
|
@ -1,27 +1,91 @@
|
|||||||
|
/*
|
||||||
|
Schedule.h - Header file for scheduled functions.
|
||||||
|
Copyright (c) 2020 esp8266/Arduino
|
||||||
|
|
||||||
|
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 ESP_SCHEDULE_H
|
#ifndef ESP_SCHEDULE_H
|
||||||
#define ESP_SCHEDULE_H
|
#define ESP_SCHEDULE_H
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#define SCHEDULED_FN_MAX_COUNT 32
|
#define SCHEDULED_FN_MAX_COUNT 32
|
||||||
#define SCHEDULED_FN_INITIAL_COUNT 4
|
|
||||||
|
|
||||||
// Warning
|
// The purpose of scheduled functions is to trigger, from SYS stack (like in
|
||||||
// This API is not considered stable.
|
// an interrupt or a system event), registration of user code to be executed
|
||||||
// Function signatures will change.
|
// in user stack (called CONT stack) without the common restrictions from
|
||||||
// You have been warned.
|
// system context. Details are below.
|
||||||
|
|
||||||
// Run given function next time `loop` function returns,
|
// The purpose of recurrent scheduled function is to independently execute
|
||||||
// or `run_scheduled_functions` is called.
|
// user code in CONT stack on a regular basis.
|
||||||
// Use std::bind to pass arguments to a function, or call a class member function.
|
// It has been introduced with ethernet service in mind, it can also be used
|
||||||
// Note: there is no mechanism for cancelling scheduled functions.
|
// for all libraries in the need of a regular `libdaemon_handlestuff()`.
|
||||||
// Keep that in mind when binding functions to objects which may have short lifetime.
|
// It allows these services to be run even from a user loop not going back
|
||||||
// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT.
|
// to `loop()`.
|
||||||
bool schedule_function(std::function<void(void)> fn);
|
// Ticker + scheduled function offer the same service but recurrent
|
||||||
|
// scheduled function happen more often: every yield() (vs every loop()),
|
||||||
|
// and time resolution is microsecond (vs millisecond). Details are below.
|
||||||
|
|
||||||
|
// scheduled functions called once:
|
||||||
|
//
|
||||||
|
// * internal queue is FIFO.
|
||||||
|
// * Add the given lambda to a fifo list of lambdas, which is run when
|
||||||
|
// `loop` function returns.
|
||||||
|
// * Use lambdas to pass arguments to a function, or call a class/static
|
||||||
|
// member function.
|
||||||
|
// * Please ensure variables or instances used from inside lambda will exist
|
||||||
|
// when lambda is later called.
|
||||||
|
// * There is no mechanism for cancelling scheduled functions.
|
||||||
|
// * `yield` can be called from inside lambdas.
|
||||||
|
// * Returns false if the number of scheduled functions exceeds
|
||||||
|
// SCHEDULED_FN_MAX_COUNT (or memory shortage).
|
||||||
|
// * Run the lambda only once next time.
|
||||||
|
// * A scheduled function can schedule a function.
|
||||||
|
|
||||||
|
bool schedule_function (const std::function<void(void)>& fn);
|
||||||
|
|
||||||
// Run all scheduled functions.
|
// Run all scheduled functions.
|
||||||
// Use this function if your are not using `loop`, or `loop` does not return
|
// Use this function if your are not using `loop`,
|
||||||
// on a regular basis.
|
// or `loop` does not return on a regular basis.
|
||||||
|
|
||||||
void run_scheduled_functions();
|
void run_scheduled_functions();
|
||||||
|
|
||||||
|
// recurrent scheduled function:
|
||||||
|
//
|
||||||
|
// * Internal queue is a FIFO.
|
||||||
|
// * Run the lambda periodically about every <repeat_us> microseconds until
|
||||||
|
// it returns false.
|
||||||
|
// * Note that it may be more than <repeat_us> microseconds between calls if
|
||||||
|
// `yield` is not called frequently, and therefore should not be used for
|
||||||
|
// timing critical operations.
|
||||||
|
// * Please ensure variables or instances used from inside lambda will exist
|
||||||
|
// when lambda is later called.
|
||||||
|
// * There is no mechanism for externally cancelling recurrent scheduled
|
||||||
|
// functions. However a user function returning false will cancel itself.
|
||||||
|
// * Long running operations or yield() or delay() are not allowed in the
|
||||||
|
// recurrent function.
|
||||||
|
// * If alarm is used, anytime during scheduling when it returns true,
|
||||||
|
// any remaining delay from repeat_us is disregarded, and fn is executed.
|
||||||
|
|
||||||
|
bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
|
||||||
|
uint32_t repeat_us, const std::function<bool(void)>& alarm = nullptr);
|
||||||
|
|
||||||
|
// Test recurrence and run recurrent scheduled functions.
|
||||||
|
// (internally called at every `yield()` and `loop()`)
|
||||||
|
|
||||||
|
void run_scheduled_recurrent_functions();
|
||||||
|
|
||||||
#endif // ESP_SCHEDULE_H
|
#endif // ESP_SCHEDULE_H
|
||||||
|
@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
* ScheduledFunctions.cpp
|
|
||||||
*
|
|
||||||
* Created on: 27 apr. 2018
|
|
||||||
* Author: Herman
|
|
||||||
*/
|
|
||||||
#include "ScheduledFunctions.h"
|
|
||||||
|
|
||||||
std::list<ScheduledFunctions::ScheduledElement> ScheduledFunctions::scheduledFunctions;
|
|
||||||
|
|
||||||
ScheduledFunctions::ScheduledFunctions()
|
|
||||||
:ScheduledFunctions(UINT_MAX)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ScheduledFunctions::ScheduledFunctions(unsigned int reqMax)
|
|
||||||
{
|
|
||||||
maxElements = reqMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScheduledFunctions::~ScheduledFunctions() {
|
|
||||||
}
|
|
||||||
|
|
||||||
ScheduledRegistration ScheduledFunctions::insertElement(ScheduledElement se, bool front)
|
|
||||||
{
|
|
||||||
if (countElements >= maxElements)
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
countElements++;
|
|
||||||
if (front)
|
|
||||||
{
|
|
||||||
scheduledFunctions.push_front(se);
|
|
||||||
return scheduledFunctions.begin()->registration;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
scheduledFunctions.push_back(se);
|
|
||||||
return scheduledFunctions.rbegin()->registration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::list<ScheduledFunctions::ScheduledElement>::iterator ScheduledFunctions::eraseElement(std::list<ScheduledFunctions::ScheduledElement>::iterator it)
|
|
||||||
{
|
|
||||||
countElements--;
|
|
||||||
return scheduledFunctions.erase(it);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ScheduledFunctions::scheduleFunction(ScheduledFunction sf, bool continuous, bool front)
|
|
||||||
{
|
|
||||||
return (insertElement({this,continuous,nullptr,sf}, front) == nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ScheduledFunctions::scheduleFunction(ScheduledFunction sf)
|
|
||||||
{
|
|
||||||
return scheduleFunction(sf, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
ScheduledRegistration ScheduledFunctions::scheduleFunctionReg (ScheduledFunction sf, bool continuous, bool front)
|
|
||||||
{
|
|
||||||
return insertElement({this,continuous,std::make_shared<int>(1),sf},front);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScheduledFunctions::runScheduledFunctions()
|
|
||||||
{
|
|
||||||
auto lastElement = scheduledFunctions.end(); // do not execute elements added during runScheduledFunctions
|
|
||||||
auto it = scheduledFunctions.begin();
|
|
||||||
while (it != lastElement)
|
|
||||||
{
|
|
||||||
bool erase = false;
|
|
||||||
if (it->registration == nullptr)
|
|
||||||
{
|
|
||||||
it->function();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (it->registration.use_count() > 1)
|
|
||||||
{
|
|
||||||
it->function();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
erase = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((!it->continuous) || (erase))
|
|
||||||
{
|
|
||||||
it = it->_this->eraseElement(it);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
it++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScheduledFunctions::removeFunction(ScheduledRegistration sr)
|
|
||||||
{
|
|
||||||
auto it = scheduledFunctions.begin();
|
|
||||||
bool removed = false;
|
|
||||||
while ((!removed) && (it != scheduledFunctions.end()))
|
|
||||||
{
|
|
||||||
if (it->registration == sr)
|
|
||||||
{
|
|
||||||
it = eraseElement(it);
|
|
||||||
removed = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
it++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* ScheduledFunctions.h
|
|
||||||
*
|
|
||||||
* Created on: 27 apr. 2018
|
|
||||||
* Author: Herman
|
|
||||||
*/
|
|
||||||
#include "Arduino.h"
|
|
||||||
#include "Schedule.h"
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <list>
|
|
||||||
#include <climits>
|
|
||||||
|
|
||||||
#ifndef SCHEDULEDFUNCTIONS_H_
|
|
||||||
#define SCHEDULEDFUNCTIONS_H_
|
|
||||||
|
|
||||||
typedef std::function<void(void)> ScheduledFunction;
|
|
||||||
typedef std::shared_ptr<void> ScheduledRegistration;
|
|
||||||
|
|
||||||
class ScheduledFunctions {
|
|
||||||
|
|
||||||
public:
|
|
||||||
ScheduledFunctions();
|
|
||||||
ScheduledFunctions(unsigned int reqMax);
|
|
||||||
virtual ~ScheduledFunctions();
|
|
||||||
|
|
||||||
struct ScheduledElement
|
|
||||||
{
|
|
||||||
ScheduledFunctions* _this;
|
|
||||||
bool continuous;
|
|
||||||
ScheduledRegistration registration;
|
|
||||||
ScheduledFunction function;
|
|
||||||
};
|
|
||||||
|
|
||||||
ScheduledRegistration insertElement(ScheduledElement se, bool front);
|
|
||||||
std::list<ScheduledElement>::iterator eraseElement(std::list<ScheduledElement>::iterator);
|
|
||||||
bool scheduleFunction(ScheduledFunction sf, bool continuous, bool front);
|
|
||||||
bool scheduleFunction(ScheduledFunction sf);
|
|
||||||
ScheduledRegistration scheduleFunctionReg (ScheduledFunction sf, bool continuous, bool front);
|
|
||||||
static void runScheduledFunctions();
|
|
||||||
void removeFunction(ScheduledRegistration sr);
|
|
||||||
|
|
||||||
|
|
||||||
static std::list<ScheduledElement> scheduledFunctions;
|
|
||||||
unsigned int maxElements;
|
|
||||||
unsigned int countElements = 0;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* SCHEDULEDFUNCTIONS_H_ */
|
|
@ -26,8 +26,13 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "pgmspace.h"
|
||||||
|
#include "debug.h"
|
||||||
#include "StackThunk.h"
|
#include "StackThunk.h"
|
||||||
#include <ets_sys.h>
|
#include <ets_sys.h>
|
||||||
|
#include <umm_malloc/umm_malloc.h>
|
||||||
|
#include <umm_malloc/umm_heap_select.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
@ -36,7 +41,8 @@ uint32_t *stack_thunk_top = NULL;
|
|||||||
uint32_t *stack_thunk_save = NULL; /* Saved A1 while in BearSSL */
|
uint32_t *stack_thunk_save = NULL; /* Saved A1 while in BearSSL */
|
||||||
uint32_t stack_thunk_refcnt = 0;
|
uint32_t stack_thunk_refcnt = 0;
|
||||||
|
|
||||||
#define _stackSize (5600/4)
|
/* Largest stack usage seen in the wild at 6120 */
|
||||||
|
#define _stackSize (6200/4)
|
||||||
#define _stackPaint 0xdeadbeef
|
#define _stackPaint 0xdeadbeef
|
||||||
|
|
||||||
/* Add a reference, and allocate the stack if necessary */
|
/* Add a reference, and allocate the stack if necessary */
|
||||||
@ -44,7 +50,19 @@ void stack_thunk_add_ref()
|
|||||||
{
|
{
|
||||||
stack_thunk_refcnt++;
|
stack_thunk_refcnt++;
|
||||||
if (stack_thunk_refcnt == 1) {
|
if (stack_thunk_refcnt == 1) {
|
||||||
|
DBG_MMU_PRINTF("\nStackThunk malloc(%u)\n", _stackSize * sizeof(uint32_t));
|
||||||
|
// The stack must be in DRAM, or an Soft WDT will follow. Not sure why,
|
||||||
|
// maybe too much time is consumed with the non32-bit exception handler.
|
||||||
|
// Also, interrupt handling on an IRAM stack would be very slow.
|
||||||
|
// Strings on the stack would be very slow to access as well.
|
||||||
|
HeapSelectDram ephemeral;
|
||||||
stack_thunk_ptr = (uint32_t *)malloc(_stackSize * sizeof(uint32_t));
|
stack_thunk_ptr = (uint32_t *)malloc(_stackSize * sizeof(uint32_t));
|
||||||
|
DBG_MMU_PRINTF("StackThunk stack_thunk_ptr: %p\n", stack_thunk_ptr);
|
||||||
|
if (!stack_thunk_ptr) {
|
||||||
|
// This is a fatal error, stop the sketch
|
||||||
|
DEBUGV("Unable to allocate BearSSL stack\n");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
stack_thunk_top = stack_thunk_ptr + _stackSize - 1;
|
stack_thunk_top = stack_thunk_ptr + _stackSize - 1;
|
||||||
stack_thunk_save = NULL;
|
stack_thunk_save = NULL;
|
||||||
stack_thunk_repaint();
|
stack_thunk_repaint();
|
||||||
@ -110,18 +128,25 @@ uint32_t stack_thunk_get_max_usage()
|
|||||||
/* Print the stack from the first used 16-byte chunk to the top, decodable by the exception decoder */
|
/* Print the stack from the first used 16-byte chunk to the top, decodable by the exception decoder */
|
||||||
void stack_thunk_dump_stack()
|
void stack_thunk_dump_stack()
|
||||||
{
|
{
|
||||||
uint32_t *pos = stack_thunk_top;
|
uint32_t *pos = stack_thunk_ptr;
|
||||||
while (pos < stack_thunk_ptr) {
|
while (pos < stack_thunk_top) {
|
||||||
if ((pos[0] != _stackPaint) || (pos[1] != _stackPaint) || (pos[2] != _stackPaint) || (pos[3] != _stackPaint))
|
if ((pos[0] != _stackPaint) || (pos[1] != _stackPaint) || (pos[2] != _stackPaint) || (pos[3] != _stackPaint))
|
||||||
break;
|
break;
|
||||||
pos += 4;
|
pos += 4;
|
||||||
}
|
}
|
||||||
ets_printf(">>>stack>>>\n");
|
ets_printf(">>>stack>>>\n");
|
||||||
while (pos < stack_thunk_ptr) {
|
while (pos < stack_thunk_top) {
|
||||||
ets_printf("%08x: %08x %08x %08x %08x\n", (int32_t)pos, pos[0], pos[1], pos[2], pos[3]);
|
ets_printf("%08x: %08x %08x %08x %08x\n", (int32_t)pos, pos[0], pos[1], pos[2], pos[3]);
|
||||||
pos += 4;
|
pos += 4;
|
||||||
}
|
}
|
||||||
ets_printf("<<<stack<<<\n");
|
ets_printf("<<<stack<<<\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Called when the stack overflow is detected by a thunk. Main memory is corrupted at this point. Do not return. */
|
||||||
|
void stack_thunk_fatal_overflow()
|
||||||
|
{
|
||||||
|
ets_printf("FATAL ERROR: BSSL stack overflow\n");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -41,6 +41,7 @@ extern uint32_t stack_thunk_get_stack_bot();
|
|||||||
extern uint32_t stack_thunk_get_cont_sp();
|
extern uint32_t stack_thunk_get_cont_sp();
|
||||||
extern uint32_t stack_thunk_get_max_usage();
|
extern uint32_t stack_thunk_get_max_usage();
|
||||||
extern void stack_thunk_dump_stack();
|
extern void stack_thunk_dump_stack();
|
||||||
|
extern void stack_thunk_fatal_overflow();
|
||||||
|
|
||||||
// Globals required for thunking operation
|
// Globals required for thunking operation
|
||||||
extern uint32_t *stack_thunk_ptr;
|
extern uint32_t *stack_thunk_ptr;
|
||||||
@ -50,9 +51,10 @@ extern uint32_t stack_thunk_refcnt;
|
|||||||
|
|
||||||
// Thunking macro
|
// Thunking macro
|
||||||
#define make_stack_thunk(fcnToThunk) \
|
#define make_stack_thunk(fcnToThunk) \
|
||||||
__asm("\n\
|
__asm__ ("\n\
|
||||||
.text\n\
|
.text\n\
|
||||||
.literal_position\n\
|
.literal_position\n\
|
||||||
|
.literal .LC_STACK_VALUE"#fcnToThunk", 0xdeadbeef\n\
|
||||||
\n\
|
\n\
|
||||||
.text\n\
|
.text\n\
|
||||||
.global thunk_"#fcnToThunk"\n\
|
.global thunk_"#fcnToThunk"\n\
|
||||||
@ -67,6 +69,14 @@ thunk_"#fcnToThunk":\n\
|
|||||||
movi a15, stack_thunk_top /* Load A1(SP) with thunk stack */\n\
|
movi a15, stack_thunk_top /* Load A1(SP) with thunk stack */\n\
|
||||||
l32i.n a1, a15, 0\n\
|
l32i.n a1, a15, 0\n\
|
||||||
call0 "#fcnToThunk" /* Do the call */\n\
|
call0 "#fcnToThunk" /* Do the call */\n\
|
||||||
|
/* Check the stack canary wasn't overwritten */\n\
|
||||||
|
movi a15, stack_thunk_ptr\n\
|
||||||
|
l32i.n a15, a15, 0 /* A15 now has the pointer to stack end*/ \n\
|
||||||
|
l32i.n a15, a15, 0 /* A15 now has contents of last stack entry */\n\
|
||||||
|
l32r a0, .LC_STACK_VALUE"#fcnToThunk" /* A0 now has the check value */\n\
|
||||||
|
beq a0, a15, .L1"#fcnToThunk"\n\
|
||||||
|
call0 stack_thunk_fatal_overflow\n\
|
||||||
|
.L1"#fcnToThunk":\n\
|
||||||
movi a15, stack_thunk_save /* Restore A1(SP) */\n\
|
movi a15, stack_thunk_save /* Restore A1(SP) */\n\
|
||||||
l32i.n a1, a15, 0\n\
|
l32i.n a1, a15, 0\n\
|
||||||
l32i.n a15, a1, 8 /* Restore the saved registers */\n\
|
l32i.n a15, a1, 8 /* Restore the saved registers */\n\
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Stream.h>
|
#include <Stream.h>
|
||||||
|
|
||||||
#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait
|
#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait
|
||||||
#define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field
|
#define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field
|
||||||
|
|
||||||
@ -33,6 +34,8 @@ int Stream::timedRead() {
|
|||||||
c = read();
|
c = read();
|
||||||
if(c >= 0)
|
if(c >= 0)
|
||||||
return c;
|
return c;
|
||||||
|
if(_timeout == 0)
|
||||||
|
return -1;
|
||||||
yield();
|
yield();
|
||||||
} while(millis() - _startMillis < _timeout);
|
} while(millis() - _startMillis < _timeout);
|
||||||
return -1; // -1 indicates timeout
|
return -1; // -1 indicates timeout
|
||||||
@ -46,6 +49,8 @@ int Stream::timedPeek() {
|
|||||||
c = peek();
|
c = peek();
|
||||||
if(c >= 0)
|
if(c >= 0)
|
||||||
return c;
|
return c;
|
||||||
|
if(_timeout == 0)
|
||||||
|
return -1;
|
||||||
yield();
|
yield();
|
||||||
} while(millis() - _startMillis < _timeout);
|
} while(millis() - _startMillis < _timeout);
|
||||||
return -1; // -1 indicates timeout
|
return -1; // -1 indicates timeout
|
||||||
@ -169,7 +174,7 @@ float Stream::parseFloat(char skipChar) {
|
|||||||
boolean isFraction = false;
|
boolean isFraction = false;
|
||||||
long value = 0;
|
long value = 0;
|
||||||
int c;
|
int c;
|
||||||
float fraction = 1.0;
|
float fraction = 1.0f;
|
||||||
|
|
||||||
c = peekNextDigit();
|
c = peekNextDigit();
|
||||||
// ignore non numeric leading characters
|
// ignore non numeric leading characters
|
||||||
@ -186,7 +191,7 @@ float Stream::parseFloat(char skipChar) {
|
|||||||
else if(c >= '0' && c <= '9') { // is c a digit?
|
else if(c >= '0' && c <= '9') { // is c a digit?
|
||||||
value = value * 10 + c - '0';
|
value = value * 10 + c - '0';
|
||||||
if(isFraction)
|
if(isFraction)
|
||||||
fraction *= 0.1;
|
fraction *= 0.1f;
|
||||||
}
|
}
|
||||||
read(); // consume the character we got with peek
|
read(); // consume the character we got with peek
|
||||||
c = timedPeek();
|
c = timedPeek();
|
||||||
@ -206,6 +211,8 @@ float Stream::parseFloat(char skipChar) {
|
|||||||
// the buffer is NOT null terminated.
|
// the buffer is NOT null terminated.
|
||||||
//
|
//
|
||||||
size_t Stream::readBytes(char *buffer, size_t length) {
|
size_t Stream::readBytes(char *buffer, size_t length) {
|
||||||
|
IAMSLOW();
|
||||||
|
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
while(count < length) {
|
while(count < length) {
|
||||||
int c = timedRead();
|
int c = timedRead();
|
||||||
@ -255,3 +262,19 @@ String Stream::readStringUntil(char terminator) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// read what can be read, immediate exit on unavailable data
|
||||||
|
// prototype similar to Arduino's `int Client::read(buf, len)`
|
||||||
|
int Stream::read (uint8_t* buffer, size_t maxLen)
|
||||||
|
{
|
||||||
|
IAMSLOW();
|
||||||
|
|
||||||
|
size_t nbread = 0;
|
||||||
|
while (nbread < maxLen && available())
|
||||||
|
{
|
||||||
|
int c = read();
|
||||||
|
if (c == -1)
|
||||||
|
break;
|
||||||
|
buffer[nbread++] = read();
|
||||||
|
}
|
||||||
|
return nbread;
|
||||||
|
}
|
||||||
|
@ -22,10 +22,13 @@
|
|||||||
#ifndef Stream_h
|
#ifndef Stream_h
|
||||||
#define Stream_h
|
#define Stream_h
|
||||||
|
|
||||||
|
#include <debug.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include "Print.h"
|
#include <Print.h>
|
||||||
|
#include <PolledTimeout.h>
|
||||||
|
#include <sys/types.h> // ssize_t
|
||||||
|
|
||||||
// compatability macros for testing
|
// compatibility macros for testing
|
||||||
/*
|
/*
|
||||||
#define getInt() parseInt()
|
#define getInt() parseInt()
|
||||||
#define getInt(skipChar) parseInt(skipchar)
|
#define getInt(skipChar) parseInt(skipchar)
|
||||||
@ -35,9 +38,18 @@
|
|||||||
readBytesBetween( pre_string, terminator, buffer, length)
|
readBytesBetween( pre_string, terminator, buffer, length)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Arduino `Client: public Stream` class defines `virtual int read(uint8_t *buf, size_t size) = 0;`
|
||||||
|
// This function is now imported into `Stream::` for `Stream::send*()`.
|
||||||
|
// Other classes inheriting from `Stream::` and implementing `read(uint8_t *buf, size_t size)`
|
||||||
|
// must consequently use `int` as return type, namely Hardware/SoftwareSerial, FileSystems...
|
||||||
|
#define STREAM_READ_RETURNS_INT 1
|
||||||
|
|
||||||
|
// Stream::send API is present
|
||||||
|
#define STREAMSEND_API 1
|
||||||
|
|
||||||
class Stream: public Print {
|
class Stream: public Print {
|
||||||
protected:
|
protected:
|
||||||
unsigned long _timeout; // number of milliseconds to wait for the next char before aborting timed read
|
unsigned long _timeout = 1000; // number of milliseconds to wait for the next char before aborting timed read
|
||||||
unsigned long _startMillis; // used for timeout measurement
|
unsigned long _startMillis; // used for timeout measurement
|
||||||
int timedRead(); // private method to read stream with timeout
|
int timedRead(); // private method to read stream with timeout
|
||||||
int timedPeek(); // private method to peek stream with timeout
|
int timedPeek(); // private method to peek stream with timeout
|
||||||
@ -48,13 +60,12 @@ class Stream: public Print {
|
|||||||
virtual int read() = 0;
|
virtual int read() = 0;
|
||||||
virtual int peek() = 0;
|
virtual int peek() = 0;
|
||||||
|
|
||||||
Stream() {
|
Stream() {}
|
||||||
_timeout = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsing methods
|
// parsing methods
|
||||||
|
|
||||||
void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second
|
void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second
|
||||||
|
unsigned long getTimeout () const { return _timeout; }
|
||||||
|
|
||||||
bool find(const char *target); // reads data from the stream until the target string is found
|
bool find(const char *target); // reads data from the stream until the target string is found
|
||||||
bool find(uint8_t *target) {
|
bool find(uint8_t *target) {
|
||||||
@ -104,12 +115,114 @@ class Stream: public Print {
|
|||||||
virtual String readString();
|
virtual String readString();
|
||||||
String readStringUntil(char terminator);
|
String readStringUntil(char terminator);
|
||||||
|
|
||||||
|
virtual int read (uint8_t* buffer, size_t len);
|
||||||
|
int read (char* buffer, size_t len) { return read((uint8_t*)buffer, len); }
|
||||||
|
|
||||||
|
//////////////////// extension: direct access to input buffer
|
||||||
|
// to provide when possible a pointer to available data for read
|
||||||
|
|
||||||
|
// informs user and ::to*() on effective buffered peek API implementation
|
||||||
|
// by default: not available
|
||||||
|
virtual bool hasPeekBufferAPI () const { return false; }
|
||||||
|
|
||||||
|
// returns number of byte accessible by peekBuffer()
|
||||||
|
virtual size_t peekAvailable () { return 0; }
|
||||||
|
|
||||||
|
// returns a pointer to available data buffer (size = peekAvailable())
|
||||||
|
// semantic forbids any kind of ::read()
|
||||||
|
// - after calling peekBuffer()
|
||||||
|
// - and before calling peekConsume()
|
||||||
|
virtual const char* peekBuffer () { return nullptr; }
|
||||||
|
|
||||||
|
// consumes bytes after peekBuffer() use
|
||||||
|
// (then ::read() is allowed)
|
||||||
|
virtual void peekConsume (size_t consume) { (void)consume; }
|
||||||
|
|
||||||
|
// by default read timeout is possible (incoming data from network,serial..)
|
||||||
|
// children can override to false (like String::)
|
||||||
|
virtual bool inputCanTimeout () { return true; }
|
||||||
|
|
||||||
|
// (outputCanTimeout() is defined in Print::)
|
||||||
|
|
||||||
|
////////////////////////
|
||||||
|
//////////////////// extensions: Streaming streams to streams
|
||||||
|
// Stream::send*()
|
||||||
|
//
|
||||||
|
// Stream::send*() uses 1-copy transfers when peekBuffer API is
|
||||||
|
// available, or makes a regular transfer through a temporary buffer.
|
||||||
|
//
|
||||||
|
// - for efficiency, Stream classes should implement peekAPI when
|
||||||
|
// possible
|
||||||
|
// - for an efficient timeout management, Print/Stream classes
|
||||||
|
// should implement {output,input}CanTimeout()
|
||||||
|
|
||||||
|
using oneShotMs = esp8266::polledTimeout::oneShotFastMs;
|
||||||
|
static constexpr int temporaryStackBufferSize = 64;
|
||||||
|
|
||||||
|
// ::send*() methods:
|
||||||
|
// - always stop before timeout when "no-more-input-possible-data"
|
||||||
|
// or "no-more-output-possible-data" condition is met
|
||||||
|
// - always return number of transfered bytes
|
||||||
|
// When result is 0 or less than requested maxLen, Print::getLastSend()
|
||||||
|
// contains an error reason.
|
||||||
|
|
||||||
|
// transfers already buffered / immediately available data (no timeout)
|
||||||
|
// returns number of transfered bytes
|
||||||
|
size_t sendAvailable (Print* to) { return sendGeneric(to, -1, -1, oneShotMs::alwaysExpired); }
|
||||||
|
size_t sendAvailable (Print& to) { return sendAvailable(&to); }
|
||||||
|
|
||||||
|
// transfers data until timeout
|
||||||
|
// returns number of transfered bytes
|
||||||
|
size_t sendAll (Print* to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, -1, timeoutMs); }
|
||||||
|
size_t sendAll (Print& to, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendAll(&to, timeoutMs); }
|
||||||
|
|
||||||
|
// transfers data until a char is encountered (the char is swallowed but not transfered) with timeout
|
||||||
|
// returns number of transfered bytes
|
||||||
|
size_t sendUntil (Print* to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, -1, readUntilChar, timeoutMs); }
|
||||||
|
size_t sendUntil (Print& to, const int readUntilChar, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendUntil(&to, readUntilChar, timeoutMs); }
|
||||||
|
|
||||||
|
// transfers data until requested size or timeout
|
||||||
|
// returns number of transfered bytes
|
||||||
|
size_t sendSize (Print* to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendGeneric(to, maxLen, -1, timeoutMs); }
|
||||||
|
size_t sendSize (Print& to, const ssize_t maxLen, const oneShotMs::timeType timeoutMs = oneShotMs::neverExpires) { return sendSize(&to, maxLen, timeoutMs); }
|
||||||
|
|
||||||
|
// remaining size (-1 by default = unknown)
|
||||||
|
virtual ssize_t streamRemaining () { return -1; }
|
||||||
|
|
||||||
|
enum class Report
|
||||||
|
{
|
||||||
|
Success = 0,
|
||||||
|
TimedOut,
|
||||||
|
ReadError,
|
||||||
|
WriteError,
|
||||||
|
ShortOperation,
|
||||||
|
};
|
||||||
|
|
||||||
|
Report getLastSendReport () const { return _sendReport; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
long parseInt(char skipChar); // as above but the given skipChar is ignored
|
size_t sendGeneric (Print* to,
|
||||||
// as above but the given skipChar is ignored
|
const ssize_t len = -1,
|
||||||
|
const int readUntilChar = -1,
|
||||||
|
oneShotMs::timeType timeoutMs = oneShotMs::neverExpires /* neverExpires=>getTimeout() */);
|
||||||
|
|
||||||
|
size_t SendGenericPeekBuffer(Print* to, const ssize_t len, const int readUntilChar, const oneShotMs::timeType timeoutMs);
|
||||||
|
size_t SendGenericRegularUntil(Print* to, const ssize_t len, const int readUntilChar, const oneShotMs::timeType timeoutMs);
|
||||||
|
size_t SendGenericRegular(Print* to, const ssize_t len, const oneShotMs::timeType timeoutMs);
|
||||||
|
|
||||||
|
void setReport (Report report) { _sendReport = report; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Report _sendReport = Report::Success;
|
||||||
|
|
||||||
|
//////////////////// end of extensions
|
||||||
|
|
||||||
|
protected:
|
||||||
|
long parseInt(char skipChar); // as parseInt() but the given skipChar is ignored
|
||||||
// this allows format characters (typically commas) in values to be ignored
|
// this allows format characters (typically commas) in values to be ignored
|
||||||
|
|
||||||
float parseFloat(char skipChar); // as above but the given skipChar is ignored
|
float parseFloat(char skipChar); // as parseFloat() but the given skipChar is ignored
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
250
cores/esp8266/StreamDev.h
Normal file
250
cores/esp8266/StreamDev.h
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
/*
|
||||||
|
StreamDev.h - Stream helpers
|
||||||
|
Copyright (c) 2019 David Gauchard. All right 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __STREAMDEV_H
|
||||||
|
#define __STREAMDEV_H
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include <esp_priv.h>
|
||||||
|
#include <StreamString.h>
|
||||||
|
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// /dev/null
|
||||||
|
// - black hole as output, swallow everything, availableForWrite = infinite
|
||||||
|
// - black hole as input, nothing to read, available = 0
|
||||||
|
|
||||||
|
class StreamNull: public Stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Print
|
||||||
|
virtual size_t write(uint8_t) override
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t write(const uint8_t* buffer, size_t size) override
|
||||||
|
{
|
||||||
|
(void)buffer;
|
||||||
|
(void)size;
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int availableForWrite() override
|
||||||
|
{
|
||||||
|
return std::numeric_limits<int16_t>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream
|
||||||
|
virtual int available() override
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int read() override
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int peek() override
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t readBytes(char* buffer, size_t len) override
|
||||||
|
{
|
||||||
|
(void)buffer;
|
||||||
|
(void)len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int read(uint8_t* buffer, size_t len) override
|
||||||
|
{
|
||||||
|
(void)buffer;
|
||||||
|
(void)len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool outputCanTimeout() override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool inputCanTimeout() override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ssize_t streamRemaining() override
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// /dev/zero
|
||||||
|
// - black hole as output, swallow everything, availableForWrite = infinite
|
||||||
|
// - big bang as input, gives infinity to read, available = infinite
|
||||||
|
|
||||||
|
class StreamZero: public StreamNull
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
|
||||||
|
char _zero;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
StreamZero(char zero = 0): _zero(zero) { }
|
||||||
|
|
||||||
|
// Stream
|
||||||
|
virtual int available() override
|
||||||
|
{
|
||||||
|
return std::numeric_limits<int16_t>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int read() override
|
||||||
|
{
|
||||||
|
return _zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int peek() override
|
||||||
|
{
|
||||||
|
return _zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t readBytes(char* buffer, size_t len) override
|
||||||
|
{
|
||||||
|
memset(buffer, _zero, len);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int read(uint8_t* buffer, size_t len) override
|
||||||
|
{
|
||||||
|
memset((char*)buffer, _zero, len);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ssize_t streamRemaining() override
|
||||||
|
{
|
||||||
|
return std::numeric_limits<int16_t>::max();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
// static buffer (in flash or ram)
|
||||||
|
// - black hole as output, swallow everything, availableForWrite = infinite
|
||||||
|
// - Stream buffer out as input, resettable
|
||||||
|
|
||||||
|
class StreamConstPtr: public StreamNull
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
const char* _buffer;
|
||||||
|
size_t _size;
|
||||||
|
bool _byteAddressable;
|
||||||
|
size_t _peekPointer = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
StreamConstPtr(const String& string): _buffer(string.c_str()), _size(string.length()), _byteAddressable(true) { }
|
||||||
|
StreamConstPtr(const char* buffer, size_t size): _buffer(buffer), _size(size), _byteAddressable(__byteAddressable(buffer)) { }
|
||||||
|
StreamConstPtr(const uint8_t* buffer, size_t size): _buffer((const char*)buffer), _size(size), _byteAddressable(__byteAddressable(buffer)) { }
|
||||||
|
StreamConstPtr(const __FlashStringHelper* buffer, size_t size): _buffer(reinterpret_cast<const char*>(buffer)), _size(size), _byteAddressable(false) { }
|
||||||
|
StreamConstPtr(const __FlashStringHelper* text): _buffer(reinterpret_cast<const char*>(text)), _size(strlen_P((PGM_P)text)), _byteAddressable(false) { }
|
||||||
|
|
||||||
|
void resetPointer(int pointer = 0)
|
||||||
|
{
|
||||||
|
_peekPointer = pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream
|
||||||
|
virtual int available() override
|
||||||
|
{
|
||||||
|
return peekAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int read() override
|
||||||
|
{
|
||||||
|
// valid with dram, iram and flash
|
||||||
|
return _peekPointer < _size ? pgm_read_byte(&_buffer[_peekPointer++]) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int peek() override
|
||||||
|
{
|
||||||
|
// valid with dram, iram and flash
|
||||||
|
return _peekPointer < _size ? pgm_read_byte(&_buffer[_peekPointer]) : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t readBytes(char* buffer, size_t len) override
|
||||||
|
{
|
||||||
|
if (_peekPointer >= _size)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t cpylen = std::min(_size - _peekPointer, len);
|
||||||
|
memcpy_P(buffer, _buffer + _peekPointer, cpylen); // whether byte adressible is true
|
||||||
|
_peekPointer += cpylen;
|
||||||
|
return cpylen;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int read(uint8_t* buffer, size_t len) override
|
||||||
|
{
|
||||||
|
return readBytes((char*)buffer, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ssize_t streamRemaining() override
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// peekBuffer
|
||||||
|
virtual bool hasPeekBufferAPI() const override
|
||||||
|
{
|
||||||
|
return _byteAddressable;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t peekAvailable() override
|
||||||
|
{
|
||||||
|
return _peekPointer < _size ? _size - _peekPointer : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const char* peekBuffer() override
|
||||||
|
{
|
||||||
|
return _peekPointer < _size ? _buffer + _peekPointer : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void peekConsume(size_t consume) override
|
||||||
|
{
|
||||||
|
_peekPointer += consume;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
Stream& operator << (Stream& out, String& string);
|
||||||
|
Stream& operator << (Stream& out, Stream& stream);
|
||||||
|
Stream& operator << (Stream& out, StreamString& stream);
|
||||||
|
Stream& operator << (Stream& out, const char* text);
|
||||||
|
Stream& operator << (Stream& out, const __FlashStringHelper* text);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////
|
||||||
|
|
||||||
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_STREAMDEV)
|
||||||
|
extern StreamNull devnull;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // __STREAMDEV_H
|
383
cores/esp8266/StreamSend.cpp
Normal file
383
cores/esp8266/StreamSend.cpp
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
/*
|
||||||
|
StreamDev.cpp - 1-copy transfer related methods
|
||||||
|
Copyright (c) 2019 David Gauchard. All right 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
|
||||||
|
|
||||||
|
parsing functions based on TextFinder library by Michael Margolis
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <StreamDev.h>
|
||||||
|
|
||||||
|
size_t Stream::sendGeneric(Print* to,
|
||||||
|
const ssize_t len,
|
||||||
|
const int readUntilChar,
|
||||||
|
const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
|
||||||
|
{
|
||||||
|
setReport(Report::Success);
|
||||||
|
|
||||||
|
if (len == 0)
|
||||||
|
{
|
||||||
|
return 0; // conveniently avoids timeout for no requested data
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are two timeouts:
|
||||||
|
// - read (network, serial, ...)
|
||||||
|
// - write (network, serial, ...)
|
||||||
|
// However
|
||||||
|
// - getTimeout() is for reading only
|
||||||
|
// - there is no getOutputTimeout() api
|
||||||
|
// So we use getTimeout() for both,
|
||||||
|
// (also when inputCanTimeout() is false)
|
||||||
|
|
||||||
|
if (hasPeekBufferAPI())
|
||||||
|
{
|
||||||
|
return SendGenericPeekBuffer(to, len, readUntilChar, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (readUntilChar >= 0)
|
||||||
|
{
|
||||||
|
return SendGenericRegularUntil(to, len, readUntilChar, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return SendGenericRegular(to, len, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t Stream::SendGenericPeekBuffer(Print* to, const ssize_t len, const int readUntilChar, const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
|
||||||
|
{
|
||||||
|
// "neverExpires (default, impossible)" is translated to default timeout
|
||||||
|
esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() : timeoutMs);
|
||||||
|
// len==-1 => maxLen=0 <=> until starvation
|
||||||
|
const size_t maxLen = std::max((ssize_t)0, len);
|
||||||
|
size_t written = 0;
|
||||||
|
|
||||||
|
while (!maxLen || written < maxLen)
|
||||||
|
{
|
||||||
|
size_t avpk = peekAvailable();
|
||||||
|
if (avpk == 0 && !inputCanTimeout())
|
||||||
|
{
|
||||||
|
// no more data to read, ever
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t w = to->availableForWrite();
|
||||||
|
if (w == 0 && !to->outputCanTimeout())
|
||||||
|
{
|
||||||
|
// no more data can be written, ever
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
w = std::min(w, avpk);
|
||||||
|
if (maxLen)
|
||||||
|
{
|
||||||
|
w = std::min(w, maxLen - written);
|
||||||
|
}
|
||||||
|
if (w)
|
||||||
|
{
|
||||||
|
const char* directbuf = peekBuffer();
|
||||||
|
bool foundChar = false;
|
||||||
|
if (readUntilChar >= 0)
|
||||||
|
{
|
||||||
|
const char* last = (const char*)memchr(directbuf, readUntilChar, w);
|
||||||
|
if (last)
|
||||||
|
{
|
||||||
|
w = std::min((size_t)(last - directbuf), w);
|
||||||
|
foundChar = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (w && ((w = to->write(directbuf, w))))
|
||||||
|
{
|
||||||
|
peekConsume(w);
|
||||||
|
written += w;
|
||||||
|
if (maxLen)
|
||||||
|
{
|
||||||
|
timedOut.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (foundChar)
|
||||||
|
{
|
||||||
|
peekConsume(1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!w && !maxLen && readUntilChar < 0)
|
||||||
|
{
|
||||||
|
// nothing has been transferred and no specific condition is requested
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timedOut)
|
||||||
|
{
|
||||||
|
// either (maxLen>0) nothing has been transferred for too long
|
||||||
|
// or readUntilChar >= 0 but char is not encountered for too long
|
||||||
|
// or (maxLen=0) too much time has been spent here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
optimistic_yield(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getLastSendReport() == Report::Success && maxLen > 0)
|
||||||
|
{
|
||||||
|
if (timeoutMs && timedOut)
|
||||||
|
{
|
||||||
|
setReport(Report::TimedOut);
|
||||||
|
}
|
||||||
|
else if ((ssize_t)written != len)
|
||||||
|
{
|
||||||
|
// This is happening when source cannot timeout (ex: a String)
|
||||||
|
// but has not enough data, or a dest has closed or cannot
|
||||||
|
// timeout but is too small (String, buffer...)
|
||||||
|
//
|
||||||
|
// Mark it as an error because user usually wants to get what is
|
||||||
|
// asked for.
|
||||||
|
setReport(Report::ShortOperation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Stream::SendGenericRegularUntil(Print* to, const ssize_t len, const int readUntilChar, const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
|
||||||
|
{
|
||||||
|
// regular Stream API
|
||||||
|
// no other choice than reading byte by byte
|
||||||
|
|
||||||
|
// "neverExpires (default, impossible)" is translated to default timeout
|
||||||
|
esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() : timeoutMs);
|
||||||
|
// len==-1 => maxLen=0 <=> until starvation
|
||||||
|
const size_t maxLen = std::max((ssize_t)0, len);
|
||||||
|
size_t written = 0;
|
||||||
|
|
||||||
|
while (!maxLen || written < maxLen)
|
||||||
|
{
|
||||||
|
size_t avr = available();
|
||||||
|
if (avr == 0 && !inputCanTimeout())
|
||||||
|
{
|
||||||
|
// no more data to read, ever
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t w = to->availableForWrite();
|
||||||
|
if (w == 0 && !to->outputCanTimeout())
|
||||||
|
{
|
||||||
|
// no more data can be written, ever
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int c = read();
|
||||||
|
if (c != -1)
|
||||||
|
{
|
||||||
|
if (c == readUntilChar)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
w = to->write(c);
|
||||||
|
if (w != 1)
|
||||||
|
{
|
||||||
|
setReport(Report::WriteError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
written += 1;
|
||||||
|
if (maxLen)
|
||||||
|
{
|
||||||
|
timedOut.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!w && !maxLen && readUntilChar < 0)
|
||||||
|
{
|
||||||
|
// nothing has been transferred and no specific condition is requested
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timedOut)
|
||||||
|
{
|
||||||
|
// either (maxLen>0) nothing has been transferred for too long
|
||||||
|
// or readUntilChar >= 0 but char is not encountered for too long
|
||||||
|
// or (maxLen=0) too much time has been spent here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
optimistic_yield(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getLastSendReport() == Report::Success && maxLen > 0)
|
||||||
|
{
|
||||||
|
if (timeoutMs && timedOut)
|
||||||
|
{
|
||||||
|
setReport(Report::TimedOut);
|
||||||
|
}
|
||||||
|
else if ((ssize_t)written != len)
|
||||||
|
{
|
||||||
|
// This is happening when source cannot timeout (ex: a String)
|
||||||
|
// but has not enough data, or a dest has closed or cannot
|
||||||
|
// timeout but is too small (String, buffer...)
|
||||||
|
//
|
||||||
|
// Mark it as an error because user usually wants to get what is
|
||||||
|
// asked for.
|
||||||
|
setReport(Report::ShortOperation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Stream::SendGenericRegular(Print* to, const ssize_t len, const esp8266::polledTimeout::oneShotFastMs::timeType timeoutMs)
|
||||||
|
{
|
||||||
|
// regular Stream API
|
||||||
|
// use an intermediary buffer
|
||||||
|
|
||||||
|
// "neverExpires (default, impossible)" is translated to default timeout
|
||||||
|
esp8266::polledTimeout::oneShotFastMs timedOut(timeoutMs >= esp8266::polledTimeout::oneShotFastMs::neverExpires ? getTimeout() : timeoutMs);
|
||||||
|
// len==-1 => maxLen=0 <=> until starvation
|
||||||
|
const size_t maxLen = std::max((ssize_t)0, len);
|
||||||
|
size_t written = 0;
|
||||||
|
|
||||||
|
while (!maxLen || written < maxLen)
|
||||||
|
{
|
||||||
|
size_t avr = available();
|
||||||
|
if (avr == 0 && !inputCanTimeout())
|
||||||
|
{
|
||||||
|
// no more data to read, ever
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t w = to->availableForWrite();
|
||||||
|
if (w == 0 && !to->outputCanTimeout())
|
||||||
|
// no more data can be written, ever
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
w = std::min(w, avr);
|
||||||
|
if (maxLen)
|
||||||
|
{
|
||||||
|
w = std::min(w, maxLen - written);
|
||||||
|
}
|
||||||
|
w = std::min(w, (decltype(w))temporaryStackBufferSize);
|
||||||
|
if (w)
|
||||||
|
{
|
||||||
|
char temp[w];
|
||||||
|
ssize_t r = read(temp, w);
|
||||||
|
if (r < 0)
|
||||||
|
{
|
||||||
|
setReport(Report::ReadError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
w = to->write(temp, r);
|
||||||
|
written += w;
|
||||||
|
if ((size_t)r != w)
|
||||||
|
{
|
||||||
|
setReport(Report::WriteError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (maxLen && w)
|
||||||
|
{
|
||||||
|
timedOut.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!w && !maxLen)
|
||||||
|
{
|
||||||
|
// nothing has been transferred and no specific condition is requested
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timedOut)
|
||||||
|
{
|
||||||
|
// either (maxLen>0) nothing has been transferred for too long
|
||||||
|
// or readUntilChar >= 0 but char is not encountered for too long
|
||||||
|
// or (maxLen=0) too much time has been spent here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
optimistic_yield(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getLastSendReport() == Report::Success && maxLen > 0)
|
||||||
|
{
|
||||||
|
if (timeoutMs && timedOut)
|
||||||
|
{
|
||||||
|
setReport(Report::TimedOut);
|
||||||
|
}
|
||||||
|
else if ((ssize_t)written != len)
|
||||||
|
{
|
||||||
|
// This is happening when source cannot timeout (ex: a String)
|
||||||
|
// but has not enough data, or a dest has closed or cannot
|
||||||
|
// timeout but is too small (String, buffer...)
|
||||||
|
//
|
||||||
|
// Mark it as an error because user usually wants to get what is
|
||||||
|
// asked for.
|
||||||
|
setReport(Report::ShortOperation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream& operator << (Stream& out, String& string)
|
||||||
|
{
|
||||||
|
StreamConstPtr(string).sendAll(out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream& operator << (Stream& out, StreamString& stream)
|
||||||
|
{
|
||||||
|
stream.sendAll(out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream& operator << (Stream& out, Stream& stream)
|
||||||
|
{
|
||||||
|
if (stream.streamRemaining() < 0)
|
||||||
|
{
|
||||||
|
if (stream.inputCanTimeout())
|
||||||
|
{
|
||||||
|
// restrict with only what's buffered on input
|
||||||
|
stream.sendAvailable(out);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// take all what is in input
|
||||||
|
stream.sendAll(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stream.sendSize(out, stream.streamRemaining());
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream& operator << (Stream& out, const char* text)
|
||||||
|
{
|
||||||
|
StreamConstPtr(text).sendAll(out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream& operator << (Stream& out, const __FlashStringHelper* text)
|
||||||
|
{
|
||||||
|
StreamConstPtr(text).sendAll(out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_STREAMDEV)
|
||||||
|
StreamNull devnull;
|
||||||
|
#endif
|
@ -1,67 +0,0 @@
|
|||||||
/**
|
|
||||||
StreamString.cpp
|
|
||||||
|
|
||||||
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 "StreamString.h"
|
|
||||||
|
|
||||||
size_t StreamString::write(const uint8_t *data, size_t size) {
|
|
||||||
if(size && data) {
|
|
||||||
const unsigned int newlen = length() + size;
|
|
||||||
if(reserve(newlen + 1)) {
|
|
||||||
memcpy((void *) (wbuffer() + len()), (const void *) data, size);
|
|
||||||
setLen(newlen);
|
|
||||||
*(wbuffer() + newlen) = 0x00; // add null for string end
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t StreamString::write(uint8_t data) {
|
|
||||||
return concat((char) data);
|
|
||||||
}
|
|
||||||
|
|
||||||
int StreamString::available() {
|
|
||||||
return length();
|
|
||||||
}
|
|
||||||
|
|
||||||
int StreamString::read() {
|
|
||||||
if(length()) {
|
|
||||||
char c = charAt(0);
|
|
||||||
remove(0, 1);
|
|
||||||
return c;
|
|
||||||
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int StreamString::peek() {
|
|
||||||
if(length()) {
|
|
||||||
char c = charAt(0);
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void StreamString::flush() {
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
StreamString.h
|
StreamString.h
|
||||||
|
|
||||||
Copyright (c) 2015 Markus Sattler. All rights reserved.
|
Copyright (c) 2020 D. Gauchard. All rights reserved.
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
This library is free software; you can redistribute it and/or
|
||||||
@ -20,20 +20,266 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef STREAMSTRING_H_
|
#ifndef __STREAMSTRING_H
|
||||||
#define STREAMSTRING_H_
|
#define __STREAMSTRING_H
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
#include "WString.h"
|
||||||
|
|
||||||
class StreamString: public Stream, public String {
|
///////////////////////////////////////////////////////////////
|
||||||
|
// S2Stream points to a String and makes it a Stream
|
||||||
|
// (it is also the helper for StreamString)
|
||||||
|
|
||||||
|
class S2Stream: public Stream
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
size_t write(const uint8_t *buffer, size_t size) override;
|
|
||||||
size_t write(uint8_t data) override;
|
|
||||||
|
|
||||||
int available() override;
|
S2Stream(String& string, int peekPointer = -1):
|
||||||
int read() override;
|
string(&string), peekPointer(peekPointer)
|
||||||
int peek() override;
|
{
|
||||||
void flush() override;
|
}
|
||||||
|
|
||||||
|
S2Stream(String* string, int peekPointer = -1):
|
||||||
|
string(string), peekPointer(peekPointer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int available() override
|
||||||
|
{
|
||||||
|
return string->length();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int availableForWrite() override
|
||||||
|
{
|
||||||
|
return std::numeric_limits<int16_t>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int read() override
|
||||||
|
{
|
||||||
|
if (peekPointer < 0)
|
||||||
|
{
|
||||||
|
// consume chars
|
||||||
|
if (string->length())
|
||||||
|
{
|
||||||
|
char c = string->charAt(0);
|
||||||
|
string->remove(0, 1);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (peekPointer < (int)string->length())
|
||||||
|
{
|
||||||
|
// return pointed and move pointer
|
||||||
|
return string->charAt(peekPointer++);
|
||||||
|
}
|
||||||
|
|
||||||
|
// everything is read
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t write(uint8_t data) override
|
||||||
|
{
|
||||||
|
return string->concat((char)data);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int read(uint8_t* buffer, size_t len) override
|
||||||
|
{
|
||||||
|
if (peekPointer < 0)
|
||||||
|
{
|
||||||
|
// string will be consumed
|
||||||
|
size_t l = std::min(len, (size_t)string->length());
|
||||||
|
memcpy(buffer, string->c_str(), l);
|
||||||
|
string->remove(0, l);
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peekPointer >= (int)string->length())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only the pointer is moved
|
||||||
|
size_t l = std::min(len, (size_t)(string->length() - peekPointer));
|
||||||
|
memcpy(buffer, string->c_str() + peekPointer, l);
|
||||||
|
peekPointer += l;
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t write(const uint8_t* buffer, size_t len) override
|
||||||
|
{
|
||||||
|
return string->concat((const char*)buffer, len) ? len : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int peek() override
|
||||||
|
{
|
||||||
|
if (peekPointer < 0)
|
||||||
|
{
|
||||||
|
if (string->length())
|
||||||
|
{
|
||||||
|
return string->charAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (peekPointer < (int)string->length())
|
||||||
|
{
|
||||||
|
return string->charAt(peekPointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void flush() override
|
||||||
|
{
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool inputCanTimeout() override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool outputCanTimeout() override
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//// Stream's peekBufferAPI
|
||||||
|
|
||||||
|
virtual bool hasPeekBufferAPI() const override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t peekAvailable()
|
||||||
|
{
|
||||||
|
if (peekPointer < 0)
|
||||||
|
{
|
||||||
|
return string->length();
|
||||||
|
}
|
||||||
|
return string->length() - peekPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const char* peekBuffer() override
|
||||||
|
{
|
||||||
|
if (peekPointer < 0)
|
||||||
|
{
|
||||||
|
return string->c_str();
|
||||||
|
}
|
||||||
|
if (peekPointer < (int)string->length())
|
||||||
|
{
|
||||||
|
return string->c_str() + peekPointer;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void peekConsume(size_t consume) override
|
||||||
|
{
|
||||||
|
if (peekPointer < 0)
|
||||||
|
{
|
||||||
|
// string is really consumed
|
||||||
|
string->remove(0, consume);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// only the pointer is moved
|
||||||
|
peekPointer = std::min((size_t)string->length(), peekPointer + consume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ssize_t streamRemaining() override
|
||||||
|
{
|
||||||
|
return peekPointer < 0 ? string->length() : string->length() - peekPointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calling setConsume() will consume bytes as the stream is read
|
||||||
|
// (enabled by default)
|
||||||
|
void setConsume()
|
||||||
|
{
|
||||||
|
peekPointer = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reading this stream will mark the string as read without consuming
|
||||||
|
// (not enabled by default)
|
||||||
|
// Calling resetPointer() resets the read state and allows rereading.
|
||||||
|
void resetPointer(int pointer = 0)
|
||||||
|
{
|
||||||
|
peekPointer = pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
String* string;
|
||||||
|
int peekPointer; // -1:String is consumed / >=0:resettable pointer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif /* STREAMSTRING_H_ */
|
// StreamString is a S2Stream holding the String
|
||||||
|
|
||||||
|
class StreamString: public String, public S2Stream
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void resetpp()
|
||||||
|
{
|
||||||
|
if (peekPointer > 0)
|
||||||
|
{
|
||||||
|
peekPointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
StreamString(StreamString&& bro): String(bro), S2Stream(this) { }
|
||||||
|
StreamString(const StreamString& bro): String(bro), S2Stream(this) { }
|
||||||
|
|
||||||
|
// duplicate String contructors and operator=:
|
||||||
|
|
||||||
|
StreamString(const char* text = nullptr): String(text), S2Stream(this) { }
|
||||||
|
StreamString(const String& string): String(string), S2Stream(this) { }
|
||||||
|
StreamString(const __FlashStringHelper *str): String(str), S2Stream(this) { }
|
||||||
|
StreamString(String&& string): String(string), S2Stream(this) { }
|
||||||
|
|
||||||
|
explicit StreamString(char c): String(c), S2Stream(this) { }
|
||||||
|
explicit StreamString(unsigned char c, unsigned char base = 10): String(c, base), S2Stream(this) { }
|
||||||
|
explicit StreamString(int i, unsigned char base = 10): String(i, base), S2Stream(this) { }
|
||||||
|
explicit StreamString(unsigned int i, unsigned char base = 10): String(i, base), S2Stream(this) { }
|
||||||
|
explicit StreamString(long l, unsigned char base = 10): String(l, base), S2Stream(this) { }
|
||||||
|
explicit StreamString(unsigned long l, unsigned char base = 10): String(l, base), S2Stream(this) { }
|
||||||
|
explicit StreamString(float f, unsigned char decimalPlaces = 2): String(f, decimalPlaces), S2Stream(this) { }
|
||||||
|
explicit StreamString(double d, unsigned char decimalPlaces = 2): String(d, decimalPlaces), S2Stream(this) { }
|
||||||
|
|
||||||
|
StreamString& operator= (const StreamString& rhs)
|
||||||
|
{
|
||||||
|
String::operator=(rhs);
|
||||||
|
resetpp();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamString& operator= (const String& rhs)
|
||||||
|
{
|
||||||
|
String::operator=(rhs);
|
||||||
|
resetpp();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamString& operator= (const char* cstr)
|
||||||
|
{
|
||||||
|
String::operator=(cstr);
|
||||||
|
resetpp();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamString& operator= (const __FlashStringHelper* str)
|
||||||
|
{
|
||||||
|
String::operator=(str);
|
||||||
|
resetpp();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamString& operator= (String&& rval)
|
||||||
|
{
|
||||||
|
String::operator=(rval);
|
||||||
|
resetpp();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __STREAMSTRING_H
|
||||||
|
476
cores/esp8266/TZ.h
Normal file
476
cores/esp8266/TZ.h
Normal file
@ -0,0 +1,476 @@
|
|||||||
|
|
||||||
|
// autogenerated from https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv
|
||||||
|
// by script <esp8266 arduino core>/tools/TZupdate.sh
|
||||||
|
// Thu Nov 12 04:07:03 UTC 2020
|
||||||
|
//
|
||||||
|
// This database is autogenerated from IANA timezone database
|
||||||
|
// https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv
|
||||||
|
// (using https://www.iana.org/time-zones)
|
||||||
|
// and can be updated on demand in this repository
|
||||||
|
// or by yourself using the above script
|
||||||
|
|
||||||
|
#ifndef TZDB_H
|
||||||
|
#define TZDB_H
|
||||||
|
|
||||||
|
#define TZ_Africa_Abidjan PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Accra PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Addis_Ababa PSTR("EAT-3")
|
||||||
|
#define TZ_Africa_Algiers PSTR("CET-1")
|
||||||
|
#define TZ_Africa_Asmara PSTR("EAT-3")
|
||||||
|
#define TZ_Africa_Bamako PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Bangui PSTR("WAT-1")
|
||||||
|
#define TZ_Africa_Banjul PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Bissau PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Blantyre PSTR("CAT-2")
|
||||||
|
#define TZ_Africa_Brazzaville PSTR("WAT-1")
|
||||||
|
#define TZ_Africa_Bujumbura PSTR("CAT-2")
|
||||||
|
#define TZ_Africa_Cairo PSTR("EET-2")
|
||||||
|
#define TZ_Africa_Casablanca PSTR("<+01>-1")
|
||||||
|
#define TZ_Africa_Ceuta PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Africa_Conakry PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Dakar PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Dar_es_Salaam PSTR("EAT-3")
|
||||||
|
#define TZ_Africa_Djibouti PSTR("EAT-3")
|
||||||
|
#define TZ_Africa_Douala PSTR("WAT-1")
|
||||||
|
#define TZ_Africa_El_Aaiun PSTR("<+01>-1")
|
||||||
|
#define TZ_Africa_Freetown PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Gaborone PSTR("CAT-2")
|
||||||
|
#define TZ_Africa_Harare PSTR("CAT-2")
|
||||||
|
#define TZ_Africa_Johannesburg PSTR("SAST-2")
|
||||||
|
#define TZ_Africa_Juba PSTR("EAT-3")
|
||||||
|
#define TZ_Africa_Kampala PSTR("EAT-3")
|
||||||
|
#define TZ_Africa_Khartoum PSTR("CAT-2")
|
||||||
|
#define TZ_Africa_Kigali PSTR("CAT-2")
|
||||||
|
#define TZ_Africa_Kinshasa PSTR("WAT-1")
|
||||||
|
#define TZ_Africa_Lagos PSTR("WAT-1")
|
||||||
|
#define TZ_Africa_Libreville PSTR("WAT-1")
|
||||||
|
#define TZ_Africa_Lome PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Luanda PSTR("WAT-1")
|
||||||
|
#define TZ_Africa_Lubumbashi PSTR("CAT-2")
|
||||||
|
#define TZ_Africa_Lusaka PSTR("CAT-2")
|
||||||
|
#define TZ_Africa_Malabo PSTR("WAT-1")
|
||||||
|
#define TZ_Africa_Maputo PSTR("CAT-2")
|
||||||
|
#define TZ_Africa_Maseru PSTR("SAST-2")
|
||||||
|
#define TZ_Africa_Mbabane PSTR("SAST-2")
|
||||||
|
#define TZ_Africa_Mogadishu PSTR("EAT-3")
|
||||||
|
#define TZ_Africa_Monrovia PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Nairobi PSTR("EAT-3")
|
||||||
|
#define TZ_Africa_Ndjamena PSTR("WAT-1")
|
||||||
|
#define TZ_Africa_Niamey PSTR("WAT-1")
|
||||||
|
#define TZ_Africa_Nouakchott PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Ouagadougou PSTR("GMT0")
|
||||||
|
#define TZ_Africa_PortomNovo PSTR("WAT-1")
|
||||||
|
#define TZ_Africa_Sao_Tome PSTR("GMT0")
|
||||||
|
#define TZ_Africa_Tripoli PSTR("EET-2")
|
||||||
|
#define TZ_Africa_Tunis PSTR("CET-1")
|
||||||
|
#define TZ_Africa_Windhoek PSTR("CAT-2")
|
||||||
|
#define TZ_America_Adak PSTR("HST10HDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Anchorage PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Anguilla PSTR("AST4")
|
||||||
|
#define TZ_America_Antigua PSTR("AST4")
|
||||||
|
#define TZ_America_Araguaina PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_Buenos_Aires PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_Catamarca PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_Cordoba PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_Jujuy PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_La_Rioja PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_Mendoza PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_Rio_Gallegos PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_Salta PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_San_Juan PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_San_Luis PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_Tucuman PSTR("<-03>3")
|
||||||
|
#define TZ_America_Argentina_Ushuaia PSTR("<-03>3")
|
||||||
|
#define TZ_America_Aruba PSTR("AST4")
|
||||||
|
#define TZ_America_Asuncion PSTR("<-04>4<-03>,M10.1.0/0,M3.4.0/0")
|
||||||
|
#define TZ_America_Atikokan PSTR("EST5")
|
||||||
|
#define TZ_America_Bahia PSTR("<-03>3")
|
||||||
|
#define TZ_America_Bahia_Banderas PSTR("CST6CDT,M4.1.0,M10.5.0")
|
||||||
|
#define TZ_America_Barbados PSTR("AST4")
|
||||||
|
#define TZ_America_Belem PSTR("<-03>3")
|
||||||
|
#define TZ_America_Belize PSTR("CST6")
|
||||||
|
#define TZ_America_BlancmSablon PSTR("AST4")
|
||||||
|
#define TZ_America_Boa_Vista PSTR("<-04>4")
|
||||||
|
#define TZ_America_Bogota PSTR("<-05>5")
|
||||||
|
#define TZ_America_Boise PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Cambridge_Bay PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Campo_Grande PSTR("<-04>4")
|
||||||
|
#define TZ_America_Cancun PSTR("EST5")
|
||||||
|
#define TZ_America_Caracas PSTR("<-04>4")
|
||||||
|
#define TZ_America_Cayenne PSTR("<-03>3")
|
||||||
|
#define TZ_America_Cayman PSTR("EST5")
|
||||||
|
#define TZ_America_Chicago PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Chihuahua PSTR("MST7MDT,M4.1.0,M10.5.0")
|
||||||
|
#define TZ_America_Costa_Rica PSTR("CST6")
|
||||||
|
#define TZ_America_Creston PSTR("MST7")
|
||||||
|
#define TZ_America_Cuiaba PSTR("<-04>4")
|
||||||
|
#define TZ_America_Curacao PSTR("AST4")
|
||||||
|
#define TZ_America_Danmarkshavn PSTR("GMT0")
|
||||||
|
#define TZ_America_Dawson PSTR("MST7")
|
||||||
|
#define TZ_America_Dawson_Creek PSTR("MST7")
|
||||||
|
#define TZ_America_Denver PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Detroit PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Dominica PSTR("AST4")
|
||||||
|
#define TZ_America_Edmonton PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Eirunepe PSTR("<-05>5")
|
||||||
|
#define TZ_America_El_Salvador PSTR("CST6")
|
||||||
|
#define TZ_America_Fortaleza PSTR("<-03>3")
|
||||||
|
#define TZ_America_Fort_Nelson PSTR("MST7")
|
||||||
|
#define TZ_America_Glace_Bay PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Godthab PSTR("<-03>3<-02>,M3.5.0/-2,M10.5.0/-1")
|
||||||
|
#define TZ_America_Goose_Bay PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Grand_Turk PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Grenada PSTR("AST4")
|
||||||
|
#define TZ_America_Guadeloupe PSTR("AST4")
|
||||||
|
#define TZ_America_Guatemala PSTR("CST6")
|
||||||
|
#define TZ_America_Guayaquil PSTR("<-05>5")
|
||||||
|
#define TZ_America_Guyana PSTR("<-04>4")
|
||||||
|
#define TZ_America_Halifax PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Havana PSTR("CST5CDT,M3.2.0/0,M11.1.0/1")
|
||||||
|
#define TZ_America_Hermosillo PSTR("MST7")
|
||||||
|
#define TZ_America_Indiana_Indianapolis PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Indiana_Knox PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Indiana_Marengo PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Indiana_Petersburg PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Indiana_Tell_City PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Indiana_Vevay PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Indiana_Vincennes PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Indiana_Winamac PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Inuvik PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Iqaluit PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Jamaica PSTR("EST5")
|
||||||
|
#define TZ_America_Juneau PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Kentucky_Louisville PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Kentucky_Monticello PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Kralendijk PSTR("AST4")
|
||||||
|
#define TZ_America_La_Paz PSTR("<-04>4")
|
||||||
|
#define TZ_America_Lima PSTR("<-05>5")
|
||||||
|
#define TZ_America_Los_Angeles PSTR("PST8PDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Lower_Princes PSTR("AST4")
|
||||||
|
#define TZ_America_Maceio PSTR("<-03>3")
|
||||||
|
#define TZ_America_Managua PSTR("CST6")
|
||||||
|
#define TZ_America_Manaus PSTR("<-04>4")
|
||||||
|
#define TZ_America_Marigot PSTR("AST4")
|
||||||
|
#define TZ_America_Martinique PSTR("AST4")
|
||||||
|
#define TZ_America_Matamoros PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Mazatlan PSTR("MST7MDT,M4.1.0,M10.5.0")
|
||||||
|
#define TZ_America_Menominee PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Merida PSTR("CST6CDT,M4.1.0,M10.5.0")
|
||||||
|
#define TZ_America_Metlakatla PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Mexico_City PSTR("CST6CDT,M4.1.0,M10.5.0")
|
||||||
|
#define TZ_America_Miquelon PSTR("<-03>3<-02>,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Moncton PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Monterrey PSTR("CST6CDT,M4.1.0,M10.5.0")
|
||||||
|
#define TZ_America_Montevideo PSTR("<-03>3")
|
||||||
|
#define TZ_America_Montreal PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Montserrat PSTR("AST4")
|
||||||
|
#define TZ_America_Nassau PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_New_York PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Nipigon PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Nome PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Noronha PSTR("<-02>2")
|
||||||
|
#define TZ_America_North_Dakota_Beulah PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_North_Dakota_Center PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_North_Dakota_New_Salem PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Ojinaga PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Panama PSTR("EST5")
|
||||||
|
#define TZ_America_Pangnirtung PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Paramaribo PSTR("<-03>3")
|
||||||
|
#define TZ_America_Phoenix PSTR("MST7")
|
||||||
|
#define TZ_America_PortmaumPrince PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Port_of_Spain PSTR("AST4")
|
||||||
|
#define TZ_America_Porto_Velho PSTR("<-04>4")
|
||||||
|
#define TZ_America_Puerto_Rico PSTR("AST4")
|
||||||
|
#define TZ_America_Punta_Arenas PSTR("<-03>3")
|
||||||
|
#define TZ_America_Rainy_River PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Rankin_Inlet PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Recife PSTR("<-03>3")
|
||||||
|
#define TZ_America_Regina PSTR("CST6")
|
||||||
|
#define TZ_America_Resolute PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Rio_Branco PSTR("<-05>5")
|
||||||
|
#define TZ_America_Santarem PSTR("<-03>3")
|
||||||
|
#define TZ_America_Santiago PSTR("<-04>4<-03>,M9.1.6/24,M4.1.6/24")
|
||||||
|
#define TZ_America_Santo_Domingo PSTR("AST4")
|
||||||
|
#define TZ_America_Sao_Paulo PSTR("<-03>3")
|
||||||
|
#define TZ_America_Scoresbysund PSTR("<-01>1<+00>,M3.5.0/0,M10.5.0/1")
|
||||||
|
#define TZ_America_Sitka PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_St_Barthelemy PSTR("AST4")
|
||||||
|
#define TZ_America_St_Johns PSTR("NST3:30NDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_St_Kitts PSTR("AST4")
|
||||||
|
#define TZ_America_St_Lucia PSTR("AST4")
|
||||||
|
#define TZ_America_St_Thomas PSTR("AST4")
|
||||||
|
#define TZ_America_St_Vincent PSTR("AST4")
|
||||||
|
#define TZ_America_Swift_Current PSTR("CST6")
|
||||||
|
#define TZ_America_Tegucigalpa PSTR("CST6")
|
||||||
|
#define TZ_America_Thule PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Thunder_Bay PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Tijuana PSTR("PST8PDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Toronto PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Tortola PSTR("AST4")
|
||||||
|
#define TZ_America_Vancouver PSTR("PST8PDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Whitehorse PSTR("MST7")
|
||||||
|
#define TZ_America_Winnipeg PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Yakutat PSTR("AKST9AKDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_America_Yellowknife PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_Antarctica_Casey PSTR("<+11>-11")
|
||||||
|
#define TZ_Antarctica_Davis PSTR("<+07>-7")
|
||||||
|
#define TZ_Antarctica_DumontDUrville PSTR("<+10>-10")
|
||||||
|
#define TZ_Antarctica_Macquarie PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||||
|
#define TZ_Antarctica_Mawson PSTR("<+05>-5")
|
||||||
|
#define TZ_Antarctica_McMurdo PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3")
|
||||||
|
#define TZ_Antarctica_Palmer PSTR("<-03>3")
|
||||||
|
#define TZ_Antarctica_Rothera PSTR("<-03>3")
|
||||||
|
#define TZ_Antarctica_Syowa PSTR("<+03>-3")
|
||||||
|
#define TZ_Antarctica_Troll PSTR("<+00>0<+02>-2,M3.5.0/1,M10.5.0/3")
|
||||||
|
#define TZ_Antarctica_Vostok PSTR("<+06>-6")
|
||||||
|
#define TZ_Arctic_Longyearbyen PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Asia_Aden PSTR("<+03>-3")
|
||||||
|
#define TZ_Asia_Almaty PSTR("<+06>-6")
|
||||||
|
#define TZ_Asia_Amman PSTR("EET-2EEST,M3.5.4/24,M10.5.5/1")
|
||||||
|
#define TZ_Asia_Anadyr PSTR("<+12>-12")
|
||||||
|
#define TZ_Asia_Aqtau PSTR("<+05>-5")
|
||||||
|
#define TZ_Asia_Aqtobe PSTR("<+05>-5")
|
||||||
|
#define TZ_Asia_Ashgabat PSTR("<+05>-5")
|
||||||
|
#define TZ_Asia_Atyrau PSTR("<+05>-5")
|
||||||
|
#define TZ_Asia_Baghdad PSTR("<+03>-3")
|
||||||
|
#define TZ_Asia_Bahrain PSTR("<+03>-3")
|
||||||
|
#define TZ_Asia_Baku PSTR("<+04>-4")
|
||||||
|
#define TZ_Asia_Bangkok PSTR("<+07>-7")
|
||||||
|
#define TZ_Asia_Barnaul PSTR("<+07>-7")
|
||||||
|
#define TZ_Asia_Beirut PSTR("EET-2EEST,M3.5.0/0,M10.5.0/0")
|
||||||
|
#define TZ_Asia_Bishkek PSTR("<+06>-6")
|
||||||
|
#define TZ_Asia_Brunei PSTR("<+08>-8")
|
||||||
|
#define TZ_Asia_Chita PSTR("<+09>-9")
|
||||||
|
#define TZ_Asia_Choibalsan PSTR("<+08>-8")
|
||||||
|
#define TZ_Asia_Colombo PSTR("<+0530>-5:30")
|
||||||
|
#define TZ_Asia_Damascus PSTR("EET-2EEST,M3.5.5/0,M10.5.5/0")
|
||||||
|
#define TZ_Asia_Dhaka PSTR("<+06>-6")
|
||||||
|
#define TZ_Asia_Dili PSTR("<+09>-9")
|
||||||
|
#define TZ_Asia_Dubai PSTR("<+04>-4")
|
||||||
|
#define TZ_Asia_Dushanbe PSTR("<+05>-5")
|
||||||
|
#define TZ_Asia_Famagusta PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Asia_Gaza PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49")
|
||||||
|
#define TZ_Asia_Hebron PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49")
|
||||||
|
#define TZ_Asia_Ho_Chi_Minh PSTR("<+07>-7")
|
||||||
|
#define TZ_Asia_Hong_Kong PSTR("HKT-8")
|
||||||
|
#define TZ_Asia_Hovd PSTR("<+07>-7")
|
||||||
|
#define TZ_Asia_Irkutsk PSTR("<+08>-8")
|
||||||
|
#define TZ_Asia_Jakarta PSTR("WIB-7")
|
||||||
|
#define TZ_Asia_Jayapura PSTR("WIT-9")
|
||||||
|
#define TZ_Asia_Jerusalem PSTR("IST-2IDT,M3.4.4/26,M10.5.0")
|
||||||
|
#define TZ_Asia_Kabul PSTR("<+0430>-4:30")
|
||||||
|
#define TZ_Asia_Kamchatka PSTR("<+12>-12")
|
||||||
|
#define TZ_Asia_Karachi PSTR("PKT-5")
|
||||||
|
#define TZ_Asia_Kathmandu PSTR("<+0545>-5:45")
|
||||||
|
#define TZ_Asia_Khandyga PSTR("<+09>-9")
|
||||||
|
#define TZ_Asia_Kolkata PSTR("IST-5:30")
|
||||||
|
#define TZ_Asia_Krasnoyarsk PSTR("<+07>-7")
|
||||||
|
#define TZ_Asia_Kuala_Lumpur PSTR("<+08>-8")
|
||||||
|
#define TZ_Asia_Kuching PSTR("<+08>-8")
|
||||||
|
#define TZ_Asia_Kuwait PSTR("<+03>-3")
|
||||||
|
#define TZ_Asia_Macau PSTR("CST-8")
|
||||||
|
#define TZ_Asia_Magadan PSTR("<+11>-11")
|
||||||
|
#define TZ_Asia_Makassar PSTR("WITA-8")
|
||||||
|
#define TZ_Asia_Manila PSTR("PST-8")
|
||||||
|
#define TZ_Asia_Muscat PSTR("<+04>-4")
|
||||||
|
#define TZ_Asia_Nicosia PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Asia_Novokuznetsk PSTR("<+07>-7")
|
||||||
|
#define TZ_Asia_Novosibirsk PSTR("<+07>-7")
|
||||||
|
#define TZ_Asia_Omsk PSTR("<+06>-6")
|
||||||
|
#define TZ_Asia_Oral PSTR("<+05>-5")
|
||||||
|
#define TZ_Asia_Phnom_Penh PSTR("<+07>-7")
|
||||||
|
#define TZ_Asia_Pontianak PSTR("WIB-7")
|
||||||
|
#define TZ_Asia_Pyongyang PSTR("KST-9")
|
||||||
|
#define TZ_Asia_Qatar PSTR("<+03>-3")
|
||||||
|
#define TZ_Asia_Qyzylorda PSTR("<+05>-5")
|
||||||
|
#define TZ_Asia_Riyadh PSTR("<+03>-3")
|
||||||
|
#define TZ_Asia_Sakhalin PSTR("<+11>-11")
|
||||||
|
#define TZ_Asia_Samarkand PSTR("<+05>-5")
|
||||||
|
#define TZ_Asia_Seoul PSTR("KST-9")
|
||||||
|
#define TZ_Asia_Shanghai PSTR("CST-8")
|
||||||
|
#define TZ_Asia_Singapore PSTR("<+08>-8")
|
||||||
|
#define TZ_Asia_Srednekolymsk PSTR("<+11>-11")
|
||||||
|
#define TZ_Asia_Taipei PSTR("CST-8")
|
||||||
|
#define TZ_Asia_Tashkent PSTR("<+05>-5")
|
||||||
|
#define TZ_Asia_Tbilisi PSTR("<+04>-4")
|
||||||
|
#define TZ_Asia_Tehran PSTR("<+0330>-3:30<+0430>,J79/24,J263/24")
|
||||||
|
#define TZ_Asia_Thimphu PSTR("<+06>-6")
|
||||||
|
#define TZ_Asia_Tokyo PSTR("JST-9")
|
||||||
|
#define TZ_Asia_Tomsk PSTR("<+07>-7")
|
||||||
|
#define TZ_Asia_Ulaanbaatar PSTR("<+08>-8")
|
||||||
|
#define TZ_Asia_Urumqi PSTR("<+06>-6")
|
||||||
|
#define TZ_Asia_UstmNera PSTR("<+10>-10")
|
||||||
|
#define TZ_Asia_Vientiane PSTR("<+07>-7")
|
||||||
|
#define TZ_Asia_Vladivostok PSTR("<+10>-10")
|
||||||
|
#define TZ_Asia_Yakutsk PSTR("<+09>-9")
|
||||||
|
#define TZ_Asia_Yangon PSTR("<+0630>-6:30")
|
||||||
|
#define TZ_Asia_Yekaterinburg PSTR("<+05>-5")
|
||||||
|
#define TZ_Asia_Yerevan PSTR("<+04>-4")
|
||||||
|
#define TZ_Atlantic_Azores PSTR("<-01>1<+00>,M3.5.0/0,M10.5.0/1")
|
||||||
|
#define TZ_Atlantic_Bermuda PSTR("AST4ADT,M3.2.0,M11.1.0")
|
||||||
|
#define TZ_Atlantic_Canary PSTR("WET0WEST,M3.5.0/1,M10.5.0")
|
||||||
|
#define TZ_Atlantic_Cape_Verde PSTR("<-01>1")
|
||||||
|
#define TZ_Atlantic_Faroe PSTR("WET0WEST,M3.5.0/1,M10.5.0")
|
||||||
|
#define TZ_Atlantic_Madeira PSTR("WET0WEST,M3.5.0/1,M10.5.0")
|
||||||
|
#define TZ_Atlantic_Reykjavik PSTR("GMT0")
|
||||||
|
#define TZ_Atlantic_South_Georgia PSTR("<-02>2")
|
||||||
|
#define TZ_Atlantic_Stanley PSTR("<-03>3")
|
||||||
|
#define TZ_Atlantic_St_Helena PSTR("GMT0")
|
||||||
|
#define TZ_Australia_Adelaide PSTR("ACST-9:30ACDT,M10.1.0,M4.1.0/3")
|
||||||
|
#define TZ_Australia_Brisbane PSTR("AEST-10")
|
||||||
|
#define TZ_Australia_Broken_Hill PSTR("ACST-9:30ACDT,M10.1.0,M4.1.0/3")
|
||||||
|
#define TZ_Australia_Currie PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||||
|
#define TZ_Australia_Darwin PSTR("ACST-9:30")
|
||||||
|
#define TZ_Australia_Eucla PSTR("<+0845>-8:45")
|
||||||
|
#define TZ_Australia_Hobart PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||||
|
#define TZ_Australia_Lindeman PSTR("AEST-10")
|
||||||
|
#define TZ_Australia_Lord_Howe PSTR("<+1030>-10:30<+11>-11,M10.1.0,M4.1.0")
|
||||||
|
#define TZ_Australia_Melbourne PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||||
|
#define TZ_Australia_Perth PSTR("AWST-8")
|
||||||
|
#define TZ_Australia_Sydney PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||||
|
#define TZ_Europe_Amsterdam PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Andorra PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Astrakhan PSTR("<+04>-4")
|
||||||
|
#define TZ_Europe_Athens PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Europe_Belgrade PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Berlin PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Bratislava PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Brussels PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Bucharest PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Europe_Budapest PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Busingen PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Chisinau PSTR("EET-2EEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Copenhagen PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Dublin PSTR("IST-1GMT0,M10.5.0,M3.5.0/1")
|
||||||
|
#define TZ_Europe_Gibraltar PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Guernsey PSTR("GMT0BST,M3.5.0/1,M10.5.0")
|
||||||
|
#define TZ_Europe_Helsinki PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Europe_Isle_of_Man PSTR("GMT0BST,M3.5.0/1,M10.5.0")
|
||||||
|
#define TZ_Europe_Istanbul PSTR("<+03>-3")
|
||||||
|
#define TZ_Europe_Jersey PSTR("GMT0BST,M3.5.0/1,M10.5.0")
|
||||||
|
#define TZ_Europe_Kaliningrad PSTR("EET-2")
|
||||||
|
#define TZ_Europe_Kiev PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Europe_Kirov PSTR("<+03>-3")
|
||||||
|
#define TZ_Europe_Lisbon PSTR("WET0WEST,M3.5.0/1,M10.5.0")
|
||||||
|
#define TZ_Europe_Ljubljana PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_London PSTR("GMT0BST,M3.5.0/1,M10.5.0")
|
||||||
|
#define TZ_Europe_Luxembourg PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Madrid PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Malta PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Mariehamn PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Europe_Minsk PSTR("<+03>-3")
|
||||||
|
#define TZ_Europe_Monaco PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Moscow PSTR("MSK-3")
|
||||||
|
#define TZ_Europe_Oslo PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Paris PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Podgorica PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Prague PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Riga PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Europe_Rome PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Samara PSTR("<+04>-4")
|
||||||
|
#define TZ_Europe_San_Marino PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Sarajevo PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Saratov PSTR("<+04>-4")
|
||||||
|
#define TZ_Europe_Simferopol PSTR("MSK-3")
|
||||||
|
#define TZ_Europe_Skopje PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Sofia PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Europe_Stockholm PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Tallinn PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Europe_Tirane PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Ulyanovsk PSTR("<+04>-4")
|
||||||
|
#define TZ_Europe_Uzhgorod PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Europe_Vaduz PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Vatican PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Vienna PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Vilnius PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Europe_Volgograd PSTR("<+04>-4")
|
||||||
|
#define TZ_Europe_Warsaw PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Zagreb PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Europe_Zaporozhye PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
|
#define TZ_Europe_Zurich PSTR("CET-1CEST,M3.5.0,M10.5.0/3")
|
||||||
|
#define TZ_Indian_Antananarivo PSTR("EAT-3")
|
||||||
|
#define TZ_Indian_Chagos PSTR("<+06>-6")
|
||||||
|
#define TZ_Indian_Christmas PSTR("<+07>-7")
|
||||||
|
#define TZ_Indian_Cocos PSTR("<+0630>-6:30")
|
||||||
|
#define TZ_Indian_Comoro PSTR("EAT-3")
|
||||||
|
#define TZ_Indian_Kerguelen PSTR("<+05>-5")
|
||||||
|
#define TZ_Indian_Mahe PSTR("<+04>-4")
|
||||||
|
#define TZ_Indian_Maldives PSTR("<+05>-5")
|
||||||
|
#define TZ_Indian_Mauritius PSTR("<+04>-4")
|
||||||
|
#define TZ_Indian_Mayotte PSTR("EAT-3")
|
||||||
|
#define TZ_Indian_Reunion PSTR("<+04>-4")
|
||||||
|
#define TZ_Pacific_Apia PSTR("<+13>-13<+14>,M9.5.0/3,M4.1.0/4")
|
||||||
|
#define TZ_Pacific_Auckland PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3")
|
||||||
|
#define TZ_Pacific_Bougainville PSTR("<+11>-11")
|
||||||
|
#define TZ_Pacific_Chatham PSTR("<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45")
|
||||||
|
#define TZ_Pacific_Chuuk PSTR("<+10>-10")
|
||||||
|
#define TZ_Pacific_Easter PSTR("<-06>6<-05>,M9.1.6/22,M4.1.6/22")
|
||||||
|
#define TZ_Pacific_Efate PSTR("<+11>-11")
|
||||||
|
#define TZ_Pacific_Enderbury PSTR("<+13>-13")
|
||||||
|
#define TZ_Pacific_Fakaofo PSTR("<+13>-13")
|
||||||
|
#define TZ_Pacific_Fiji PSTR("<+12>-12<+13>,M11.2.0,M1.2.3/99")
|
||||||
|
#define TZ_Pacific_Funafuti PSTR("<+12>-12")
|
||||||
|
#define TZ_Pacific_Galapagos PSTR("<-06>6")
|
||||||
|
#define TZ_Pacific_Gambier PSTR("<-09>9")
|
||||||
|
#define TZ_Pacific_Guadalcanal PSTR("<+11>-11")
|
||||||
|
#define TZ_Pacific_Guam PSTR("ChST-10")
|
||||||
|
#define TZ_Pacific_Honolulu PSTR("HST10")
|
||||||
|
#define TZ_Pacific_Kiritimati PSTR("<+14>-14")
|
||||||
|
#define TZ_Pacific_Kosrae PSTR("<+11>-11")
|
||||||
|
#define TZ_Pacific_Kwajalein PSTR("<+12>-12")
|
||||||
|
#define TZ_Pacific_Majuro PSTR("<+12>-12")
|
||||||
|
#define TZ_Pacific_Marquesas PSTR("<-0930>9:30")
|
||||||
|
#define TZ_Pacific_Midway PSTR("SST11")
|
||||||
|
#define TZ_Pacific_Nauru PSTR("<+12>-12")
|
||||||
|
#define TZ_Pacific_Niue PSTR("<-11>11")
|
||||||
|
#define TZ_Pacific_Norfolk PSTR("<+11>-11<+12>,M10.1.0,M4.1.0/3")
|
||||||
|
#define TZ_Pacific_Noumea PSTR("<+11>-11")
|
||||||
|
#define TZ_Pacific_Pago_Pago PSTR("SST11")
|
||||||
|
#define TZ_Pacific_Palau PSTR("<+09>-9")
|
||||||
|
#define TZ_Pacific_Pitcairn PSTR("<-08>8")
|
||||||
|
#define TZ_Pacific_Pohnpei PSTR("<+11>-11")
|
||||||
|
#define TZ_Pacific_Port_Moresby PSTR("<+10>-10")
|
||||||
|
#define TZ_Pacific_Rarotonga PSTR("<-10>10")
|
||||||
|
#define TZ_Pacific_Saipan PSTR("ChST-10")
|
||||||
|
#define TZ_Pacific_Tahiti PSTR("<-10>10")
|
||||||
|
#define TZ_Pacific_Tarawa PSTR("<+12>-12")
|
||||||
|
#define TZ_Pacific_Tongatapu PSTR("<+13>-13")
|
||||||
|
#define TZ_Pacific_Wake PSTR("<+12>-12")
|
||||||
|
#define TZ_Pacific_Wallis PSTR("<+12>-12")
|
||||||
|
#define TZ_Etc_GMT PSTR("GMT0")
|
||||||
|
#define TZ_Etc_GMTm0 PSTR("GMT0")
|
||||||
|
#define TZ_Etc_GMTm1 PSTR("<+01>-1")
|
||||||
|
#define TZ_Etc_GMTm2 PSTR("<+02>-2")
|
||||||
|
#define TZ_Etc_GMTm3 PSTR("<+03>-3")
|
||||||
|
#define TZ_Etc_GMTm4 PSTR("<+04>-4")
|
||||||
|
#define TZ_Etc_GMTm5 PSTR("<+05>-5")
|
||||||
|
#define TZ_Etc_GMTm6 PSTR("<+06>-6")
|
||||||
|
#define TZ_Etc_GMTm7 PSTR("<+07>-7")
|
||||||
|
#define TZ_Etc_GMTm8 PSTR("<+08>-8")
|
||||||
|
#define TZ_Etc_GMTm9 PSTR("<+09>-9")
|
||||||
|
#define TZ_Etc_GMTm10 PSTR("<+10>-10")
|
||||||
|
#define TZ_Etc_GMTm11 PSTR("<+11>-11")
|
||||||
|
#define TZ_Etc_GMTm12 PSTR("<+12>-12")
|
||||||
|
#define TZ_Etc_GMTm13 PSTR("<+13>-13")
|
||||||
|
#define TZ_Etc_GMTm14 PSTR("<+14>-14")
|
||||||
|
#define TZ_Etc_GMT0 PSTR("GMT0")
|
||||||
|
#define TZ_Etc_GMTp0 PSTR("GMT0")
|
||||||
|
#define TZ_Etc_GMTp1 PSTR("<-01>1")
|
||||||
|
#define TZ_Etc_GMTp2 PSTR("<-02>2")
|
||||||
|
#define TZ_Etc_GMTp3 PSTR("<-03>3")
|
||||||
|
#define TZ_Etc_GMTp4 PSTR("<-04>4")
|
||||||
|
#define TZ_Etc_GMTp5 PSTR("<-05>5")
|
||||||
|
#define TZ_Etc_GMTp6 PSTR("<-06>6")
|
||||||
|
#define TZ_Etc_GMTp7 PSTR("<-07>7")
|
||||||
|
#define TZ_Etc_GMTp8 PSTR("<-08>8")
|
||||||
|
#define TZ_Etc_GMTp9 PSTR("<-09>9")
|
||||||
|
#define TZ_Etc_GMTp10 PSTR("<-10>10")
|
||||||
|
#define TZ_Etc_GMTp11 PSTR("<-11>11")
|
||||||
|
#define TZ_Etc_GMTp12 PSTR("<-12>12")
|
||||||
|
#define TZ_Etc_UCT PSTR("UTC0")
|
||||||
|
#define TZ_Etc_UTC PSTR("UTC0")
|
||||||
|
#define TZ_Etc_Greenwich PSTR("GMT0")
|
||||||
|
#define TZ_Etc_Universal PSTR("UTC0")
|
||||||
|
#define TZ_Etc_Zulu PSTR("UTC0")
|
||||||
|
|
||||||
|
#endif // TZDB_H
|
@ -23,24 +23,27 @@
|
|||||||
|
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "core_esp8266_waveform.h"
|
#include "core_esp8266_waveform.h"
|
||||||
|
#include "user_interface.h"
|
||||||
|
|
||||||
// Which pins have a tone running on them?
|
static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) {
|
||||||
static uint32_t _toneMap = 0;
|
|
||||||
|
|
||||||
|
|
||||||
static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, unsigned long duration) {
|
|
||||||
if (_pin > 16) {
|
if (_pin > 16) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop any analogWrites (PWM) because they are a different generator
|
||||||
|
_stopPWM(_pin);
|
||||||
|
// If there's another Tone or startWaveform on this pin
|
||||||
|
// it will be changed on-the-fly (no need to stop it)
|
||||||
|
|
||||||
pinMode(_pin, OUTPUT);
|
pinMode(_pin, OUTPUT);
|
||||||
|
|
||||||
high = std::max(high, (uint32_t)100);
|
high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency,
|
||||||
low = std::max(low, (uint32_t)100);
|
low = std::max(low, (uint32_t)microsecondsToClockCycles(25)); // (25us high + 25us low period = 20KHz)
|
||||||
|
|
||||||
if (startWaveform(_pin, high, low, (uint32_t) duration * 1000)) {
|
duration = microsecondsToClockCycles(duration * 1000UL);
|
||||||
_toneMap |= 1 << _pin;
|
duration += high + low - 1;
|
||||||
}
|
duration -= duration % (high + low);
|
||||||
|
startWaveformClockCycles(_pin, high, low, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -48,7 +51,7 @@ void tone(uint8_t _pin, unsigned int frequency, unsigned long duration) {
|
|||||||
if (frequency == 0) {
|
if (frequency == 0) {
|
||||||
noTone(_pin);
|
noTone(_pin);
|
||||||
} else {
|
} else {
|
||||||
uint32_t period = 1000000L / frequency;
|
uint32_t period = microsecondsToClockCycles(1000000UL) / frequency;
|
||||||
uint32_t high = period / 2;
|
uint32_t high = period / 2;
|
||||||
uint32_t low = period - high;
|
uint32_t low = period - high;
|
||||||
_startTone(_pin, high, low, duration);
|
_startTone(_pin, high, low, duration);
|
||||||
@ -62,7 +65,7 @@ void tone(uint8_t _pin, double frequency, unsigned long duration) {
|
|||||||
if (frequency < 1.0) { // FP means no exact comparisons
|
if (frequency < 1.0) { // FP means no exact comparisons
|
||||||
noTone(_pin);
|
noTone(_pin);
|
||||||
} else {
|
} else {
|
||||||
double period = 1000000.0 / frequency;
|
double period = (double)microsecondsToClockCycles(1000000UL) / frequency;
|
||||||
uint32_t high = (uint32_t)((period / 2.0) + 0.5);
|
uint32_t high = (uint32_t)((period / 2.0) + 0.5);
|
||||||
uint32_t low = (uint32_t)(period + 0.5) - high;
|
uint32_t low = (uint32_t)(period + 0.5) - high;
|
||||||
_startTone(_pin, high, low, duration);
|
_startTone(_pin, high, low, duration);
|
||||||
@ -82,6 +85,5 @@ void noTone(uint8_t _pin) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopWaveform(_pin);
|
stopWaveform(_pin);
|
||||||
_toneMap &= ~(1 << _pin);
|
|
||||||
digitalWrite(_pin, 0);
|
digitalWrite(_pin, 0);
|
||||||
}
|
}
|
||||||
|
91
cores/esp8266/TypeConversion.cpp
Normal file
91
cores/esp8266/TypeConversion.cpp
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
TypeConversion functionality
|
||||||
|
Copyright (C) 2019 Anders Löfgren
|
||||||
|
|
||||||
|
License (MIT license):
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include "TypeConversion.h"
|
||||||
|
|
||||||
|
namespace experimental
|
||||||
|
{
|
||||||
|
namespace TypeConversion
|
||||||
|
{
|
||||||
|
const char base36Chars[36] PROGMEM = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
|
||||||
|
const uint8_t base36CharValues[75] PROGMEM {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, // 0 to 9
|
||||||
|
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0, 0, 0, 0, // Upper case letters
|
||||||
|
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 // Lower case letters
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
String uint8ArrayToHexString(const uint8_t *uint8Array, const uint32_t arrayLength)
|
||||||
|
{
|
||||||
|
String hexString;
|
||||||
|
if (!hexString.reserve(2 * arrayLength)) // Each uint8_t will become two characters (00 to FF)
|
||||||
|
{
|
||||||
|
return emptyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < arrayLength; ++i)
|
||||||
|
{
|
||||||
|
hexString += (char)pgm_read_byte(base36Chars + (uint8Array[i] >> 4));
|
||||||
|
hexString += (char)pgm_read_byte(base36Chars + uint8Array[i] % 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hexString;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, const uint32_t arrayLength)
|
||||||
|
{
|
||||||
|
assert(hexString.length() >= arrayLength * 2); // Each array element can hold two hexString characters
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < arrayLength; ++i)
|
||||||
|
{
|
||||||
|
uint8Array[i] = (pgm_read_byte(base36CharValues + hexString.charAt(i * 2) - '0') << 4) + pgm_read_byte(base36CharValues + hexString.charAt(i * 2 + 1) - '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *uint64ToUint8ArrayBE(const uint64_t value, uint8_t *resultArray)
|
||||||
|
{
|
||||||
|
resultArray[7] = value;
|
||||||
|
resultArray[6] = value >> 8;
|
||||||
|
resultArray[5] = value >> 16;
|
||||||
|
resultArray[4] = value >> 24;
|
||||||
|
resultArray[3] = value >> 32;
|
||||||
|
resultArray[2] = value >> 40;
|
||||||
|
resultArray[1] = value >> 48;
|
||||||
|
resultArray[0] = value >> 56;
|
||||||
|
|
||||||
|
return resultArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t uint8ArrayToUint64BE(const uint8_t *inputArray)
|
||||||
|
{
|
||||||
|
uint64_t result = (uint64_t)inputArray[0] << 56 | (uint64_t)inputArray[1] << 48 | (uint64_t)inputArray[2] << 40 | (uint64_t)inputArray[3] << 32
|
||||||
|
| (uint64_t)inputArray[4] << 24 | (uint64_t)inputArray[5] << 16 | (uint64_t)inputArray[6] << 8 | (uint64_t)inputArray[7];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
cores/esp8266/TypeConversion.h
Normal file
80
cores/esp8266/TypeConversion.h
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
TypeConversion functionality
|
||||||
|
Copyright (C) 2019 Anders Löfgren
|
||||||
|
|
||||||
|
License (MIT license):
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __ESP8266_TYPECONVERSION_H__
|
||||||
|
#define __ESP8266_TYPECONVERSION_H__
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
namespace experimental
|
||||||
|
{
|
||||||
|
namespace TypeConversion
|
||||||
|
{
|
||||||
|
extern const char base36Chars[36];
|
||||||
|
|
||||||
|
// Subtract '0' to normalize the char before lookup.
|
||||||
|
extern const uint8_t base36CharValues[75];
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert the contents of a uint8_t array to a String in HEX format. The resulting String starts from index 0 of the array.
|
||||||
|
All array elements will be padded with zeroes to ensure they are converted to 2 String characters each.
|
||||||
|
|
||||||
|
@param uint8Array The array to make into a HEX String.
|
||||||
|
@param arrayLength The size of uint8Array, in bytes.
|
||||||
|
@return Normally a String containing the HEX representation of the uint8Array. An empty String if the memory allocation for the String failed.
|
||||||
|
*/
|
||||||
|
String uint8ArrayToHexString(const uint8_t *uint8Array, const uint32_t arrayLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Convert the contents of a String in HEX format to a uint8_t array. Index 0 of the array will represent the start of the String.
|
||||||
|
There must be 2 String characters for each array element. Use padding with zeroes where required.
|
||||||
|
|
||||||
|
@param hexString The HEX String to convert to a uint8_t array. Must contain at least 2*arrayLength characters.
|
||||||
|
@param uint8Array The array to fill with the contents of the hexString.
|
||||||
|
@param arrayLength The number of bytes to fill in uint8Array.
|
||||||
|
@return A pointer to the uint8Array.
|
||||||
|
*/
|
||||||
|
uint8_t *hexStringToUint8Array(const String &hexString, uint8_t *uint8Array, const uint32_t arrayLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Takes a uint64_t value and stores the bits in a uint8_t array. Assumes index 0 of the array should contain MSB (big endian).
|
||||||
|
|
||||||
|
@param value The uint64_t value to convert to a uint8_t array.
|
||||||
|
@param resultArray A uint8_t array that will hold the result once the function returns. Should have a size of at least 8 bytes.
|
||||||
|
@return The resultArray.
|
||||||
|
*/
|
||||||
|
uint8_t *uint64ToUint8ArrayBE(const uint64_t value, uint8_t *resultArray);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Takes a uint8_t array and converts the first 8 (lowest index) elements to a uint64_t. Assumes index 0 of the array contains MSB (big endian).
|
||||||
|
|
||||||
|
@param inputArray A uint8_t array containing the data to convert to a uint64_t. Should have a size of at least 8 bytes.
|
||||||
|
@return A uint64_t representation of the first 8 bytes of the array.
|
||||||
|
*/
|
||||||
|
uint64_t uint8ArrayToUint64BE(const uint8_t *inputArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -83,14 +83,14 @@ class UDP: public Stream {
|
|||||||
// Return the port of the host who sent the current incoming packet
|
// Return the port of the host who sent the current incoming packet
|
||||||
virtual uint16_t remotePort() =0;
|
virtual uint16_t remotePort() =0;
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
uint8_t* rawIPAddress(IPAddress& addr) {
|
uint8_t* rawIPAddress(IPAddress& addr) {
|
||||||
return addr.raw_address();
|
return addr.raw_address();
|
||||||
}
|
}
|
||||||
#if LWIP_VERSION_MAJOR != 1
|
|
||||||
const uint8_t* rawIPAddress(const IPAddress& addr) {
|
const uint8_t* rawIPAddress(const IPAddress& addr) {
|
||||||
return addr.raw_address();
|
return addr.raw_address();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
#include "Updater.h"
|
#include "Updater.h"
|
||||||
#include "Arduino.h"
|
|
||||||
#include "eboot_command.h"
|
#include "eboot_command.h"
|
||||||
#include <interrupts.h>
|
|
||||||
#include <esp8266_peri.h>
|
#include <esp8266_peri.h>
|
||||||
#include <PolledTimeout.h>
|
#include <PolledTimeout.h>
|
||||||
|
#include "StackThunk.h"
|
||||||
|
|
||||||
//#define DEBUG_UPDATER Serial
|
//#define DEBUG_UPDATER Serial
|
||||||
|
|
||||||
@ -13,10 +12,10 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ARDUINO_SIGNING
|
#if ARDUINO_SIGNING
|
||||||
#include "../../libraries/ESP8266WiFi/src/BearSSLHelpers.h"
|
namespace esp8266 {
|
||||||
static BearSSL::PublicKey signPubKey(signing_pubkey);
|
extern UpdaterHashClass& updaterSigningHash;
|
||||||
static BearSSL::HashSHA256 hash;
|
extern UpdaterVerifyClass& updaterSigningVerifier;
|
||||||
static BearSSL::SigningVerifier sign(&signPubKey);
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -25,23 +24,21 @@ extern "C" {
|
|||||||
#include "user_interface.h"
|
#include "user_interface.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" uint32_t _SPIFFS_start;
|
extern "C" uint32_t _FS_start;
|
||||||
|
extern "C" uint32_t _FS_end;
|
||||||
|
|
||||||
UpdaterClass::UpdaterClass()
|
UpdaterClass::UpdaterClass()
|
||||||
: _async(false)
|
|
||||||
, _error(0)
|
|
||||||
, _buffer(0)
|
|
||||||
, _bufferLen(0)
|
|
||||||
, _size(0)
|
|
||||||
, _startAddress(0)
|
|
||||||
, _currentAddress(0)
|
|
||||||
, _command(U_FLASH)
|
|
||||||
, _hash(nullptr)
|
|
||||||
, _verify(nullptr)
|
|
||||||
, _progress_callback(nullptr)
|
|
||||||
{
|
{
|
||||||
#if ARDUINO_SIGNING
|
#if ARDUINO_SIGNING
|
||||||
installSignature(&hash, &sign);
|
installSignature(&esp8266::updaterSigningHash, &esp8266::updaterSigningVerifier);
|
||||||
|
stack_thunk_add_ref();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdaterClass::~UpdaterClass()
|
||||||
|
{
|
||||||
|
#if ARDUINO_SIGNING
|
||||||
|
stack_thunk_del_ref();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,8 +85,8 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
if (command == U_SPIFFS) {
|
if (command == U_FS) {
|
||||||
DEBUG_UPDATER.println(F("[begin] Update SPIFFS."));
|
DEBUG_UPDATER.println(F("[begin] Update Filesystem."));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -105,18 +102,24 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
|
|||||||
|
|
||||||
_reset();
|
_reset();
|
||||||
clearError(); // _error = 0
|
clearError(); // _error = 0
|
||||||
|
_target_md5 = emptyString;
|
||||||
|
_md5 = MD5Builder();
|
||||||
|
|
||||||
|
#ifndef HOST_MOCK
|
||||||
wifi_set_sleep_type(NONE_SLEEP_T);
|
wifi_set_sleep_type(NONE_SLEEP_T);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//address where we will start writing the update
|
||||||
uintptr_t updateStartAddress = 0;
|
uintptr_t updateStartAddress = 0;
|
||||||
if (command == U_FLASH) {
|
|
||||||
//size of current sketch rounded to a sector
|
//size of current sketch rounded to a sector
|
||||||
size_t currentSketchSize = (ESP.getSketchSize() + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
size_t currentSketchSize = (ESP.getSketchSize() + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
||||||
//address of the end of the space available for sketch and update
|
|
||||||
uintptr_t updateEndAddress = (uintptr_t)&_SPIFFS_start - 0x40200000;
|
|
||||||
//size of the update rounded to a sector
|
//size of the update rounded to a sector
|
||||||
size_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
size_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
|
||||||
//address where we will start writing the update
|
|
||||||
|
if (command == U_FLASH) {
|
||||||
|
//address of the end of the space available for sketch and update
|
||||||
|
uintptr_t updateEndAddress = (uintptr_t)&_FS_start - 0x40200000;
|
||||||
|
|
||||||
updateStartAddress = (updateEndAddress > roundedSize)? (updateEndAddress - roundedSize) : 0;
|
updateStartAddress = (updateEndAddress > roundedSize)? (updateEndAddress - roundedSize) : 0;
|
||||||
|
|
||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
@ -131,8 +134,25 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (command == U_SPIFFS) {
|
else if (command == U_FS) {
|
||||||
updateStartAddress = (uintptr_t)&_SPIFFS_start - 0x40200000;
|
if((uintptr_t)&_FS_start + roundedSize > (uintptr_t)&_FS_end) {
|
||||||
|
_setError(UPDATE_ERROR_SPACE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ATOMIC_FS_UPDATE
|
||||||
|
//address of the end of the space available for update
|
||||||
|
uintptr_t updateEndAddress = (uintptr_t)&_FS_start - 0x40200000;
|
||||||
|
|
||||||
|
updateStartAddress = (updateEndAddress > roundedSize)? (updateEndAddress - roundedSize) : 0;
|
||||||
|
|
||||||
|
if(updateStartAddress < currentSketchSize) {
|
||||||
|
_setError(UPDATE_ERROR_SPACE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
updateStartAddress = (uintptr_t)&_FS_start - 0x40200000;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// unknown command
|
// unknown command
|
||||||
@ -180,14 +200,19 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
|||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
DEBUG_UPDATER.println(F("no update"));
|
DEBUG_UPDATER.println(F("no update"));
|
||||||
#endif
|
#endif
|
||||||
|
_reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updating w/o any data is an error we detect here
|
||||||
|
if (!progress()) {
|
||||||
|
_setError(UPDATE_ERROR_NO_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
if(hasError() || (!isFinished() && !evenIfRemaining)){
|
if(hasError() || (!isFinished() && !evenIfRemaining)){
|
||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
DEBUG_UPDATER.printf_P(PSTR("premature end: res:%u, pos:%zu/%zu\n"), getError(), progress(), _size);
|
DEBUG_UPDATER.printf_P(PSTR("premature end: res:%u, pos:%zu/%zu\n"), getError(), progress(), _size);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_reset();
|
_reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -217,7 +242,7 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
|||||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Adjusted binsize: %d\n"), binSize);
|
DEBUG_UPDATER.printf_P(PSTR("[Updater] Adjusted binsize: %d\n"), binSize);
|
||||||
#endif
|
#endif
|
||||||
// Calculate the MD5 and hash using proper size
|
// Calculate the MD5 and hash using proper size
|
||||||
uint8_t buff[128];
|
uint8_t buff[128] __attribute__((aligned(4)));
|
||||||
for(int i = 0; i < binSize; i += sizeof(buff)) {
|
for(int i = 0; i < binSize; i += sizeof(buff)) {
|
||||||
ESP.flashRead(_startAddress + i, (uint32_t *)buff, sizeof(buff));
|
ESP.flashRead(_startAddress + i, (uint32_t *)buff, sizeof(buff));
|
||||||
size_t read = std::min((int)sizeof(buff), binSize - i);
|
size_t read = std::min((int)sizeof(buff), binSize - i);
|
||||||
@ -236,7 +261,7 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
|||||||
_reset();
|
_reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ESP.flashRead(_startAddress + binSize, (uint32_t *)sig, sigLen);
|
ESP.flashRead(_startAddress + binSize, sig, sigLen);
|
||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Received Signature:"));
|
DEBUG_UPDATER.printf_P(PSTR("[Updater] Received Signature:"));
|
||||||
for (size_t i=0; i<sigLen; i++) {
|
for (size_t i=0; i<sigLen; i++) {
|
||||||
@ -245,10 +270,14 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
|||||||
DEBUG_UPDATER.printf("\n");
|
DEBUG_UPDATER.printf("\n");
|
||||||
#endif
|
#endif
|
||||||
if (!_verify->verify(_hash, (void *)sig, sigLen)) {
|
if (!_verify->verify(_hash, (void *)sig, sigLen)) {
|
||||||
|
free(sig);
|
||||||
_setError(UPDATE_ERROR_SIGN);
|
_setError(UPDATE_ERROR_SIGN);
|
||||||
_reset();
|
_reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
free(sig);
|
||||||
|
_size = binSize; // Adjust size to remove signature, not part of bin payload
|
||||||
|
|
||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Signature matches\n"));
|
DEBUG_UPDATER.printf_P(PSTR("[Updater] Signature matches\n"));
|
||||||
#endif
|
#endif
|
||||||
@ -256,7 +285,6 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
|||||||
_md5.calculate();
|
_md5.calculate();
|
||||||
if (strcasecmp(_target_md5.c_str(), _md5.toString().c_str())) {
|
if (strcasecmp(_target_md5.c_str(), _md5.toString().c_str())) {
|
||||||
_setError(UPDATE_ERROR_MD5);
|
_setError(UPDATE_ERROR_MD5);
|
||||||
_reset();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
@ -279,9 +307,20 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
|||||||
|
|
||||||
#ifdef DEBUG_UPDATER
|
#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%08X, size:0x%08zX\n"), _startAddress, _size);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
else if (_command == U_SPIFFS) {
|
else if (_command == U_FS) {
|
||||||
DEBUG_UPDATER.printf_P(PSTR("SPIFFS: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
|
#ifdef ATOMIC_FS_UPDATE
|
||||||
|
eboot_command ebcmd;
|
||||||
|
ebcmd.action = ACTION_COPY_RAW;
|
||||||
|
ebcmd.args[0] = _startAddress;
|
||||||
|
ebcmd.args[1] = (uintptr_t)&_FS_start - 0x40200000;
|
||||||
|
ebcmd.args[2] = _size;
|
||||||
|
eboot_command_write(&ebcmd);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DEBUG_UPDATER
|
||||||
|
DEBUG_UPDATER.printf_P(PSTR("Filesystem: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,7 +344,8 @@ bool UpdaterClass::_writeBuffer(){
|
|||||||
bool modifyFlashMode = false;
|
bool modifyFlashMode = false;
|
||||||
FlashMode_t flashMode = FM_QIO;
|
FlashMode_t flashMode = FM_QIO;
|
||||||
FlashMode_t bufferFlashMode = FM_QIO;
|
FlashMode_t bufferFlashMode = FM_QIO;
|
||||||
if (_currentAddress == _startAddress + FLASH_MODE_PAGE) {
|
//TODO - GZIP can't do this
|
||||||
|
if ((_currentAddress == _startAddress + FLASH_MODE_PAGE) && (_buffer[0] != 0x1f) && (_command == U_FLASH)) {
|
||||||
flashMode = ESP.getFlashChipMode();
|
flashMode = ESP.getFlashChipMode();
|
||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
DEBUG_UPDATER.printf_P(PSTR("Header: 0x%1X %1X %1X %1X\n"), _buffer[0], _buffer[1], _buffer[2], _buffer[3]);
|
DEBUG_UPDATER.printf_P(PSTR("Header: 0x%1X %1X %1X %1X\n"), _buffer[0], _buffer[1], _buffer[2], _buffer[3]);
|
||||||
@ -323,7 +363,7 @@ bool UpdaterClass::_writeBuffer(){
|
|||||||
|
|
||||||
if (eraseResult) {
|
if (eraseResult) {
|
||||||
if(!_async) yield();
|
if(!_async) yield();
|
||||||
writeResult = ESP.flashWrite(_currentAddress, (uint32_t*) _buffer, _bufferLen);
|
writeResult = ESP.flashWrite(_currentAddress, _buffer, _bufferLen);
|
||||||
} else { // if erase was unsuccessful
|
} else { // if erase was unsuccessful
|
||||||
_currentAddress = (_startAddress + _size);
|
_currentAddress = (_startAddress + _size);
|
||||||
_setError(UPDATE_ERROR_ERASE);
|
_setError(UPDATE_ERROR_ERASE);
|
||||||
@ -353,9 +393,7 @@ size_t UpdaterClass::write(uint8_t *data, size_t len) {
|
|||||||
if(hasError() || !isRunning())
|
if(hasError() || !isRunning())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if(len > remaining()){
|
if(progress() + _bufferLen + len > _size) {
|
||||||
//len = remaining();
|
|
||||||
//fail instead
|
|
||||||
_setError(UPDATE_ERROR_SPACE);
|
_setError(UPDATE_ERROR_SPACE);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -387,14 +425,14 @@ size_t UpdaterClass::write(uint8_t *data, size_t len) {
|
|||||||
bool UpdaterClass::_verifyHeader(uint8_t data) {
|
bool UpdaterClass::_verifyHeader(uint8_t data) {
|
||||||
if(_command == U_FLASH) {
|
if(_command == U_FLASH) {
|
||||||
// check for valid first magic byte (is always 0xE9)
|
// check for valid first magic byte (is always 0xE9)
|
||||||
if(data != 0xE9) {
|
if ((data != 0xE9) && (data != 0x1f)) {
|
||||||
_currentAddress = (_startAddress + _size);
|
_currentAddress = (_startAddress + _size);
|
||||||
_setError(UPDATE_ERROR_MAGIC_BYTE);
|
_setError(UPDATE_ERROR_MAGIC_BYTE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if(_command == U_SPIFFS) {
|
} else if(_command == U_FS) {
|
||||||
// no check of SPIFFS possible with first byte.
|
// no check of FS possible with first byte.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -403,7 +441,7 @@ bool UpdaterClass::_verifyHeader(uint8_t data) {
|
|||||||
bool UpdaterClass::_verifyEnd() {
|
bool UpdaterClass::_verifyEnd() {
|
||||||
if(_command == U_FLASH) {
|
if(_command == U_FLASH) {
|
||||||
|
|
||||||
uint8_t buf[4];
|
uint8_t buf[4] __attribute__((aligned(4)));
|
||||||
if(!ESP.flashRead(_startAddress, (uint32_t *) &buf[0], 4)) {
|
if(!ESP.flashRead(_startAddress, (uint32_t *) &buf[0], 4)) {
|
||||||
_currentAddress = (_startAddress);
|
_currentAddress = (_startAddress);
|
||||||
_setError(UPDATE_ERROR_READ);
|
_setError(UPDATE_ERROR_READ);
|
||||||
@ -411,7 +449,12 @@ bool UpdaterClass::_verifyEnd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check for valid first magic byte
|
// check for valid first magic byte
|
||||||
if(buf[0] != 0xE9) {
|
//
|
||||||
|
// TODO: GZIP compresses the chipsize flags, so can't do check here
|
||||||
|
if ((buf[0] == 0x1f) && (buf[1] == 0x8b)) {
|
||||||
|
// GZIP, just assume OK
|
||||||
|
return true;
|
||||||
|
} else if (buf[0] != 0xE9) {
|
||||||
_currentAddress = (_startAddress);
|
_currentAddress = (_startAddress);
|
||||||
_setError(UPDATE_ERROR_MAGIC_BYTE);
|
_setError(UPDATE_ERROR_MAGIC_BYTE);
|
||||||
return false;
|
return false;
|
||||||
@ -427,8 +470,8 @@ bool UpdaterClass::_verifyEnd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else if(_command == U_SPIFFS) {
|
} else if(_command == U_FS) {
|
||||||
// SPIFFS is already over written checks make no sense any more.
|
// FS is already over written checks make no sense any more.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -498,6 +541,7 @@ void UpdaterClass::_setError(int error){
|
|||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
printError(DEBUG_UPDATER);
|
printError(DEBUG_UPDATER);
|
||||||
#endif
|
#endif
|
||||||
|
_reset(); // Any error condition invalidates the entire update, so clear partial status
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdaterClass::printError(Print &out){
|
void UpdaterClass::printError(Print &out){
|
||||||
@ -516,6 +560,8 @@ void UpdaterClass::printError(Print &out){
|
|||||||
out.println(F("Bad Size Given"));
|
out.println(F("Bad Size Given"));
|
||||||
} else if(_error == UPDATE_ERROR_STREAM){
|
} else if(_error == UPDATE_ERROR_STREAM){
|
||||||
out.println(F("Stream Read Timeout"));
|
out.println(F("Stream Read Timeout"));
|
||||||
|
} else if(_error == UPDATE_ERROR_NO_DATA){
|
||||||
|
out.println(F("No data supplied"));
|
||||||
} else if(_error == UPDATE_ERROR_MD5){
|
} else if(_error == UPDATE_ERROR_MD5){
|
||||||
out.printf_P(PSTR("MD5 Failed: expected:%s, calculated:%s\n"), _target_md5.c_str(), _md5.toString().c_str());
|
out.printf_P(PSTR("MD5 Failed: expected:%s, calculated:%s\n"), _target_md5.c_str(), _md5.toString().c_str());
|
||||||
} else if(_error == UPDATE_ERROR_SIGN){
|
} else if(_error == UPDATE_ERROR_SIGN){
|
||||||
|
@ -19,9 +19,10 @@
|
|||||||
#define UPDATE_ERROR_MAGIC_BYTE (10)
|
#define UPDATE_ERROR_MAGIC_BYTE (10)
|
||||||
#define UPDATE_ERROR_BOOTSTRAP (11)
|
#define UPDATE_ERROR_BOOTSTRAP (11)
|
||||||
#define UPDATE_ERROR_SIGN (12)
|
#define UPDATE_ERROR_SIGN (12)
|
||||||
|
#define UPDATE_ERROR_NO_DATA (13)
|
||||||
|
|
||||||
#define U_FLASH 0
|
#define U_FLASH 0
|
||||||
#define U_SPIFFS 100
|
#define U_FS 100
|
||||||
#define U_AUTH 200
|
#define U_AUTH 200
|
||||||
|
|
||||||
#ifdef DEBUG_ESP_UPDATER
|
#ifdef DEBUG_ESP_UPDATER
|
||||||
@ -38,6 +39,7 @@ class UpdaterHashClass {
|
|||||||
virtual void end() = 0;
|
virtual void end() = 0;
|
||||||
virtual int len() = 0;
|
virtual int len() = 0;
|
||||||
virtual const void *hash() = 0;
|
virtual const void *hash() = 0;
|
||||||
|
virtual const unsigned char *oid() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Abstract class to implement a signature verifier
|
// Abstract class to implement a signature verifier
|
||||||
@ -52,6 +54,7 @@ class UpdaterClass {
|
|||||||
typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
|
typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
|
||||||
|
|
||||||
UpdaterClass();
|
UpdaterClass();
|
||||||
|
~UpdaterClass();
|
||||||
|
|
||||||
/* Optionally add a cryptographic signature verification hash and method */
|
/* Optionally add a cryptographic signature verification hash and method */
|
||||||
void installSignature(UpdaterHashClass *hash, UpdaterVerifyClass *verify) { _hash = hash; _verify = verify; }
|
void installSignature(UpdaterHashClass *hash, UpdaterVerifyClass *verify) { _hash = hash; _verify = verify; }
|
||||||
@ -180,27 +183,27 @@ class UpdaterClass {
|
|||||||
|
|
||||||
void _setError(int error);
|
void _setError(int error);
|
||||||
|
|
||||||
bool _async;
|
bool _async = false;
|
||||||
uint8_t _error;
|
uint8_t _error = 0;
|
||||||
uint8_t *_buffer;
|
uint8_t *_buffer = nullptr;
|
||||||
size_t _bufferLen; // amount of data written into _buffer
|
size_t _bufferLen = 0; // amount of data written into _buffer
|
||||||
size_t _bufferSize; // total size of _buffer
|
size_t _bufferSize = 0; // total size of _buffer
|
||||||
size_t _size;
|
size_t _size = 0;
|
||||||
uint32_t _startAddress;
|
uint32_t _startAddress = 0;
|
||||||
uint32_t _currentAddress;
|
uint32_t _currentAddress = 0;
|
||||||
uint32_t _command;
|
uint32_t _command = U_FLASH;
|
||||||
|
|
||||||
String _target_md5;
|
String _target_md5;
|
||||||
MD5Builder _md5;
|
MD5Builder _md5;
|
||||||
|
|
||||||
int _ledPin;
|
int _ledPin = -1;
|
||||||
uint8_t _ledOn;
|
uint8_t _ledOn;
|
||||||
|
|
||||||
// Optional signed binary verification
|
// Optional signed binary verification
|
||||||
UpdaterHashClass *_hash;
|
UpdaterHashClass *_hash = nullptr;
|
||||||
UpdaterVerifyClass *_verify;
|
UpdaterVerifyClass *_verify = nullptr;
|
||||||
// Optional progress callback function
|
// Optional progress callback function
|
||||||
THandlerFunction_Progress _progress_callback;
|
THandlerFunction_Progress _progress_callback = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern UpdaterClass Update;
|
extern UpdaterClass Update;
|
||||||
|
@ -70,11 +70,11 @@ long secureRandom(long howsmall, long howbig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long map(long x, long in_min, long in_max, long out_min, long out_max) {
|
long map(long x, long in_min, long in_max, long out_min, long out_max) {
|
||||||
long divisor = (in_max - in_min);
|
const long dividend = out_max - out_min;
|
||||||
if(divisor == 0){
|
const long divisor = in_max - in_min;
|
||||||
return -1; //AVR returns -1, SAM returns 0
|
const long delta = x - in_min;
|
||||||
}
|
|
||||||
return (x - in_min) * (out_max - out_min) / divisor + out_min;
|
return (delta * dividend + (divisor / 2)) / divisor + out_min;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int makeWord(unsigned int w) {
|
unsigned int makeWord(unsigned int w) {
|
||||||
|
@ -21,10 +21,16 @@
|
|||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include "Arduino.h"
|
||||||
#include "WString.h"
|
#include "WString.h"
|
||||||
#include "stdlib_noniso.h"
|
#include "stdlib_noniso.h"
|
||||||
|
|
||||||
|
#define OOM_STRING_BORDER_DISPLAY 10
|
||||||
|
#define OOM_STRING_THRESHOLD_REALLOC_WARN 128
|
||||||
|
|
||||||
|
#define __STRHELPER(x) #x
|
||||||
|
#define STR(x) __STRHELPER(x) // stringifier
|
||||||
|
|
||||||
/*********************************************/
|
/*********************************************/
|
||||||
/* Constructors */
|
/* Constructors */
|
||||||
/*********************************************/
|
/*********************************************/
|
||||||
@ -32,7 +38,7 @@
|
|||||||
String::String(const char *cstr) {
|
String::String(const char *cstr) {
|
||||||
init();
|
init();
|
||||||
if (cstr)
|
if (cstr)
|
||||||
copy(cstr, strlen(cstr));
|
copy(cstr, strlen_P(cstr));
|
||||||
}
|
}
|
||||||
|
|
||||||
String::String(const String &value) {
|
String::String(const String &value) {
|
||||||
@ -45,26 +51,11 @@ String::String(const __FlashStringHelper *pstr) {
|
|||||||
*this = pstr; // see operator =
|
*this = pstr; // see operator =
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
String::String(String &&rval) noexcept {
|
||||||
String::String(String &&rval) {
|
|
||||||
init();
|
init();
|
||||||
move(rval);
|
move(rval);
|
||||||
}
|
}
|
||||||
|
|
||||||
String::String(StringSumHelper &&rval) {
|
|
||||||
init();
|
|
||||||
move(rval);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
String::String(char c) {
|
|
||||||
init();
|
|
||||||
char buf[2];
|
|
||||||
buf[0] = c;
|
|
||||||
buf[1] = 0;
|
|
||||||
*this = buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
String::String(unsigned char value, unsigned char base) {
|
String::String(unsigned char value, unsigned char base) {
|
||||||
init();
|
init();
|
||||||
char buf[1 + 8 * sizeof(unsigned char)];
|
char buf[1 + 8 * sizeof(unsigned char)];
|
||||||
@ -108,6 +99,32 @@ String::String(unsigned long value, unsigned char base) {
|
|||||||
*this = buf;
|
*this = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String::String(long long value) {
|
||||||
|
init();
|
||||||
|
char buf[2 + 8 * sizeof(long long)];
|
||||||
|
sprintf(buf, "%lld", value);
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(unsigned long long value) {
|
||||||
|
init();
|
||||||
|
char buf[1 + 8 * sizeof(unsigned long long)];
|
||||||
|
sprintf(buf, "%llu", value);
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(long long value, unsigned char base) {
|
||||||
|
init();
|
||||||
|
char buf[2 + 8 * sizeof(long long)];
|
||||||
|
*this = lltoa(value, buf, sizeof(buf), base);
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(unsigned long long value, unsigned char base) {
|
||||||
|
init();
|
||||||
|
char buf[1 + 8 * sizeof(unsigned long long)];
|
||||||
|
*this = ulltoa(value, buf, sizeof(buf), base);
|
||||||
|
}
|
||||||
|
|
||||||
String::String(float value, unsigned char decimalPlaces) {
|
String::String(float value, unsigned char decimalPlaces) {
|
||||||
init();
|
init();
|
||||||
char buf[33];
|
char buf[33];
|
||||||
@ -120,23 +137,12 @@ String::String(double value, unsigned char decimalPlaces) {
|
|||||||
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
|
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
String::~String() {
|
/*********************************************/
|
||||||
invalidate();
|
/* Memory Management */
|
||||||
}
|
/*********************************************/
|
||||||
|
|
||||||
// /*********************************************/
|
|
||||||
// /* Memory Management */
|
|
||||||
// /*********************************************/
|
|
||||||
|
|
||||||
inline void String::init(void) {
|
|
||||||
setSSO(false);
|
|
||||||
setCapacity(0);
|
|
||||||
setLen(0);
|
|
||||||
setBuffer(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::invalidate(void) {
|
void String::invalidate(void) {
|
||||||
if(!sso() && wbuffer())
|
if (!isSSO() && wbuffer())
|
||||||
free(wbuffer());
|
free(wbuffer());
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
@ -154,37 +160,46 @@ unsigned char String::reserve(unsigned int size) {
|
|||||||
|
|
||||||
unsigned char String::changeBuffer(unsigned int maxStrLen) {
|
unsigned char String::changeBuffer(unsigned int maxStrLen) {
|
||||||
// Can we use SSO here to avoid allocation?
|
// Can we use SSO here to avoid allocation?
|
||||||
if (maxStrLen < sizeof(sso_buf)) {
|
if (maxStrLen < sizeof(sso.buff) - 1) {
|
||||||
if (sso() || !buffer()) {
|
if (isSSO() || !buffer()) {
|
||||||
// Already using SSO, nothing to do
|
// Already using SSO, nothing to do
|
||||||
|
uint16_t oldLen = len();
|
||||||
setSSO(true);
|
setSSO(true);
|
||||||
return 1;
|
setLen(oldLen);
|
||||||
} else { // if bufptr && !sso()
|
} else { // if bufptr && !isSSO()
|
||||||
// Using bufptr, need to shrink into sso_buff
|
// Using bufptr, need to shrink into sso.buff
|
||||||
char temp[sizeof(sso_buf)];
|
const char *temp = buffer();
|
||||||
memcpy(temp, buffer(), maxStrLen);
|
uint16_t oldLen = len();
|
||||||
free(wbuffer());
|
|
||||||
setSSO(true);
|
setSSO(true);
|
||||||
|
setLen(oldLen);
|
||||||
memcpy(wbuffer(), temp, maxStrLen);
|
memcpy(wbuffer(), temp, maxStrLen);
|
||||||
return 1;
|
free((void *)temp);
|
||||||
}
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
// Fallthrough to normal allocator
|
// Fallthrough to normal allocator
|
||||||
size_t newSize = (maxStrLen + 16) & (~0xf);
|
size_t newSize = (maxStrLen + 16) & (~0xf);
|
||||||
|
#ifdef DEBUG_ESP_OOM
|
||||||
|
if (!isSSO() && capacity() >= OOM_STRING_THRESHOLD_REALLOC_WARN && maxStrLen > capacity()) {
|
||||||
|
// warn when badly re-allocating
|
||||||
|
DEBUGV("[offending String op %d->%d ('%." STR(OOM_STRING_BORDER_DISPLAY) "s ... %." STR(OOM_STRING_BORDER_DISPLAY) "s')]\n",
|
||||||
|
len(), maxStrLen, c_str(),
|
||||||
|
len() > OOM_STRING_BORDER_DISPLAY? c_str() + std::max((int)len() - OOM_STRING_BORDER_DISPLAY, OOM_STRING_BORDER_DISPLAY): "");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
// Make sure we can fit newsize in the buffer
|
// Make sure we can fit newsize in the buffer
|
||||||
if (newSize > CAPACITY_MAX) {
|
if (newSize > CAPACITY_MAX) {
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
uint16_t oldLen = len();
|
uint16_t oldLen = len();
|
||||||
char *newbuffer = (char *) realloc(sso() ? nullptr : wbuffer(), newSize);
|
char *newbuffer = (char *)realloc(isSSO() ? nullptr : wbuffer(), newSize);
|
||||||
if (newbuffer) {
|
if (newbuffer) {
|
||||||
size_t oldSize = capacity() + 1; // include NULL.
|
size_t oldSize = capacity() + 1; // include NULL.
|
||||||
if (sso()) {
|
if (isSSO()) {
|
||||||
// Copy the SSO buffer into allocated space
|
// Copy the SSO buffer into allocated space
|
||||||
memcpy(newbuffer, sso_buf, sizeof(sso_buf));
|
memmove_P(newbuffer, sso.buff, sizeof(sso.buff));
|
||||||
}
|
}
|
||||||
if (newSize > oldSize)
|
if (newSize > oldSize) {
|
||||||
{
|
|
||||||
memset(newbuffer + oldSize, 0, newSize - oldSize);
|
memset(newbuffer + oldSize, 0, newSize - oldSize);
|
||||||
}
|
}
|
||||||
setSSO(false);
|
setSSO(false);
|
||||||
@ -196,9 +211,9 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Copy and Move */
|
/* Copy and Move */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
String &String::copy(const char *cstr, unsigned int length) {
|
String &String::copy(const char *cstr, unsigned int length) {
|
||||||
if (!reserve(length)) {
|
if (!reserve(length)) {
|
||||||
@ -206,7 +221,7 @@ String & String::copy(const char *cstr, unsigned int length) {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
setLen(length);
|
setLen(length);
|
||||||
strcpy(wbuffer(), cstr);
|
memmove_P(wbuffer(), cstr, length + 1);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,87 +231,51 @@ String & String::copy(const __FlashStringHelper *pstr, unsigned int length) {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
setLen(length);
|
setLen(length);
|
||||||
strcpy_P(wbuffer(), (PGM_P)pstr);
|
memcpy_P(wbuffer(), (PGM_P)pstr, length + 1); // We know wbuffer() cannot ever be in PROGMEM, so memcpy safe here
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
void String::move(String &rhs) noexcept {
|
||||||
void String::move(String &rhs) {
|
invalidate();
|
||||||
if(buffer()) {
|
sso = rhs.sso;
|
||||||
if(capacity() >= rhs.len()) {
|
rhs.init();
|
||||||
strcpy(wbuffer(), rhs.buffer());
|
|
||||||
setLen(rhs.len());
|
|
||||||
rhs.invalidate();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (!sso()) {
|
|
||||||
free(wbuffer());
|
|
||||||
setBuffer(nullptr);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rhs.sso()) {
|
|
||||||
setSSO(true);
|
|
||||||
memmove(sso_buf, rhs.sso_buf, sizeof(sso_buf));
|
|
||||||
} else {
|
|
||||||
setSSO(false);
|
|
||||||
setBuffer(rhs.wbuffer());
|
|
||||||
}
|
|
||||||
setCapacity(rhs.capacity());
|
|
||||||
setLen(rhs.len());
|
|
||||||
rhs.setSSO(false);
|
|
||||||
rhs.setCapacity(0);
|
|
||||||
rhs.setLen(0);
|
|
||||||
rhs.setBuffer(nullptr);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
String &String::operator =(const String &rhs) {
|
String &String::operator =(const String &rhs) {
|
||||||
if (this == &rhs)
|
if (this == &rhs)
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
if (rhs.buffer())
|
if (rhs.buffer())
|
||||||
copy(rhs.buffer(), rhs.len());
|
copy(rhs.buffer(), rhs.len());
|
||||||
else
|
else
|
||||||
invalidate();
|
invalidate();
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
String &String::operator =(String &&rval) noexcept {
|
||||||
String & String::operator =(String &&rval) {
|
|
||||||
if (this != &rval)
|
if (this != &rval)
|
||||||
move(rval);
|
move(rval);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
String & String::operator =(StringSumHelper &&rval) {
|
|
||||||
if(this != &rval)
|
|
||||||
move(rval);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
String &String::operator =(const char *cstr) {
|
String &String::operator =(const char *cstr) {
|
||||||
if (cstr)
|
if (cstr)
|
||||||
copy(cstr, strlen(cstr));
|
copy(cstr, strlen(cstr));
|
||||||
else
|
else
|
||||||
invalidate();
|
invalidate();
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
String & String::operator = (const __FlashStringHelper *pstr)
|
String &String::operator =(const __FlashStringHelper *pstr) {
|
||||||
{
|
if (pstr)
|
||||||
if (pstr) copy(pstr, strlen_P((PGM_P)pstr));
|
copy(pstr, strlen_P((PGM_P)pstr));
|
||||||
else invalidate();
|
else
|
||||||
|
invalidate();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* concat */
|
/* concat */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
unsigned char String::concat(const String &s) {
|
unsigned char String::concat(const String &s) {
|
||||||
// Special case if we're concatting ourself (s += s;) since we may end up
|
// Special case if we're concatting ourself (s += s;) since we may end up
|
||||||
@ -309,9 +288,9 @@ unsigned char String::concat(const String &s) {
|
|||||||
return 1;
|
return 1;
|
||||||
if (!reserve(newlen))
|
if (!reserve(newlen))
|
||||||
return 0;
|
return 0;
|
||||||
memcpy(wbuffer() + len(), buffer(), len());
|
memmove_P(wbuffer() + len(), buffer(), len());
|
||||||
setLen(newlen);
|
setLen(newlen);
|
||||||
wbuffer()[len()] = 0;
|
wbuffer()[newlen] = 0;
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return concat(s.buffer(), s.len());
|
return concat(s.buffer(), s.len());
|
||||||
@ -326,8 +305,9 @@ unsigned char String::concat(const char *cstr, unsigned int length) {
|
|||||||
return 1;
|
return 1;
|
||||||
if (!reserve(newlen))
|
if (!reserve(newlen))
|
||||||
return 0;
|
return 0;
|
||||||
strcpy(wbuffer() + len(), cstr);
|
memmove_P(wbuffer() + len(), cstr, length + 1);
|
||||||
setLen(newlen);
|
setLen(newlen);
|
||||||
|
wbuffer()[newlen] = 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,22 +318,17 @@ unsigned char String::concat(const char *cstr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(char c) {
|
unsigned char String::concat(char c) {
|
||||||
char buf[2];
|
return concat(&c, 1);
|
||||||
buf[0] = c;
|
|
||||||
buf[1] = 0;
|
|
||||||
return concat(buf, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(unsigned char num) {
|
unsigned char String::concat(unsigned char num) {
|
||||||
char buf[1 + 3 * sizeof(unsigned char)];
|
char buf[1 + 3 * sizeof(unsigned char)];
|
||||||
sprintf(buf, "%d", num);
|
return concat(buf, sprintf(buf, "%d", num));
|
||||||
return concat(buf, strlen(buf));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(int num) {
|
unsigned char String::concat(int num) {
|
||||||
char buf[2 + 3 * sizeof(int)];
|
char buf[2 + 3 * sizeof(int)];
|
||||||
sprintf(buf, "%d", num);
|
return concat(buf, sprintf(buf, "%d", num));
|
||||||
return concat(buf, strlen(buf));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(unsigned int num) {
|
unsigned char String::concat(unsigned int num) {
|
||||||
@ -364,8 +339,7 @@ unsigned char String::concat(unsigned int num) {
|
|||||||
|
|
||||||
unsigned char String::concat(long num) {
|
unsigned char String::concat(long num) {
|
||||||
char buf[2 + 3 * sizeof(long)];
|
char buf[2 + 3 * sizeof(long)];
|
||||||
sprintf(buf, "%ld", num);
|
return concat(buf, sprintf(buf, "%ld", num));
|
||||||
return concat(buf, strlen(buf));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(unsigned long num) {
|
unsigned char String::concat(unsigned long num) {
|
||||||
@ -374,6 +348,16 @@ unsigned char String::concat(unsigned long num) {
|
|||||||
return concat(buf, strlen(buf));
|
return concat(buf, strlen(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned char String::concat(long long num) {
|
||||||
|
char buf[2 + 3 * sizeof(long long)];
|
||||||
|
return concat(buf, sprintf(buf, "%lld", num));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char String::concat(unsigned long long num) {
|
||||||
|
char buf[1 + 3 * sizeof(unsigned long long)];
|
||||||
|
return concat(buf, sprintf(buf, "%llu", num));
|
||||||
|
}
|
||||||
|
|
||||||
unsigned char String::concat(float num) {
|
unsigned char String::concat(float num) {
|
||||||
char buf[20];
|
char buf[20];
|
||||||
char *string = dtostrf(num, 4, 2, buf);
|
char *string = dtostrf(num, 4, 2, buf);
|
||||||
@ -387,101 +371,111 @@ unsigned char String::concat(double num) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(const __FlashStringHelper *str) {
|
unsigned char String::concat(const __FlashStringHelper *str) {
|
||||||
if (!str) return 0;
|
if (!str)
|
||||||
|
return 0;
|
||||||
int length = strlen_P((PGM_P)str);
|
int length = strlen_P((PGM_P)str);
|
||||||
if (length == 0) return 1;
|
if (length == 0)
|
||||||
|
return 1;
|
||||||
unsigned int newlen = len() + length;
|
unsigned int newlen = len() + length;
|
||||||
if (!reserve(newlen)) return 0;
|
if (!reserve(newlen))
|
||||||
strcpy_P(wbuffer() + len(), (PGM_P)str);
|
return 0;
|
||||||
|
memcpy_P(wbuffer() + len(), (PGM_P)str, length + 1);
|
||||||
setLen(newlen);
|
setLen(newlen);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*********************************************/
|
/*********************************************/
|
||||||
/* Concatenate */
|
/* Insert */
|
||||||
/*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs) {
|
String &String::insert(size_t position, const char *other, size_t other_length) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
if (position > length())
|
||||||
if(!a.concat(rhs.buffer(), rhs.len()))
|
return *this;
|
||||||
a.invalidate();
|
|
||||||
return a;
|
auto len = length();
|
||||||
|
auto total = len + other_length;
|
||||||
|
if (!reserve(total))
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
auto left = len - position;
|
||||||
|
setLen(total);
|
||||||
|
|
||||||
|
auto *start = wbuffer() + position;
|
||||||
|
memmove(start + other_length, start, left);
|
||||||
|
memmove_P(start, other, other_length);
|
||||||
|
wbuffer()[total] = '\0';
|
||||||
|
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr) {
|
String &String::insert(size_t position, const __FlashStringHelper *other) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
auto *p = reinterpret_cast<const char*>(other);
|
||||||
if(!cstr || !a.concat(cstr, strlen(cstr)))
|
return insert(position, p, strlen_P(p));
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, char c) {
|
String &String::insert(size_t position, char other) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
char tmp[2] { other, '\0' };
|
||||||
if(!a.concat(c))
|
return insert(position, tmp, 1);
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num) {
|
String &String::insert(size_t position, const char *other) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
return insert(position, other, strlen(other));
|
||||||
if(!a.concat(num))
|
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, int num) {
|
String &String::insert(size_t position, const String &other) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
return insert(position, other.c_str(), other.length());
|
||||||
if(!a.concat(num))
|
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num) {
|
String operator +(const String &lhs, String &&rhs) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
String res;
|
||||||
if(!a.concat(num))
|
auto total = lhs.length() + rhs.length();
|
||||||
a.invalidate();
|
if (rhs.capacity() > total) {
|
||||||
return a;
|
rhs.insert(0, lhs);
|
||||||
|
res = std::move(rhs);
|
||||||
|
} else {
|
||||||
|
res.reserve(total);
|
||||||
|
res += lhs;
|
||||||
|
res += rhs;
|
||||||
|
rhs.invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, long num) {
|
return res;
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
|
||||||
if(!a.concat(num))
|
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num) {
|
String operator +(String &&lhs, String &&rhs) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
String res;
|
||||||
if(!a.concat(num))
|
auto total = lhs.length() + rhs.length();
|
||||||
a.invalidate();
|
if ((total > lhs.capacity()) && (total < rhs.capacity())) {
|
||||||
return a;
|
rhs.insert(0, lhs);
|
||||||
|
res = std::move(rhs);
|
||||||
|
} else {
|
||||||
|
lhs += rhs;
|
||||||
|
rhs.invalidate();
|
||||||
|
res = std::move(lhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, float num) {
|
return res;
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
|
||||||
if(!a.concat(num))
|
|
||||||
a.invalidate();
|
|
||||||
return a;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, double num) {
|
String operator +(char lhs, const String &rhs) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
String res;
|
||||||
if(!a.concat(num))
|
res.reserve(rhs.length() + 1);
|
||||||
a.invalidate();
|
res += lhs;
|
||||||
return a;
|
res += rhs;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs)
|
String operator +(const char *lhs, const String &rhs) {
|
||||||
{
|
String res;
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
res.reserve(strlen_P(lhs) + rhs.length());
|
||||||
if (!a.concat(rhs))
|
res += lhs;
|
||||||
a.invalidate();
|
res += rhs;
|
||||||
return a;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Comparison */
|
/* Comparison */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
int String::compareTo(const String &s) const {
|
int String::compareTo(const String &s) const {
|
||||||
if (!buffer() || !s.buffer()) {
|
if (!buffer() || !s.buffer()) {
|
||||||
@ -546,7 +540,7 @@ unsigned char String::equalsConstantTime(const String &s2) const {
|
|||||||
//at this point lengths are the same
|
//at this point lengths are the same
|
||||||
if (len() == 0)
|
if (len() == 0)
|
||||||
return 1;
|
return 1;
|
||||||
//at this point lenghts are the same and non-zero
|
//at this point lengths are the same and non-zero
|
||||||
const char *p1 = buffer();
|
const char *p1 = buffer();
|
||||||
const char *p2 = s2.buffer();
|
const char *p2 = s2.buffer();
|
||||||
unsigned int equalchars = 0;
|
unsigned int equalchars = 0;
|
||||||
@ -583,13 +577,9 @@ unsigned char String::endsWith(const String &s2) const {
|
|||||||
return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0;
|
return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Character Access */
|
/* Character Access */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
char String::charAt(unsigned int loc) const {
|
|
||||||
return operator[](loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::setCharAt(unsigned int loc, char c) {
|
void String::setCharAt(unsigned int loc, char c) {
|
||||||
if (loc < len())
|
if (loc < len())
|
||||||
@ -625,13 +615,9 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind
|
|||||||
buf[n] = 0;
|
buf[n] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Search */
|
/* Search */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
int String::indexOf(char c) const {
|
|
||||||
return indexOf(c, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int String::indexOf(char ch, unsigned int fromIndex) const {
|
int String::indexOf(char ch, unsigned int fromIndex) const {
|
||||||
if (fromIndex >= len())
|
if (fromIndex >= len())
|
||||||
@ -642,33 +628,34 @@ int String::indexOf(char ch, unsigned int fromIndex) const {
|
|||||||
return temp - buffer();
|
return temp - buffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::indexOf(const String &s2) const {
|
int String::indexOf(const char *s2, unsigned int fromIndex) const {
|
||||||
return indexOf(s2, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int String::indexOf(const String &s2, unsigned int fromIndex) const {
|
|
||||||
if (fromIndex >= len())
|
if (fromIndex >= len())
|
||||||
return -1;
|
return -1;
|
||||||
const char *found = strstr(buffer() + fromIndex, s2.buffer());
|
const char *found = strstr_P(buffer() + fromIndex, s2);
|
||||||
if (found == NULL)
|
if (found == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
return found - buffer();
|
return found - buffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::lastIndexOf(char theChar) const {
|
int String::indexOf(const String &s2, unsigned int fromIndex) const {
|
||||||
return lastIndexOf(theChar, len() - 1);
|
return indexOf(s2.c_str(), fromIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
int String::lastIndexOf(char ch) const {
|
||||||
|
return lastIndexOf(ch, len() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::lastIndexOf(char ch, unsigned int fromIndex) const {
|
int String::lastIndexOf(char ch, unsigned int fromIndex) const {
|
||||||
if (fromIndex >= len())
|
if (fromIndex >= len())
|
||||||
return -1;
|
return -1;
|
||||||
char tempchar = buffer()[fromIndex + 1];
|
char *writeTo = wbuffer();
|
||||||
wbuffer()[fromIndex + 1] = '\0';
|
char tempchar = writeTo[fromIndex + 1]; // save the replaced character
|
||||||
char* temp = strrchr(wbuffer(), ch);
|
writeTo[fromIndex + 1] = '\0';
|
||||||
wbuffer()[fromIndex + 1] = tempchar;
|
char *temp = strrchr(writeTo, ch);
|
||||||
|
writeTo[fromIndex + 1] = tempchar; // restore character
|
||||||
if (temp == NULL)
|
if (temp == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
return temp - buffer();
|
return temp - writeTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::lastIndexOf(const String &s2) const {
|
int String::lastIndexOf(const String &s2) const {
|
||||||
@ -681,11 +668,11 @@ int String::lastIndexOf(const String &s2, unsigned int fromIndex) const {
|
|||||||
if (fromIndex >= len())
|
if (fromIndex >= len())
|
||||||
fromIndex = len() - 1;
|
fromIndex = len() - 1;
|
||||||
int found = -1;
|
int found = -1;
|
||||||
for(char *p = wbuffer(); p <= wbuffer() + fromIndex; p++) {
|
for (const char *p = buffer(); p <= buffer() + fromIndex; p++) {
|
||||||
p = strstr(p, s2.buffer());
|
p = strstr(p, s2.buffer());
|
||||||
if (!p)
|
if (!p)
|
||||||
break;
|
break;
|
||||||
if((unsigned int) (p - wbuffer()) <= fromIndex)
|
if ((unsigned int)(p - buffer()) <= fromIndex)
|
||||||
found = p - buffer();
|
found = p - buffer();
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
@ -702,16 +689,17 @@ String String::substring(unsigned int left, unsigned int right) const {
|
|||||||
return out;
|
return out;
|
||||||
if (right > len())
|
if (right > len())
|
||||||
right = len();
|
right = len();
|
||||||
char temp = buffer()[right]; // save the replaced character
|
char *writeTo = wbuffer();
|
||||||
wbuffer()[right] = '\0';
|
char tempchar = writeTo[right]; // save the replaced character
|
||||||
out = wbuffer() + left; // pointer arithmetic
|
writeTo[right] = '\0';
|
||||||
wbuffer()[right] = temp; //restore character
|
out = writeTo + left; // pointer arithmetic
|
||||||
|
writeTo[right] = tempchar; // restore character
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Modification */
|
/* Modification */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
void String::replace(char find, char replace) {
|
void String::replace(char find, char replace) {
|
||||||
if (!buffer())
|
if (!buffer())
|
||||||
@ -730,21 +718,21 @@ void String::replace(const String& find, const String& replace) {
|
|||||||
char *foundAt;
|
char *foundAt;
|
||||||
if (diff == 0) {
|
if (diff == 0) {
|
||||||
while ((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
while ((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
||||||
memmove(foundAt, replace.buffer(), replace.len());
|
memmove_P(foundAt, replace.buffer(), replace.len());
|
||||||
readFrom = foundAt + replace.len();
|
readFrom = foundAt + replace.len();
|
||||||
}
|
}
|
||||||
} else if (diff < 0) {
|
} else if (diff < 0) {
|
||||||
char *writeTo = wbuffer();
|
char *writeTo = wbuffer();
|
||||||
while ((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
while ((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
||||||
unsigned int n = foundAt - readFrom;
|
unsigned int n = foundAt - readFrom;
|
||||||
memmove(writeTo, readFrom, n);
|
memmove_P(writeTo, readFrom, n);
|
||||||
writeTo += n;
|
writeTo += n;
|
||||||
memmove(writeTo, replace.buffer(), replace.len());
|
memmove_P(writeTo, replace.buffer(), replace.len());
|
||||||
writeTo += replace.len();
|
writeTo += replace.len();
|
||||||
readFrom = foundAt + find.len();
|
readFrom = foundAt + find.len();
|
||||||
setLen(len() + diff);
|
setLen(len() + diff);
|
||||||
}
|
}
|
||||||
memmove(writeTo, readFrom, strlen(readFrom)+1);
|
memmove_P(writeTo, readFrom, strlen(readFrom) + 1);
|
||||||
} else {
|
} else {
|
||||||
unsigned int size = len(); // compute size needed for result
|
unsigned int size = len(); // compute size needed for result
|
||||||
while ((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
while ((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
||||||
@ -758,9 +746,9 @@ void String::replace(const String& find, const String& replace) {
|
|||||||
int index = len() - 1;
|
int index = len() - 1;
|
||||||
while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) {
|
while (index >= 0 && (index = lastIndexOf(find, index)) >= 0) {
|
||||||
readFrom = wbuffer() + index + find.len();
|
readFrom = wbuffer() + index + find.len();
|
||||||
memmove(readFrom + diff, readFrom, len() - (readFrom - buffer()));
|
memmove_P(readFrom + diff, readFrom, len() - (readFrom - buffer()));
|
||||||
int newLen = len() + diff;
|
int newLen = len() + diff;
|
||||||
memmove(wbuffer() + index, replace.buffer(), replace.len());
|
memmove_P(wbuffer() + index, replace.buffer(), replace.len());
|
||||||
setLen(newLen);
|
setLen(newLen);
|
||||||
wbuffer()[newLen] = 0;
|
wbuffer()[newLen] = 0;
|
||||||
index--;
|
index--;
|
||||||
@ -768,13 +756,6 @@ void String::replace(const String& find, const String& replace) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::remove(unsigned int index) {
|
|
||||||
// Pass the biggest integer as the count. The remove method
|
|
||||||
// below will take care of truncating it at the end of the
|
|
||||||
// string.
|
|
||||||
remove(index, (unsigned int) -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::remove(unsigned int index, unsigned int count) {
|
void String::remove(unsigned int index, unsigned int count) {
|
||||||
if (index >= len()) {
|
if (index >= len()) {
|
||||||
return;
|
return;
|
||||||
@ -788,7 +769,7 @@ void String::remove(unsigned int index, unsigned int count) {
|
|||||||
char *writeTo = wbuffer() + index;
|
char *writeTo = wbuffer() + index;
|
||||||
unsigned int newlen = len() - count;
|
unsigned int newlen = len() - count;
|
||||||
setLen(newlen);
|
setLen(newlen);
|
||||||
memmove(writeTo, wbuffer() + index + count, newlen - index);
|
memmove_P(writeTo, wbuffer() + index + count, newlen - index);
|
||||||
wbuffer()[newlen] = 0;
|
wbuffer()[newlen] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -820,13 +801,13 @@ void String::trim(void) {
|
|||||||
unsigned int newlen = end + 1 - begin;
|
unsigned int newlen = end + 1 - begin;
|
||||||
setLen(newlen);
|
setLen(newlen);
|
||||||
if (begin > buffer())
|
if (begin > buffer())
|
||||||
memmove(wbuffer(), begin, newlen);
|
memmove_P(wbuffer(), begin, newlen);
|
||||||
wbuffer()[newlen] = 0;
|
wbuffer()[newlen] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Parsing / Conversion */
|
/* Parsing / Conversion */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
long String::toInt(void) const {
|
long String::toInt(void) const {
|
||||||
if (buffer())
|
if (buffer())
|
||||||
@ -837,11 +818,10 @@ long String::toInt(void) const {
|
|||||||
float String::toFloat(void) const {
|
float String::toFloat(void) const {
|
||||||
if (buffer())
|
if (buffer())
|
||||||
return atof(buffer());
|
return atof(buffer());
|
||||||
return 0;
|
return 0.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
double String::toDouble(void) const
|
double String::toDouble(void) const {
|
||||||
{
|
|
||||||
if (buffer())
|
if (buffer())
|
||||||
return atof(buffer());
|
return atof(buffer());
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
@ -23,14 +23,15 @@
|
|||||||
#define String_class_h
|
#define String_class_h
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <pgmspace.h>
|
#include <pgmspace.h>
|
||||||
|
|
||||||
// An inherited class for holding the result of a concatenation. These
|
#include <cstdlib>
|
||||||
// result objects are assumed to be writable by subsequent concatenations.
|
#include <cstdint>
|
||||||
class StringSumHelper;
|
#include <cstring>
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
// an abstract class used as a means to proide a unique pointer type
|
// an abstract class used as a means to proide a unique pointer type
|
||||||
// but really has no body
|
// but really has no body
|
||||||
@ -38,6 +39,10 @@ class __FlashStringHelper;
|
|||||||
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
|
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
|
||||||
#define F(string_literal) (FPSTR(PSTR(string_literal)))
|
#define F(string_literal) (FPSTR(PSTR(string_literal)))
|
||||||
|
|
||||||
|
// support libraries that expect this name to be available
|
||||||
|
// replace with `using StringSumHelper = String;` in case something wants this constructible
|
||||||
|
class StringSumHelper;
|
||||||
|
|
||||||
// The string class
|
// The string class
|
||||||
class String {
|
class String {
|
||||||
// use a function pointer to allow for "if (s)" without the
|
// use a function pointer to allow for "if (s)" without the
|
||||||
@ -53,34 +58,47 @@ class String {
|
|||||||
// if the initial value is null or invalid, or if memory allocation
|
// if the initial value is null or invalid, or if memory allocation
|
||||||
// fails, the string will be marked as invalid (i.e. "if (s)" will
|
// fails, the string will be marked as invalid (i.e. "if (s)" will
|
||||||
// be false).
|
// be false).
|
||||||
String(const char *cstr = "");
|
String() __attribute__((always_inline)) { // See init()
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
String(const char *cstr);
|
||||||
String(const String &str);
|
String(const String &str);
|
||||||
String(const __FlashStringHelper *str);
|
String(const __FlashStringHelper *str);
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
String(String &&rval) noexcept;
|
||||||
String(String &&rval);
|
explicit String(char c) {
|
||||||
String(StringSumHelper &&rval);
|
sso.buff[0] = c;
|
||||||
#endif
|
sso.buff[1] = 0;
|
||||||
explicit String(char c);
|
sso.len = 1;
|
||||||
|
sso.isHeap = 0;
|
||||||
|
}
|
||||||
explicit String(unsigned char, unsigned char base = 10);
|
explicit String(unsigned char, unsigned char base = 10);
|
||||||
explicit String(int, unsigned char base = 10);
|
explicit String(int, unsigned char base = 10);
|
||||||
explicit String(unsigned int, unsigned char base = 10);
|
explicit String(unsigned int, unsigned char base = 10);
|
||||||
explicit String(long, unsigned char base = 10);
|
explicit String(long, unsigned char base = 10);
|
||||||
explicit String(unsigned long, unsigned char base = 10);
|
explicit String(unsigned long, unsigned char base = 10);
|
||||||
|
explicit String(long long /* base 10 */);
|
||||||
|
explicit String(long long, unsigned char base);
|
||||||
|
explicit String(unsigned long long /* base 10 */);
|
||||||
|
explicit String(unsigned long long, unsigned char base);
|
||||||
explicit String(float, unsigned char decimalPlaces = 2);
|
explicit String(float, unsigned char decimalPlaces = 2);
|
||||||
explicit String(double, unsigned char decimalPlaces = 2);
|
explicit String(double, unsigned char decimalPlaces = 2);
|
||||||
~String(void);
|
~String() {
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
// memory management
|
// memory management
|
||||||
// return true on success, false on failure (in which case, the string
|
// return true on success, false on failure (in which case, the string
|
||||||
// is left unchanged). reserve(0), if successful, will validate an
|
// is left unchanged). reserve(0), if successful, will validate an
|
||||||
// invalid string (i.e., "if (s)" will be true afterwards)
|
// invalid string (i.e., "if (s)" will be true afterwards)
|
||||||
unsigned char reserve(unsigned int size);
|
unsigned char reserve(unsigned int size);
|
||||||
inline unsigned int length(void) const {
|
unsigned int length(void) const {
|
||||||
if(buffer()) {
|
return buffer() ? len() : 0;
|
||||||
return len();
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
void clear(void) {
|
||||||
|
setLen(0);
|
||||||
|
}
|
||||||
|
bool isEmpty(void) const {
|
||||||
|
return length() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a copy of the assigned value. if the value is null or
|
// creates a copy of the assigned value. if the value is null or
|
||||||
@ -89,10 +107,12 @@ class String {
|
|||||||
String &operator =(const String &rhs);
|
String &operator =(const String &rhs);
|
||||||
String &operator =(const char *cstr);
|
String &operator =(const char *cstr);
|
||||||
String &operator =(const __FlashStringHelper *str);
|
String &operator =(const __FlashStringHelper *str);
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
String &operator =(String &&rval) noexcept;
|
||||||
String & operator =(String &&rval);
|
String &operator =(char c) {
|
||||||
String & operator =(StringSumHelper &&rval);
|
char buffer[2] { c, '\0' };
|
||||||
#endif
|
*this = buffer;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
// concatenate (works w/ built-in types)
|
// concatenate (works w/ built-in types)
|
||||||
|
|
||||||
@ -107,68 +127,20 @@ class String {
|
|||||||
unsigned char concat(unsigned int num);
|
unsigned char concat(unsigned int num);
|
||||||
unsigned char concat(long num);
|
unsigned char concat(long num);
|
||||||
unsigned char concat(unsigned long num);
|
unsigned char concat(unsigned long num);
|
||||||
|
unsigned char concat(long long num);
|
||||||
|
unsigned char concat(unsigned long long num);
|
||||||
unsigned char concat(float num);
|
unsigned char concat(float num);
|
||||||
unsigned char concat(double num);
|
unsigned char concat(double num);
|
||||||
unsigned char concat(const __FlashStringHelper *str);
|
unsigned char concat(const __FlashStringHelper *str);
|
||||||
|
unsigned char concat(const char *cstr, unsigned int length);
|
||||||
|
|
||||||
// if there's not enough memory for the concatenated value, the string
|
// if there's not enough memory for the concatenated value, the string
|
||||||
// will be left unchanged (but this isn't signalled in any way)
|
// will be left unchanged (but this isn't signalled in any way)
|
||||||
String & operator +=(const String &rhs) {
|
template <typename T>
|
||||||
|
String &operator +=(const T &rhs) {
|
||||||
concat(rhs);
|
concat(rhs);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
String & operator +=(const char *cstr) {
|
|
||||||
concat(cstr);
|
|
||||||
return (*this);
|
|
||||||
}
|
|
||||||
String & operator +=(char c) {
|
|
||||||
concat(c);
|
|
||||||
return (*this);
|
|
||||||
}
|
|
||||||
String & operator +=(unsigned char num) {
|
|
||||||
concat(num);
|
|
||||||
return (*this);
|
|
||||||
}
|
|
||||||
String & operator +=(int num) {
|
|
||||||
concat(num);
|
|
||||||
return (*this);
|
|
||||||
}
|
|
||||||
String & operator +=(unsigned int num) {
|
|
||||||
concat(num);
|
|
||||||
return (*this);
|
|
||||||
}
|
|
||||||
String & operator +=(long num) {
|
|
||||||
concat(num);
|
|
||||||
return (*this);
|
|
||||||
}
|
|
||||||
String & operator +=(unsigned long num) {
|
|
||||||
concat(num);
|
|
||||||
return (*this);
|
|
||||||
}
|
|
||||||
String & operator +=(float num) {
|
|
||||||
concat(num);
|
|
||||||
return (*this);
|
|
||||||
}
|
|
||||||
String & operator +=(double num) {
|
|
||||||
concat(num);
|
|
||||||
return (*this);
|
|
||||||
}
|
|
||||||
String & operator += (const __FlashStringHelper *str){
|
|
||||||
concat(str);
|
|
||||||
return (*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs);
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr);
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, char c);
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num);
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, int num);
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num);
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, long num);
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num);
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, float num);
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, double num);
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs);
|
|
||||||
|
|
||||||
// comparison (only works w/ Strings and "strings")
|
// comparison (only works w/ Strings and "strings")
|
||||||
operator StringIfHelperType() const {
|
operator StringIfHelperType() const {
|
||||||
@ -196,11 +168,25 @@ class String {
|
|||||||
unsigned char equalsIgnoreCase(const String &s) const;
|
unsigned char equalsIgnoreCase(const String &s) const;
|
||||||
unsigned char equalsConstantTime(const String &s) const;
|
unsigned char equalsConstantTime(const String &s) const;
|
||||||
unsigned char startsWith(const String &prefix) const;
|
unsigned char startsWith(const String &prefix) const;
|
||||||
|
unsigned char startsWith(const char *prefix) const {
|
||||||
|
return this->startsWith(String(prefix));
|
||||||
|
}
|
||||||
|
unsigned char startsWith(const __FlashStringHelper *prefix) const {
|
||||||
|
return this->startsWith(String(prefix));
|
||||||
|
}
|
||||||
unsigned char startsWith(const String &prefix, unsigned int offset) const;
|
unsigned char startsWith(const String &prefix, unsigned int offset) const;
|
||||||
unsigned char endsWith(const String &suffix) const;
|
unsigned char endsWith(const String &suffix) const;
|
||||||
|
unsigned char endsWith(const char *suffix) const {
|
||||||
|
return this->endsWith(String(suffix));
|
||||||
|
}
|
||||||
|
unsigned char endsWith(const __FlashStringHelper *suffix) const {
|
||||||
|
return this->endsWith(String(suffix));
|
||||||
|
}
|
||||||
|
|
||||||
// character access
|
// character access
|
||||||
char charAt(unsigned int index) const;
|
char charAt(unsigned int index) const {
|
||||||
|
return operator [](index);
|
||||||
|
}
|
||||||
void setCharAt(unsigned int index, char c);
|
void setCharAt(unsigned int index, char c);
|
||||||
char operator [](unsigned int index) const;
|
char operator [](unsigned int index) const;
|
||||||
char &operator [](unsigned int index);
|
char &operator [](unsigned int index);
|
||||||
@ -215,10 +201,12 @@ class String {
|
|||||||
const char *end() const { return c_str() + length(); }
|
const char *end() const { return c_str() + length(); }
|
||||||
|
|
||||||
// search
|
// search
|
||||||
int indexOf(char ch) const;
|
int indexOf(char ch, unsigned int fromIndex = 0) const;
|
||||||
int indexOf(char ch, unsigned int fromIndex) const;
|
int indexOf(const char *str, unsigned int fromIndex = 0) const;
|
||||||
int indexOf(const String &str) const;
|
int indexOf(const __FlashStringHelper *str, unsigned int fromIndex = 0) const {
|
||||||
int indexOf(const String &str, unsigned int fromIndex) const;
|
return indexOf((const char*)str, fromIndex);
|
||||||
|
}
|
||||||
|
int indexOf(const String &str, unsigned int fromIndex = 0) const;
|
||||||
int lastIndexOf(char ch) const;
|
int lastIndexOf(char ch) const;
|
||||||
int lastIndexOf(char ch, unsigned int fromIndex) const;
|
int lastIndexOf(char ch, unsigned int fromIndex) const;
|
||||||
int lastIndexOf(const String &str) const;
|
int lastIndexOf(const String &str) const;
|
||||||
@ -226,14 +214,29 @@ class String {
|
|||||||
String substring(unsigned int beginIndex) const {
|
String substring(unsigned int beginIndex) const {
|
||||||
return substring(beginIndex, len());
|
return substring(beginIndex, len());
|
||||||
}
|
}
|
||||||
;
|
|
||||||
String substring(unsigned int beginIndex, unsigned int endIndex) const;
|
String substring(unsigned int beginIndex, unsigned int endIndex) const;
|
||||||
|
|
||||||
// modification
|
// modification
|
||||||
void replace(char find, char replace);
|
void replace(char find, char replace);
|
||||||
void replace(const String &find, const String &replace);
|
void replace(const String &find, const String &replace);
|
||||||
void remove(unsigned int index);
|
void replace(const char *find, const String &replace) {
|
||||||
void remove(unsigned int index, unsigned int count);
|
this->replace(String(find), replace);
|
||||||
|
}
|
||||||
|
void replace(const __FlashStringHelper *find, const String &replace) {
|
||||||
|
this->replace(String(find), replace);
|
||||||
|
}
|
||||||
|
void replace(const char *find, const char *replace) {
|
||||||
|
this->replace(String(find), String(replace));
|
||||||
|
}
|
||||||
|
void replace(const __FlashStringHelper *find, const char *replace) {
|
||||||
|
this->replace(String(find), String(replace));
|
||||||
|
}
|
||||||
|
void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) {
|
||||||
|
this->replace(String(find), String(replace));
|
||||||
|
}
|
||||||
|
// Pass the biggest integer if the count is not specified.
|
||||||
|
// The remove method below will take care of truncating it at the end of the string.
|
||||||
|
void remove(unsigned int index, unsigned int count = (unsigned int)-1);
|
||||||
void toLowerCase(void);
|
void toLowerCase(void);
|
||||||
void toUpperCase(void);
|
void toUpperCase(void);
|
||||||
void trim(void);
|
void trim(void);
|
||||||
@ -250,75 +253,140 @@ class String {
|
|||||||
uint16_t cap;
|
uint16_t cap;
|
||||||
uint16_t len;
|
uint16_t len;
|
||||||
};
|
};
|
||||||
|
// This allows strings up up to 11 (10 + \0 termination) without any extra space.
|
||||||
// SSO is handled by checking the last byte of sso_buff.
|
enum { SSOSIZE = sizeof(struct _ptr) + 4 - 1 }; // Characters to allocate space for SSO, must be 12 or more
|
||||||
// When not in SSO mode, that byte is set to 0xff, while when in SSO mode it is always 0x00 (so it can serve as the string terminator as well as a flag)
|
struct _sso {
|
||||||
// This allows strings up up to 12 (11 + \0 termination) without any extra space.
|
char buff[SSOSIZE];
|
||||||
enum { SSOSIZE = sizeof(struct _ptr) + 4 }; // Characters to allocate space for SSO, must be 12 or more
|
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
|
||||||
enum { CAPACITY_MAX = 65535 }; // If size of capacity changed, be sure to update this enum
|
unsigned char isHeap : 1;
|
||||||
|
} __attribute__((packed)); // Ensure that GCC doesn't expand the flag byte to a 32-bit word for alignment issues
|
||||||
|
enum { CAPACITY_MAX = 65535 }; // If typeof(cap) changed from uint16_t, be sure to update this enum to the max value storable in the type
|
||||||
union {
|
union {
|
||||||
struct _ptr ptr;
|
struct _ptr ptr;
|
||||||
char sso_buf[SSOSIZE];
|
struct _sso sso;
|
||||||
};
|
};
|
||||||
// Accessor functions
|
// Accessor functions
|
||||||
inline bool sso() const { return sso_buf[SSOSIZE - 1] == 0; }
|
bool isSSO() const { return !sso.isHeap; }
|
||||||
inline unsigned int len() const { return sso() ? strlen(sso_buf) : ptr.len; }
|
unsigned int len() const { return isSSO() ? sso.len : ptr.len; }
|
||||||
inline unsigned int capacity() const { return sso() ? SSOSIZE - 1 : ptr.cap; }
|
unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL
|
||||||
inline void setSSO(bool sso) { sso_buf[SSOSIZE - 1] = sso ? 0x00 : 0xff; }
|
void setSSO(bool set) { sso.isHeap = !set; }
|
||||||
inline void setLen(int len) { if (!sso()) ptr.len = len; }
|
void setLen(int len) {
|
||||||
inline void setCapacity(int cap) { if (!sso()) ptr.cap = cap; }
|
if (isSSO()) {
|
||||||
inline void setBuffer(char *buff) { if (!sso()) ptr.buff = buff; }
|
setSSO(true); // Avoid emitting of bitwise EXTRACT-AND-OR ops (store-merging optimization)
|
||||||
|
sso.len = len;
|
||||||
|
} else
|
||||||
|
ptr.len = len;
|
||||||
|
}
|
||||||
|
void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; }
|
||||||
|
void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; }
|
||||||
// Buffer accessor functions
|
// Buffer accessor functions
|
||||||
inline const char *buffer() const { return (const char *)(sso() ? sso_buf : ptr.buff); }
|
const char *buffer() const { return wbuffer(); }
|
||||||
inline char *wbuffer() const { return sso() ? const_cast<char *>(sso_buf) : ptr.buff; } // Writable version of buffer
|
char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
|
||||||
|
|
||||||
|
// concatenation is done via non-member functions
|
||||||
|
// make sure we still have access to internal methods, since we optimize based on capacity of both sides and want to manipulate internal buffers directly
|
||||||
|
friend String operator +(const String &lhs, String &&rhs);
|
||||||
|
friend String operator +(String &&lhs, String &&rhs);
|
||||||
|
friend String operator +(char lhs, String &&rhs);
|
||||||
|
friend String operator +(const char *lhs, String &&rhs);
|
||||||
|
friend String operator +(const __FlashStringHelper *lhs, String &&rhs);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void init(void);
|
void init(void) __attribute__((always_inline)) {
|
||||||
|
sso.buff[0] = 0;
|
||||||
|
sso.len = 0;
|
||||||
|
sso.isHeap = 0;
|
||||||
|
// Without the 6 statements shown below, GCC simply emits such as: "MOVI.N aX,0", "S8I aX,a2,0" and "S8I aX,a2,11" (8 bytes in total)
|
||||||
|
sso.buff[1] = 0;
|
||||||
|
sso.buff[2] = 0;
|
||||||
|
sso.buff[3] = 0;
|
||||||
|
sso.buff[8] = 0;
|
||||||
|
sso.buff[9] = 0;
|
||||||
|
sso.buff[10] = 0;
|
||||||
|
// With the above, thanks to store-merging, GCC can use the narrow form of 32-bit store insn ("S32I.N") and emits:
|
||||||
|
// "MOVI.N aX,0", "S32I.N aX,a2,0" and "S32I.N aX,a2,8" (6 bytes in total)
|
||||||
|
// (Literature: Xtensa(R) Instruction Set Reference Manual, "S8I - Store 8-bit" [p.504] and "S32I.N - Narrow Store 32-bit" [p.512])
|
||||||
|
// Unfortunately, GCC seems not to re-evaluate the cost of inlining after the store-merging optimizer stage,
|
||||||
|
// `always_inline` attribute is necessary in order to keep inlining.
|
||||||
|
}
|
||||||
void invalidate(void);
|
void invalidate(void);
|
||||||
unsigned char changeBuffer(unsigned int maxStrLen);
|
unsigned char changeBuffer(unsigned int maxStrLen);
|
||||||
unsigned char concat(const char *cstr, unsigned int length);
|
|
||||||
|
|
||||||
// copy and move
|
// copy or insert at a specific position
|
||||||
String ©(const char *cstr, unsigned int length);
|
String ©(const char *cstr, unsigned int length);
|
||||||
String ©(const __FlashStringHelper *pstr, unsigned int length);
|
String ©(const __FlashStringHelper *pstr, unsigned int length);
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
|
||||||
void move(String &rhs);
|
String &insert(size_t position, char);
|
||||||
#endif
|
String &insert(size_t position, const char *);
|
||||||
|
String &insert(size_t position, const __FlashStringHelper *);
|
||||||
|
String &insert(size_t position, const char *, size_t length);
|
||||||
|
String &insert(size_t position, const String &);
|
||||||
|
|
||||||
|
// rvalue helper
|
||||||
|
void move(String &rhs) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
class StringSumHelper: public String {
|
// concatenation (note that it's done using non-method operators to handle both possible type refs)
|
||||||
public:
|
|
||||||
StringSumHelper(const String &s) :
|
inline String operator +(const String &lhs, const String &rhs) {
|
||||||
String(s) {
|
String res;
|
||||||
|
res.reserve(lhs.length() + rhs.length());
|
||||||
|
res += lhs;
|
||||||
|
res += rhs;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
StringSumHelper(const char *p) :
|
|
||||||
String(p) {
|
inline String operator +(String &&lhs, const String &rhs) {
|
||||||
|
lhs += rhs;
|
||||||
|
return std::move(lhs);
|
||||||
}
|
}
|
||||||
StringSumHelper(char c) :
|
|
||||||
String(c) {
|
String operator +(const String &lhs, String &&rhs);
|
||||||
|
String operator +(String &&lhs, String &&rhs);
|
||||||
|
|
||||||
|
template <typename T,
|
||||||
|
typename = std::enable_if_t<!std::is_same_v<String, std::decay_t<T>>>>
|
||||||
|
inline String operator +(const String &lhs, const T &value) {
|
||||||
|
String res(lhs);
|
||||||
|
res += value;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
StringSumHelper(unsigned char num) :
|
|
||||||
String(num) {
|
template <typename T,
|
||||||
|
typename = std::enable_if_t<!std::is_same_v<String, std::decay_t<T>>>>
|
||||||
|
inline String operator +(String &&lhs, const T &value) {
|
||||||
|
lhs += value;
|
||||||
|
return std::move(lhs);
|
||||||
}
|
}
|
||||||
StringSumHelper(int num) :
|
|
||||||
String(num) {
|
// `String(char)` is explicit, but we used to have StringSumHelper silently allowing the following:
|
||||||
|
// `String x; x = 'a' + String('b') + 'c';`
|
||||||
|
// For comparison, `std::string(char)` does not exist. However, we are allowed to chain `char` as both lhs and rhs
|
||||||
|
|
||||||
|
String operator +(char lhs, const String &rhs);
|
||||||
|
|
||||||
|
inline String operator +(char lhs, String &&rhs) {
|
||||||
|
return std::move(rhs.insert(0, lhs));
|
||||||
}
|
}
|
||||||
StringSumHelper(unsigned int num) :
|
|
||||||
String(num) {
|
// both `char*` and `__FlashStringHelper*` are implicitly converted into `String()`, calling the `operator+(const String& ...);`
|
||||||
|
// however, here we:
|
||||||
|
// - do an automatic `reserve(total length)` for the resulting string
|
||||||
|
// - possibly do rhs.insert(0, ...), when &&rhs capacity could fit both
|
||||||
|
|
||||||
|
String operator +(const char *lhs, const String &rhs);
|
||||||
|
|
||||||
|
inline String operator +(const char *lhs, String &&rhs) {
|
||||||
|
return std::move(rhs.insert(0, lhs));
|
||||||
}
|
}
|
||||||
StringSumHelper(long num) :
|
|
||||||
String(num) {
|
inline String operator +(const __FlashStringHelper *lhs, const String &rhs) {
|
||||||
|
return reinterpret_cast<const char*>(lhs) + rhs;
|
||||||
}
|
}
|
||||||
StringSumHelper(unsigned long num) :
|
|
||||||
String(num) {
|
inline String operator +(const __FlashStringHelper *lhs, String &&rhs) {
|
||||||
|
return std::move(rhs.insert(0, lhs));
|
||||||
}
|
}
|
||||||
StringSumHelper(float num) :
|
|
||||||
String(num) {
|
|
||||||
}
|
|
||||||
StringSumHelper(double num) :
|
|
||||||
String(num) {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
extern const String emptyString;
|
extern const String emptyString;
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <debug.h>
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <cxxabi.h>
|
#include <cxxabi.h>
|
||||||
|
|
||||||
@ -31,6 +30,55 @@ extern int umm_last_fail_alloc_size;
|
|||||||
extern "C" void __cxa_pure_virtual(void) __attribute__ ((__noreturn__));
|
extern "C" void __cxa_pure_virtual(void) __attribute__ ((__noreturn__));
|
||||||
extern "C" void __cxa_deleted_virtual(void) __attribute__ ((__noreturn__));
|
extern "C" void __cxa_deleted_virtual(void) __attribute__ ((__noreturn__));
|
||||||
|
|
||||||
|
|
||||||
|
#if !defined(__cpp_exceptions)
|
||||||
|
|
||||||
|
// overwrite weak operators new/new[] definitions
|
||||||
|
|
||||||
|
void* operator new(size_t size)
|
||||||
|
{
|
||||||
|
void *ret = malloc(size);
|
||||||
|
if (0 != size && 0 == ret) {
|
||||||
|
umm_last_fail_alloc_addr = __builtin_return_address(0);
|
||||||
|
umm_last_fail_alloc_size = size;
|
||||||
|
__unhandled_exception(PSTR("OOM"));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* operator new[](size_t size)
|
||||||
|
{
|
||||||
|
void *ret = malloc(size);
|
||||||
|
if (0 != size && 0 == ret) {
|
||||||
|
umm_last_fail_alloc_addr = __builtin_return_address(0);
|
||||||
|
umm_last_fail_alloc_size = size;
|
||||||
|
__unhandled_exception(PSTR("OOM"));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* operator new (size_t size, const std::nothrow_t&)
|
||||||
|
{
|
||||||
|
void *ret = malloc(size);
|
||||||
|
if (0 != size && 0 == ret) {
|
||||||
|
umm_last_fail_alloc_addr = __builtin_return_address(0);
|
||||||
|
umm_last_fail_alloc_size = size;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* operator new[] (size_t size, const std::nothrow_t&)
|
||||||
|
{
|
||||||
|
void *ret = malloc(size);
|
||||||
|
if (0 != size && 0 == ret) {
|
||||||
|
umm_last_fail_alloc_addr = __builtin_return_address(0);
|
||||||
|
umm_last_fail_alloc_size = size;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !defined(__cpp_exceptions)
|
||||||
|
|
||||||
void __cxa_pure_virtual(void)
|
void __cxa_pure_virtual(void)
|
||||||
{
|
{
|
||||||
panic();
|
panic();
|
||||||
|
163
cores/esp8266/aes_unwrap.cpp
Normal file
163
cores/esp8266/aes_unwrap.cpp
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* Replacement for the ROM aes_unwrap() function. It uses the heap instead of
|
||||||
|
* the static DRAM address at 0x3FFFEA80, which may step on the SYS stack in
|
||||||
|
* special circumstances such as HWDT Stack Dump.
|
||||||
|
*
|
||||||
|
* When not using WPS, the address space 0x3FFFE000 up to 0x40000000 is mostly
|
||||||
|
* available for the stacks. The one known exception is the ROM AES APIs. When
|
||||||
|
* `aes_decrypt_init` is called, it uses memory at 0x3FFFEA80 up to 0x3FFFEB30
|
||||||
|
* for a buffer. At the finish, `aes_decrypt_deinit` zeros out the buffer.
|
||||||
|
*
|
||||||
|
* The NONOS SDK appears to have replacements for most of the ROM's AES APIs.
|
||||||
|
* However, the SDK still calls on the ROM's aes_unwrap function, which uses
|
||||||
|
* the ROM's AES APIs to operate. These calls can overwrite some of the stack
|
||||||
|
* space. To resolve the problem, this module replaces `aes_unwrap`.
|
||||||
|
*
|
||||||
|
* Final note, so far, I have not seen a problem when using the extra 4K heap
|
||||||
|
* option without the "debug HWDT". It is when combined with the HWDT Stack
|
||||||
|
* Dump that a problem shows. This combination adds a Boot ROM stack, which
|
||||||
|
* pushes up the SYS and CONT stacks into the AES Buffer space. Then the
|
||||||
|
* problem shows.
|
||||||
|
*
|
||||||
|
* While debugging with painted stack space, during WiFi Connect, Reconnect,
|
||||||
|
* and about every hour, a block of memory 0x3FFFEA80 - 0x3FFFEB30 (176 bytes)
|
||||||
|
* was zeroed by the Boot ROM function aes_decrypt_init. All other painted
|
||||||
|
* memory in the area was untouched after starting WiFi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(KEEP_ROM_AES_UNWRAP)
|
||||||
|
// Using the ROM version of aes_unwrap should be fine for the no extra 4K case
|
||||||
|
// which is usually used in conjunction with WPS.
|
||||||
|
|
||||||
|
#else
|
||||||
|
// This is required for DEBUG_ESP_HWDT.
|
||||||
|
// The need is unconfirmed for the extra 4K heap case.
|
||||||
|
#include "umm_malloc/umm_malloc.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
// Uses this function from the Boot ROM
|
||||||
|
void rijndaelKeySetupDec(u32 rk[], const u8 cipherKey[]);
|
||||||
|
|
||||||
|
// This replaces the Boot ROM version just for this module
|
||||||
|
// Uses a malloc-ed buffer instead of the static buffer in stack address space.
|
||||||
|
static void *aes_decrypt_init(const u8 *key, size_t len) {
|
||||||
|
if (16u != len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
u32 *rk = (u32 *)malloc(16*11);
|
||||||
|
// u32 *rk = (u32 *)0x3FFFEA80u; // This is what the ROM would have used.
|
||||||
|
if (rk) {
|
||||||
|
rijndaelKeySetupDec(rk, key);
|
||||||
|
}
|
||||||
|
return (void *)rk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This replaces the Boot ROM version just for this module
|
||||||
|
static void aes_decrypt_deinit(void *ctx) {
|
||||||
|
if (ctx) {
|
||||||
|
ets_memset(ctx, 0, 16*11);
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The NONOS SDK has an override on this function. To replace the aes_unwrap
|
||||||
|
* without changing its behavior too much. We need access to the ROM version of
|
||||||
|
* the AES APIs to make our aes_unwrap functionally equal to the current
|
||||||
|
* environment except for the AES Buffer.
|
||||||
|
*/
|
||||||
|
#ifndef ROM_aes_decrypt
|
||||||
|
#define ROM_aes_decrypt 0x400092d4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void (*fp_aes_decrypt_t)(void *ctx, const u8 *crypt, u8 *plain);
|
||||||
|
#define AES_DECRYPT (reinterpret_cast<fp_aes_decrypt_t>(ROM_aes_decrypt))
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
/*
|
||||||
|
* This aes_unwrap() function overrides/replaces the Boot ROM version.
|
||||||
|
*
|
||||||
|
* It was adapted from aes_unwrap() found in the ESP8266 RTOS SDK
|
||||||
|
* .../components/wap_supplicant/src/crypto/aes-unwrap.c
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
/*
|
||||||
|
* AES key unwrap (128-bit KEK, RFC3394)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2003-2007, Jouni Malinen <j@w1.fi>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* Alternatively, this software may be distributed under the terms of BSD
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* See README and COPYING for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** based on RTOS SDK
|
||||||
|
* aes_unwrap - Unwrap key with AES Key Wrap Algorithm (128-bit KEK) (RFC3394)
|
||||||
|
* @kek: Key encryption key (KEK)
|
||||||
|
* @n: Length of the plaintext key in 64-bit units; e.g., 2 = 128-bit = 16
|
||||||
|
* bytes
|
||||||
|
* @cipher: Wrapped key to be unwrapped, (n + 1) * 64 bits
|
||||||
|
* @plain: Plaintext key, n * 64 bits
|
||||||
|
* Returns: 0 on success, -1 on failure (e.g., integrity verification failed)
|
||||||
|
*/
|
||||||
|
int aes_unwrap(const u8 *kek, int n, const u8 *cipher, u8 *plain)
|
||||||
|
{
|
||||||
|
u8 a[8], *r, b[16];
|
||||||
|
int i, j;
|
||||||
|
void *ctx;
|
||||||
|
|
||||||
|
/* 1) Initialize variables. */
|
||||||
|
ets_memcpy(a, cipher, 8);
|
||||||
|
r = plain;
|
||||||
|
ets_memcpy(r, cipher + 8, 8 * n);
|
||||||
|
|
||||||
|
ctx = aes_decrypt_init(kek, 16);
|
||||||
|
if (ctx == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* 2) Compute intermediate values.
|
||||||
|
* For j = 5 to 0
|
||||||
|
* For i = n to 1
|
||||||
|
* B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i
|
||||||
|
* A = MSB(64, B)
|
||||||
|
* R[i] = LSB(64, B)
|
||||||
|
*/
|
||||||
|
for (j = 5; j >= 0; j--) {
|
||||||
|
r = plain + (n - 1) * 8;
|
||||||
|
for (i = n; i >= 1; i--) {
|
||||||
|
ets_memcpy(b, a, 8);
|
||||||
|
b[7] ^= n * j + i;
|
||||||
|
|
||||||
|
ets_memcpy(b + 8, r, 8);
|
||||||
|
AES_DECRYPT(ctx, b, b);
|
||||||
|
ets_memcpy(a, b, 8);
|
||||||
|
ets_memcpy(r, b + 8, 8);
|
||||||
|
r -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aes_decrypt_deinit(ctx);
|
||||||
|
|
||||||
|
/* 3) Output results.
|
||||||
|
*
|
||||||
|
* These are already in @plain due to the location of temporary
|
||||||
|
* variables. Just verify that the IV matches with the expected value.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
if (a[i] != 0xa6)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
1
cores/esp8266/avr/dtostrf.h
Normal file
1
cores/esp8266/avr/dtostrf.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include <stdlib_noniso.h>
|
@ -24,23 +24,27 @@
|
|||||||
|
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "libb64/cdecode.h"
|
|
||||||
#include "libb64/cencode.h"
|
#include "libb64/cencode.h"
|
||||||
}
|
}
|
||||||
#include "base64.h"
|
#include "base64.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* convert input data to base64
|
* convert input data to base64
|
||||||
* @param data uint8_t *
|
* @param data const uint8_t *
|
||||||
* @param length size_t
|
* @param length size_t
|
||||||
* @return String
|
* @return String
|
||||||
*/
|
*/
|
||||||
String base64::encode(uint8_t * data, size_t length, bool doNewLines) {
|
String base64::encode(const uint8_t * data, size_t length, bool doNewLines)
|
||||||
|
{
|
||||||
|
String base64;
|
||||||
|
|
||||||
// base64 needs more size then the source data, use cencode.h macros
|
// base64 needs more size then the source data, use cencode.h macros
|
||||||
size_t size = ((doNewLines ? base64_encode_expected_len( length )
|
size_t size = ((doNewLines ? base64_encode_expected_len( length )
|
||||||
: base64_encode_expected_len_nonewlines( length )) + 1);
|
: base64_encode_expected_len_nonewlines( length )) + 1);
|
||||||
char * buffer = (char *) malloc(size);
|
|
||||||
if(buffer) {
|
if (base64.reserve(size))
|
||||||
|
{
|
||||||
|
|
||||||
base64_encodestate _state;
|
base64_encodestate _state;
|
||||||
if (doNewLines)
|
if (doNewLines)
|
||||||
{
|
{
|
||||||
@ -50,22 +54,23 @@ String base64::encode(uint8_t * data, size_t length, bool doNewLines) {
|
|||||||
{
|
{
|
||||||
base64_init_encodestate_nonewlines(&_state);
|
base64_init_encodestate_nonewlines(&_state);
|
||||||
}
|
}
|
||||||
int len = base64_encode_block((const char *) &data[0], length, &buffer[0], &_state);
|
|
||||||
len = base64_encode_blockend((buffer + len), &_state);
|
|
||||||
|
|
||||||
String base64 = String(buffer);
|
constexpr size_t BUFSIZE = 48;
|
||||||
free(buffer);
|
char buf[BUFSIZE + 1 /* newline */ + 1 /* NUL */];
|
||||||
|
for (size_t len = 0; len < length; len += BUFSIZE * 3 / 4)
|
||||||
|
{
|
||||||
|
size_t blocklen = base64_encode_block((const char*) data + len,
|
||||||
|
std::min( BUFSIZE * 3 / 4, length - len ), buf, &_state);
|
||||||
|
buf[blocklen] = '\0';
|
||||||
|
base64 += buf;
|
||||||
|
}
|
||||||
|
if (base64_encode_blockend(buf, &_state))
|
||||||
|
base64 += buf;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base64 = F("-FAIL-");
|
||||||
|
}
|
||||||
|
|
||||||
return base64;
|
return base64;
|
||||||
}
|
}
|
||||||
return String("-FAIL-");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* convert input data to base64
|
|
||||||
* @param text String
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
String base64::encode(String text, bool doNewLines) {
|
|
||||||
return base64::encode((uint8_t *) text.c_str(), text.length(), doNewLines);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -25,13 +25,31 @@
|
|||||||
#ifndef CORE_BASE64_H_
|
#ifndef CORE_BASE64_H_
|
||||||
#define CORE_BASE64_H_
|
#define CORE_BASE64_H_
|
||||||
|
|
||||||
class base64 {
|
#include <WString.h>
|
||||||
|
|
||||||
|
class base64
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
// NOTE: The default behaviour of backend (lib64)
|
// NOTE: The default behaviour of backend (lib64)
|
||||||
// is to add a newline every 72 (encoded) characters output.
|
// is to add a newline every 72 (encoded) characters output.
|
||||||
// This may 'break' longer uris and json variables
|
// This may 'break' longer uris and json variables
|
||||||
static String encode(uint8_t * data, size_t length, bool doNewLines = true);
|
static String encode(const uint8_t * data, size_t length, bool doNewLines);
|
||||||
static String encode(String text, bool doNewLines = true);
|
static inline String encode(const String& text, bool doNewLines)
|
||||||
|
{
|
||||||
|
return encode( (const uint8_t *) text.c_str(), text.length(), doNewLines );
|
||||||
|
}
|
||||||
|
|
||||||
|
// esp32 compat:
|
||||||
|
|
||||||
|
static inline String encode(const uint8_t * data, size_t length)
|
||||||
|
{
|
||||||
|
return encode(data, length, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline String encode(const String& text)
|
||||||
|
{
|
||||||
|
return encode(text, false);
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <new> // std::nothrow
|
||||||
#include "cbuf.h"
|
#include "cbuf.h"
|
||||||
#include "c_types.h"
|
#include "c_types.h"
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ size_t cbuf::resize(size_t newSize) {
|
|||||||
return _size;
|
return _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *newbuf = new char[newSize];
|
char *newbuf = new (std::nothrow) char[newSize];
|
||||||
char *oldbuf = _buf;
|
char *oldbuf = _buf;
|
||||||
|
|
||||||
if(!newbuf) {
|
if(!newbuf) {
|
||||||
@ -66,7 +67,7 @@ size_t cbuf::resize(size_t newSize) {
|
|||||||
return _size;
|
return _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ICACHE_RAM_ATTR cbuf::available() const {
|
size_t IRAM_ATTR cbuf::available() const {
|
||||||
if(_end >= _begin) {
|
if(_end >= _begin) {
|
||||||
return _end - _begin;
|
return _end - _begin;
|
||||||
}
|
}
|
||||||
@ -107,7 +108,7 @@ size_t cbuf::peek(char *dst, size_t size) {
|
|||||||
return size_read;
|
return size_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ICACHE_RAM_ATTR cbuf::read() {
|
int IRAM_ATTR cbuf::read() {
|
||||||
if(empty())
|
if(empty())
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@ -132,7 +133,7 @@ size_t cbuf::read(char* dst, size_t size) {
|
|||||||
return size_read;
|
return size_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ICACHE_RAM_ATTR cbuf::write(char c) {
|
size_t IRAM_ATTR cbuf::write(char c) {
|
||||||
if(full())
|
if(full())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.text
|
.section .irom0.text
|
||||||
.align 4
|
.align 4
|
||||||
.literal_position
|
.literal_position
|
||||||
.global cont_yield
|
.global cont_yield
|
||||||
@ -84,7 +84,7 @@ cont_wrapper:
|
|||||||
|
|
||||||
////////////////////////////////////////////////////
|
////////////////////////////////////////////////////
|
||||||
|
|
||||||
.text
|
.section .irom0.text
|
||||||
.align 4
|
.align 4
|
||||||
.literal_position
|
.literal_position
|
||||||
.global cont_run
|
.global cont_run
|
||||||
|
@ -42,7 +42,7 @@ void cont_init(cont_t* cont) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int ICACHE_RAM_ATTR cont_check(cont_t* cont) {
|
int IRAM_ATTR cont_check(cont_t* cont) {
|
||||||
if(cont->stack_guard1 != CONT_STACKGUARD || cont->stack_guard2 != CONT_STACKGUARD) return 1;
|
if(cont->stack_guard1 != CONT_STACKGUARD || cont->stack_guard2 != CONT_STACKGUARD) return 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -62,7 +62,7 @@ int cont_get_free_stack(cont_t* cont) {
|
|||||||
return freeWords * 4;
|
return freeWords * 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ICACHE_RAM_ATTR cont_can_yield(cont_t* cont) {
|
bool IRAM_ATTR cont_can_yield(cont_t* cont) {
|
||||||
return !ETS_INTR_WITHINISR() &&
|
return !ETS_INTR_WITHINISR() &&
|
||||||
cont->pc_ret != 0 && cont->pc_yield == 0;
|
cont->pc_ret != 0 && cont->pc_yield == 0;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ extern "C" void call_user_start();
|
|||||||
/* this is the default NONOS-SDK user's heap location */
|
/* this is the default NONOS-SDK user's heap location */
|
||||||
static cont_t g_cont __attribute__ ((aligned (16)));
|
static cont_t g_cont __attribute__ ((aligned (16)));
|
||||||
|
|
||||||
extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void)
|
extern "C" void app_entry_redefinable(void)
|
||||||
{
|
{
|
||||||
g_pcont = &g_cont;
|
g_pcont = &g_cont;
|
||||||
|
|
||||||
|
@ -21,36 +21,15 @@
|
|||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include "coredecls.h"
|
||||||
#include "eboot_command.h"
|
#include "eboot_command.h"
|
||||||
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static uint32_t crc_update(uint32_t crc, const uint8_t *data, size_t length)
|
|
||||||
{
|
|
||||||
uint32_t i;
|
|
||||||
bool bit;
|
|
||||||
uint8_t c;
|
|
||||||
|
|
||||||
while (length--) {
|
|
||||||
c = *data++;
|
|
||||||
for (i = 0x80; i > 0; i >>= 1) {
|
|
||||||
bit = crc & 0x80000000;
|
|
||||||
if (c & i) {
|
|
||||||
bit = !bit;
|
|
||||||
}
|
|
||||||
crc <<= 1;
|
|
||||||
if (bit) {
|
|
||||||
crc ^= 0x04c11db7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t eboot_command_calculate_crc32(const struct eboot_command* cmd)
|
static uint32_t eboot_command_calculate_crc32(const struct eboot_command* cmd)
|
||||||
{
|
{
|
||||||
return crc_update(0xffffffff, (const uint8_t*) cmd,
|
return crc32((const uint8_t*) cmd, offsetof(struct eboot_command, crc32));
|
||||||
offsetof(struct eboot_command, crc32));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int eboot_command_read(struct eboot_command* cmd)
|
int eboot_command_read(struct eboot_command* cmd)
|
||||||
|
51
cores/esp8266/core_esp8266_features.cpp
Normal file
51
cores/esp8266/core_esp8266_features.cpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
core_esp8266_features.cpp
|
||||||
|
|
||||||
|
Copyright (c) 2019 Mike Nix. 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 <stdint.h>
|
||||||
|
|
||||||
|
/* precache()
|
||||||
|
* pre-loads flash data into the flash cache
|
||||||
|
* if f==0, preloads instructions starting at the address we were called from.
|
||||||
|
* otherwise preloads flash at the given address.
|
||||||
|
* All preloads are word aligned.
|
||||||
|
*/
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void precache(void *f, uint32_t bytes) {
|
||||||
|
// Size of a cache page in bytes. We only need to read one word per
|
||||||
|
// page (ie 1 word in 8) for this to work.
|
||||||
|
#define CACHE_PAGE_SIZE 32
|
||||||
|
|
||||||
|
uint32_t a0;
|
||||||
|
__asm__("mov.n %0, a0" : "=r"(a0));
|
||||||
|
uint32_t lines = (bytes/CACHE_PAGE_SIZE)+2;
|
||||||
|
volatile uint32_t *p = (uint32_t*)((f ? (uint32_t)f : a0) & ~0x03);
|
||||||
|
uint32_t x;
|
||||||
|
for (uint32_t i=0; i<lines; i++, p+=CACHE_PAGE_SIZE/sizeof(uint32_t)) x=*p;
|
||||||
|
(void)x;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
@ -32,8 +32,98 @@
|
|||||||
|
|
||||||
#define WIFI_HAS_EVENT_CALLBACK
|
#define WIFI_HAS_EVENT_CALLBACK
|
||||||
|
|
||||||
|
#include <stdlib.h> // malloc()
|
||||||
|
#include <stddef.h> // size_t
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifndef __STRINGIFY
|
||||||
|
#define __STRINGIFY(a) #a
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// these low level routines provide a replacement for SREG interrupt save that AVR uses
|
||||||
|
// but are esp8266 specific. A normal use pattern is like
|
||||||
|
//
|
||||||
|
//{
|
||||||
|
// uint32_t savedPS = xt_rsil(1); // this routine will allow level 2 and above
|
||||||
|
// // do work here
|
||||||
|
// xt_wsr_ps(savedPS); // restore the state
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
// level (0-15), interrupts of the given level and above will be active
|
||||||
|
// level 15 will disable ALL interrupts,
|
||||||
|
// level 0 will enable ALL interrupts,
|
||||||
|
//
|
||||||
|
#ifndef CORE_MOCK
|
||||||
|
|
||||||
|
#define xt_rsil(level) (__extension__({uint32_t state; __asm__ __volatile__("rsil %0," __STRINGIFY(level) : "=a" (state) :: "memory"); state;}))
|
||||||
|
#define xt_wsr_ps(state) __asm__ __volatile__("wsr %0,ps; isync" :: "a" (state) : "memory")
|
||||||
|
|
||||||
|
inline uint32_t esp_get_cycle_count() __attribute__((always_inline));
|
||||||
|
inline uint32_t esp_get_cycle_count() {
|
||||||
|
uint32_t ccount;
|
||||||
|
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
|
||||||
|
return ccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint32_t esp_get_program_counter() __attribute__((always_inline));
|
||||||
|
inline uint32_t esp_get_program_counter() {
|
||||||
|
uint32_t pc;
|
||||||
|
__asm__ __volatile__("movi %0, ." : "=r" (pc) : : ); // ©earlephilhower
|
||||||
|
return pc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // CORE_MOCK
|
||||||
|
|
||||||
|
#define xt_rsil(level) (level)
|
||||||
|
#define xt_wsr_ps(state) do { (void)(state); } while (0)
|
||||||
|
|
||||||
|
inline uint32_t esp_get_program_counter() { return 0; }
|
||||||
|
|
||||||
|
#endif // CORE_MOCK
|
||||||
|
|
||||||
|
|
||||||
|
// Tools for preloading code into the flash cache
|
||||||
|
#define PRECACHE_ATTR __attribute__((optimize("no-reorder-blocks"))) \
|
||||||
|
__attribute__((noinline))
|
||||||
|
|
||||||
|
#define PRECACHE_START(tag) \
|
||||||
|
precache(NULL,(uint8_t *)&&_precache_end_##tag - (uint8_t*)&&_precache_start_##tag); \
|
||||||
|
_precache_start_##tag:
|
||||||
|
|
||||||
|
#define PRECACHE_END(tag) \
|
||||||
|
_precache_end_##tag:
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void precache(void *f, uint32_t bytes);
|
||||||
|
unsigned long millis(void);
|
||||||
|
unsigned long micros(void);
|
||||||
|
uint64_t micros64(void);
|
||||||
|
void delay(unsigned long);
|
||||||
|
void delayMicroseconds(unsigned int us);
|
||||||
|
|
||||||
|
#if defined(F_CPU) || defined(CORE_MOCK)
|
||||||
|
#ifdef __cplusplus
|
||||||
|
constexpr
|
||||||
|
#else
|
||||||
|
inline
|
||||||
|
#endif
|
||||||
|
int esp_get_cpu_freq_mhz()
|
||||||
|
{
|
||||||
|
return F_CPU / 1000000L;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
inline int esp_get_cpu_freq_mhz()
|
||||||
|
{
|
||||||
|
return system_get_cpu_freq();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // CORE_ESP8266_FEATURES_H
|
||||||
|
85
cores/esp8266/core_esp8266_flash_quirks.cpp
Normal file
85
cores/esp8266/core_esp8266_flash_quirks.cpp
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
flash_quirks.cpp - Chip specific flash init
|
||||||
|
Copyright (c) 2019 Mike Nix. 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 <c_types.h>
|
||||||
|
#include "spi_flash.h"
|
||||||
|
|
||||||
|
#include "spi_utils.h"
|
||||||
|
#include "flash_quirks.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace experimental {
|
||||||
|
|
||||||
|
static int get_flash_mhz() {
|
||||||
|
// FIXME: copied from Esp.cpp - we really should define the magic values
|
||||||
|
uint32_t data;
|
||||||
|
uint8_t * bytes = (uint8_t *) &data;
|
||||||
|
// read first 4 byte (magic byte + flash config)
|
||||||
|
if(spi_flash_read(0x0000, &data, 4) == SPI_FLASH_RESULT_OK) {
|
||||||
|
switch (bytes[3] & 0x0F) {
|
||||||
|
case 0x0: // 40 MHz
|
||||||
|
return 40;
|
||||||
|
case 0x1: // 26 MHz
|
||||||
|
return 26;
|
||||||
|
case 0x2: // 20 MHz
|
||||||
|
return 20;
|
||||||
|
case 0xf: // 80 MHz
|
||||||
|
return 80;
|
||||||
|
default: // fail?
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* initFlashQuirks()
|
||||||
|
* Do any chip-specific initialization to improve performance and reliability.
|
||||||
|
*/
|
||||||
|
void initFlashQuirks() {
|
||||||
|
using namespace experimental;
|
||||||
|
uint32_t vendor = spi_flash_get_id() & 0x000000ff;
|
||||||
|
|
||||||
|
switch (vendor) {
|
||||||
|
case SPI_FLASH_VENDOR_XMC:
|
||||||
|
uint32_t SR3, newSR3;
|
||||||
|
if (SPI0Command(SPI_FLASH_CMD_RSR3, &SR3, 0, 8)==SPI_RESULT_OK) { // read SR3
|
||||||
|
newSR3=SR3;
|
||||||
|
if (get_flash_mhz()>26) { // >26Mhz?
|
||||||
|
// Set the output drive to 100%
|
||||||
|
newSR3 &= ~(SPI_FLASH_SR3_XMC_DRV_MASK << SPI_FLASH_SR3_XMC_DRV_S);
|
||||||
|
newSR3 |= (SPI_FLASH_SR3_XMC_DRV_100 << SPI_FLASH_SR3_XMC_DRV_S);
|
||||||
|
}
|
||||||
|
if (newSR3 != SR3) { // only write if changed
|
||||||
|
if (SPI0Command(SPI_FLASH_CMD_WEVSR,NULL,0,0)==SPI_RESULT_OK) // write enable volatile SR
|
||||||
|
SPI0Command(SPI_FLASH_CMD_WSR3,&newSR3,8,0); // write to SR3
|
||||||
|
SPI0Command(SPI_FLASH_CMD_WRDI,NULL,0,0); // write disable - probably not needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace experimental
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
@ -24,7 +24,7 @@
|
|||||||
#include "osapi.h"
|
#include "osapi.h"
|
||||||
#include "ets_sys.h"
|
#include "ets_sys.h"
|
||||||
#include "i2s_reg.h"
|
#include "i2s_reg.h"
|
||||||
#include "i2s.h"
|
#include "core_esp8266_i2s.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
@ -61,9 +61,10 @@ typedef struct i2s_state {
|
|||||||
uint32_t * curr_slc_buf; // Current buffer for writing
|
uint32_t * curr_slc_buf; // Current buffer for writing
|
||||||
uint32_t curr_slc_buf_pos; // Position in the current buffer
|
uint32_t curr_slc_buf_pos; // Position in the current buffer
|
||||||
void (*callback) (void);
|
void (*callback) (void);
|
||||||
// Callback function should be defined as 'void ICACHE_RAM_ATTR function_name()',
|
// Callback function should be defined as 'void IRAM_ATTR function_name()',
|
||||||
// and be placed in IRAM for faster execution. Avoid long computational tasks in this
|
// and be placed in IRAM for faster execution. Avoid long computational tasks in this
|
||||||
// function, use it to set flags and process later.
|
// function, use it to set flags and process later.
|
||||||
|
bool driveClocks;
|
||||||
} i2s_state_t;
|
} i2s_state_t;
|
||||||
|
|
||||||
// RX = I2S receive (i.e. microphone), TX = I2S transmit (i.e. DAC)
|
// RX = I2S receive (i.e. microphone), TX = I2S transmit (i.e. DAC)
|
||||||
@ -72,6 +73,7 @@ static i2s_state_t *tx = NULL;
|
|||||||
|
|
||||||
// Last I2S sample rate requested
|
// Last I2S sample rate requested
|
||||||
static uint32_t _i2s_sample_rate;
|
static uint32_t _i2s_sample_rate;
|
||||||
|
static int _i2s_bits = 16;
|
||||||
|
|
||||||
// IOs used for I2S. Not defined in i2s.h, unfortunately.
|
// IOs used for I2S. Not defined in i2s.h, unfortunately.
|
||||||
// Note these are internal GPIO numbers and not pins on an
|
// Note these are internal GPIO numbers and not pins on an
|
||||||
@ -83,6 +85,14 @@ static uint32_t _i2s_sample_rate;
|
|||||||
#define I2SI_BCK 13
|
#define I2SI_BCK 13
|
||||||
#define I2SI_WS 14
|
#define I2SI_WS 14
|
||||||
|
|
||||||
|
bool i2s_set_bits(int bits) {
|
||||||
|
if (tx || rx || (bits != 16 && bits != 24)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_i2s_bits = bits;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool _i2s_is_full(const i2s_state_t *ch) {
|
static bool _i2s_is_full(const i2s_state_t *ch) {
|
||||||
if (!ch) {
|
if (!ch) {
|
||||||
return false;
|
return false;
|
||||||
@ -129,7 +139,7 @@ uint16_t i2s_rx_available(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pop the top off of the queue and return it
|
// Pop the top off of the queue and return it
|
||||||
static uint32_t * ICACHE_RAM_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) {
|
static uint32_t * IRAM_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) {
|
||||||
uint8_t i;
|
uint8_t i;
|
||||||
uint32_t *item = ch->slc_queue[0];
|
uint32_t *item = ch->slc_queue[0];
|
||||||
ch->slc_queue_len--;
|
ch->slc_queue_len--;
|
||||||
@ -140,7 +150,7 @@ static uint32_t * ICACHE_RAM_ATTR i2s_slc_queue_next_item(i2s_state_t *ch) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Append an item to the end of the queue from receive
|
// Append an item to the end of the queue from receive
|
||||||
static void ICACHE_RAM_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t *item) {
|
static void IRAM_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t *item) {
|
||||||
// Shift everything up, except for the one corresponding to this item
|
// Shift everything up, except for the one corresponding to this item
|
||||||
for (int i=0, dest=0; i < ch->slc_queue_len; i++) {
|
for (int i=0, dest=0; i < ch->slc_queue_len; i++) {
|
||||||
if (ch->slc_queue[i] != item) {
|
if (ch->slc_queue[i] != item) {
|
||||||
@ -154,7 +164,7 @@ static void ICACHE_RAM_ATTR i2s_slc_queue_append_item(i2s_state_t *ch, uint32_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ICACHE_RAM_ATTR i2s_slc_isr(void) {
|
static void IRAM_ATTR i2s_slc_isr(void) {
|
||||||
ETS_SLC_INTR_DISABLE();
|
ETS_SLC_INTR_DISABLE();
|
||||||
uint32_t slc_intr_status = SLCIS;
|
uint32_t slc_intr_status = SLCIS;
|
||||||
SLCIC = 0xFFFFFFFF;
|
SLCIC = 0xFFFFFFFF;
|
||||||
@ -184,11 +194,11 @@ static void ICACHE_RAM_ATTR i2s_slc_isr(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void i2s_set_callback(void (*callback) (void)) {
|
void i2s_set_callback(void (*callback) (void)) {
|
||||||
tx->callback = callback;
|
if (tx) tx->callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void i2s_rx_set_callback(void (*callback) (void)) {
|
void i2s_rx_set_callback(void (*callback) (void)) {
|
||||||
rx->callback = callback;
|
if (rx) rx->callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _alloc_channel(i2s_state_t *ch) {
|
static bool _alloc_channel(i2s_state_t *ch) {
|
||||||
@ -333,7 +343,7 @@ bool i2s_write_lr(int16_t left, int16_t right){
|
|||||||
|
|
||||||
// writes a buffer of frames into the DMA memory, returns the amount of frames written
|
// writes a buffer of frames into the DMA memory, returns the amount of frames written
|
||||||
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
|
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
|
||||||
static uint16_t _i2s_write_buffer(int16_t *frames, uint16_t frame_count, bool mono, bool nb) {
|
static uint16_t _i2s_write_buffer(const int16_t *frames, uint16_t frame_count, bool mono, bool nb) {
|
||||||
uint16_t frames_written=0;
|
uint16_t frames_written=0;
|
||||||
|
|
||||||
while(frame_count>0) {
|
while(frame_count>0) {
|
||||||
@ -391,13 +401,13 @@ static uint16_t _i2s_write_buffer(int16_t *frames, uint16_t frame_count, bool mo
|
|||||||
return frames_written;
|
return frames_written;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t i2s_write_buffer_mono_nb(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, true); }
|
uint16_t i2s_write_buffer_mono_nb(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, true); }
|
||||||
|
|
||||||
uint16_t i2s_write_buffer_mono(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, false); }
|
uint16_t i2s_write_buffer_mono(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, false); }
|
||||||
|
|
||||||
uint16_t i2s_write_buffer_nb(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, true); }
|
uint16_t i2s_write_buffer_nb(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, true); }
|
||||||
|
|
||||||
uint16_t i2s_write_buffer(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, false); }
|
uint16_t i2s_write_buffer(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, false); }
|
||||||
|
|
||||||
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) {
|
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) {
|
||||||
if (!rx) {
|
if (!rx) {
|
||||||
@ -440,7 +450,7 @@ void i2s_set_rate(uint32_t rate) { //Rate in HZ
|
|||||||
}
|
}
|
||||||
_i2s_sample_rate = rate;
|
_i2s_sample_rate = rate;
|
||||||
|
|
||||||
uint32_t scaled_base_freq = I2SBASEFREQ/32;
|
uint32_t scaled_base_freq = I2SBASEFREQ / (_i2s_bits * 2);
|
||||||
float delta_best = scaled_base_freq;
|
float delta_best = scaled_base_freq;
|
||||||
|
|
||||||
uint8_t sbd_div_best=1;
|
uint8_t sbd_div_best=1;
|
||||||
@ -464,21 +474,42 @@ void i2s_set_dividers(uint8_t div1, uint8_t div2) {
|
|||||||
div1 &= I2SBDM;
|
div1 &= I2SBDM;
|
||||||
div2 &= I2SCDM;
|
div2 &= I2SCDM;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Following this post: https://github.com/esp8266/Arduino/issues/2590
|
||||||
|
We should reset the transmitter while changing the configuration bits to avoid random distortion.
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint32_t i2sc_temp = I2SC;
|
||||||
|
i2sc_temp |= (I2STXR); // Hold transmitter in reset
|
||||||
|
I2SC = i2sc_temp;
|
||||||
|
|
||||||
// trans master(active low), recv master(active_low), !bits mod(==16 bits/chanel), clear clock dividers
|
// trans master(active low), recv master(active_low), !bits mod(==16 bits/chanel), clear clock dividers
|
||||||
I2SC &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
|
i2sc_temp &= ~(I2STSM | I2SRSM | (I2SBMM << I2SBM) | (I2SBDM << I2SBD) | (I2SCDM << I2SCD));
|
||||||
|
|
||||||
// I2SRF = Send/recv right channel first (? may be swapped form I2S spec of WS=0 => left)
|
// I2SRF = Send/recv right channel first (? may be swapped form I2S spec of WS=0 => left)
|
||||||
// I2SMR = MSB recv/xmit first
|
// I2SMR = MSB recv/xmit first
|
||||||
// I2SRMS, I2STMS = 1-bit delay from WS to MSB (I2S format)
|
// I2SRMS, I2STMS = 1-bit delay from WS to MSB (I2S format)
|
||||||
// div1, div2 = Set I2S WS clock frequency. BCLK seems to be generated from 32x this
|
// div1, div2 = Set I2S WS clock frequency. BCLK seems to be generated from 32x this
|
||||||
I2SC |= I2SRF | I2SMR | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD);
|
i2sc_temp |= I2SRF | I2SMR | I2SRMS | I2STMS | (div1 << I2SBD) | (div2 << I2SCD);
|
||||||
|
|
||||||
|
// Adjust the shift count for 16/24b output
|
||||||
|
i2sc_temp |= (_i2s_bits == 24 ? 8 : 0) << I2SBM;
|
||||||
|
|
||||||
|
I2SC = i2sc_temp;
|
||||||
|
|
||||||
|
i2sc_temp &= ~(I2STXR); // Release reset
|
||||||
|
I2SC = i2sc_temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
float i2s_get_real_rate(){
|
float i2s_get_real_rate(){
|
||||||
return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM);
|
return (float)I2SBASEFREQ/(_i2s_bits * 2)/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
||||||
|
return i2s_rxtxdrive_begin(enableRx, enableTx, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool i2s_rxtxdrive_begin(bool enableRx, bool enableTx, bool driveRxClocks, bool driveTxClocks) {
|
||||||
if (tx || rx) {
|
if (tx || rx) {
|
||||||
i2s_end(); // Stop and free any ongoing stuff
|
i2s_end(); // Stop and free any ongoing stuff
|
||||||
}
|
}
|
||||||
@ -489,25 +520,30 @@ bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
|||||||
// Nothing to clean up yet
|
// Nothing to clean up yet
|
||||||
return false; // OOM Error!
|
return false; // OOM Error!
|
||||||
}
|
}
|
||||||
pinMode(I2SO_WS, FUNCTION_1);
|
tx->driveClocks = driveTxClocks;
|
||||||
pinMode(I2SO_DATA, FUNCTION_1);
|
pinMode(I2SO_DATA, FUNCTION_1);
|
||||||
|
if (driveTxClocks) {
|
||||||
|
pinMode(I2SO_WS, FUNCTION_1);
|
||||||
pinMode(I2SO_BCK, FUNCTION_1);
|
pinMode(I2SO_BCK, FUNCTION_1);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (enableRx) {
|
if (enableRx) {
|
||||||
rx = (i2s_state_t*)calloc(1, sizeof(*rx));
|
rx = (i2s_state_t*)calloc(1, sizeof(*rx));
|
||||||
if (!rx) {
|
if (!rx) {
|
||||||
i2s_end(); // Clean up any TX or pin changes
|
i2s_end(); // Clean up any TX or pin changes
|
||||||
return false; // OOM error!
|
return false; // OOM error!
|
||||||
}
|
}
|
||||||
|
rx->driveClocks = driveRxClocks;
|
||||||
|
pinMode(I2SI_DATA, INPUT);
|
||||||
|
if (driveRxClocks) {
|
||||||
pinMode(I2SI_WS, OUTPUT);
|
pinMode(I2SI_WS, OUTPUT);
|
||||||
pinMode(I2SI_BCK, OUTPUT);
|
pinMode(I2SI_BCK, OUTPUT);
|
||||||
pinMode(I2SI_DATA, INPUT);
|
|
||||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_I2SI_DATA);
|
|
||||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_I2SI_BCK);
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_I2SI_BCK);
|
||||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_I2SI_WS);
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_I2SI_WS);
|
||||||
}
|
}
|
||||||
|
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_I2SI_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
_i2s_sample_rate = 0;
|
|
||||||
if (!i2s_slc_begin()) {
|
if (!i2s_slc_begin()) {
|
||||||
// OOM in SLC memory allocations, tear it all down and abort!
|
// OOM in SLC memory allocations, tear it all down and abort!
|
||||||
i2s_end();
|
i2s_end();
|
||||||
@ -525,12 +561,21 @@ bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
|||||||
|
|
||||||
// I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data shifted in/out
|
// I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data shifted in/out
|
||||||
I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only)
|
I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only)
|
||||||
|
if (_i2s_bits == 24) {
|
||||||
|
I2SFC |= (2 << I2STXFM) | (2 << I2SRXFM);
|
||||||
|
}
|
||||||
I2SFC |= I2SDE; // Enable DMA
|
I2SFC |= I2SDE; // Enable DMA
|
||||||
|
|
||||||
// I2STXCMM, I2SRXCMM=0 => Dual channel mode
|
// I2STXCMM, I2SRXCMM=0 => Dual channel mode
|
||||||
I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); // Set RX/TX CHAN_MOD=0
|
I2SCC &= ~((I2STXCMM << I2STXCM) | (I2SRXCMM << I2SRXCM)); // Set RX/TX CHAN_MOD=0
|
||||||
|
|
||||||
i2s_set_rate(44100);
|
// Ensure a sane clock is set, but don't change any pre-existing ones.
|
||||||
|
// But we also need to make sure the other bits weren't reset by a previous
|
||||||
|
// reset. So, store the present one, clear the flag, then set the same
|
||||||
|
// value (writing all needed config bits in the process
|
||||||
|
uint32_t save_rate = _i2s_sample_rate;
|
||||||
|
_i2s_sample_rate = 0;
|
||||||
|
i2s_set_rate(save_rate ? save_rate : 44100);
|
||||||
|
|
||||||
if (rx) {
|
if (rx) {
|
||||||
// Need to prime the # of samples to receive in the engine
|
// Need to prime the # of samples to receive in the engine
|
||||||
@ -560,15 +605,19 @@ void i2s_end() {
|
|||||||
|
|
||||||
if (tx) {
|
if (tx) {
|
||||||
pinMode(I2SO_DATA, INPUT);
|
pinMode(I2SO_DATA, INPUT);
|
||||||
|
if (tx->driveClocks) {
|
||||||
pinMode(I2SO_BCK, INPUT);
|
pinMode(I2SO_BCK, INPUT);
|
||||||
pinMode(I2SO_WS, INPUT);
|
pinMode(I2SO_WS, INPUT);
|
||||||
|
}
|
||||||
free(tx);
|
free(tx);
|
||||||
tx = NULL;
|
tx = NULL;
|
||||||
}
|
}
|
||||||
if (rx) {
|
if (rx) {
|
||||||
pinMode(I2SI_DATA, INPUT);
|
pinMode(I2SI_DATA, INPUT);
|
||||||
|
if (rx->driveClocks) {
|
||||||
pinMode(I2SI_BCK, INPUT);
|
pinMode(I2SI_BCK, INPUT);
|
||||||
pinMode(I2SI_WS, INPUT);
|
pinMode(I2SI_WS, INPUT);
|
||||||
|
}
|
||||||
free(rx);
|
free(rx);
|
||||||
rx = NULL;
|
rx = NULL;
|
||||||
}
|
}
|
||||||
|
81
cores/esp8266/core_esp8266_i2s.h
Normal file
81
cores/esp8266/core_esp8266_i2s.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
i2s.h - Software I2S library for esp8266
|
||||||
|
|
||||||
|
Copyright (c) 2015 Hristo Gochkov. 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 I2S_h
|
||||||
|
#define I2S_h
|
||||||
|
|
||||||
|
#define I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS 1
|
||||||
|
|
||||||
|
/*
|
||||||
|
How does this work? Basically, to get sound, you need to:
|
||||||
|
- Connect an I2S codec to the I2S pins on the ESP.
|
||||||
|
- Start up a thread that's going to do the sound output
|
||||||
|
- Call i2s_set_bits() if you want to enable 24-bit mode
|
||||||
|
- Call i2s_begin()
|
||||||
|
- Call i2s_set_rate() with the sample rate you want.
|
||||||
|
- Generate sound and call i2s_write_sample() with 32-bit samples.
|
||||||
|
The 32bit samples basically are 2 16-bit signed values (the analog values for
|
||||||
|
the left and right channel) concatenated as (Rout<<16)+Lout
|
||||||
|
|
||||||
|
i2s_write_sample will block when you're sending data too quickly, so you can just
|
||||||
|
generate and push data as fast as you can and i2s_write_sample will regulate the
|
||||||
|
speed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool i2s_set_bits(int bits); // Set bits per sample, only 16 or 24 supported. Call before begin.
|
||||||
|
// Note that in 24 bit mode each sample must be left-aligned (i.e. 0x00000000 .. 0xffffff00) as the
|
||||||
|
// hardware shifts starting at bit 31, not bit 23.
|
||||||
|
|
||||||
|
void i2s_begin(); // Enable TX only, for compatibility
|
||||||
|
bool i2s_rxtx_begin(bool enableRx, bool enableTx); // Allow TX and/or RX, returns false on OOM error
|
||||||
|
bool i2s_rxtxdrive_begin(bool enableRx, bool enableTx, bool driveRxClocks, bool driveTxClocks);
|
||||||
|
void i2s_end();
|
||||||
|
void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000)
|
||||||
|
void i2s_set_dividers(uint8_t div1, uint8_t div2);//Direct control over output rate
|
||||||
|
float i2s_get_real_rate();//The actual Sample Rate on output
|
||||||
|
bool i2s_write_sample(uint32_t sample);//32bit sample with channels being upper and lower 16 bits (blocking when DMA is full)
|
||||||
|
bool i2s_write_sample_nb(uint32_t sample);//same as above but does not block when DMA is full and returns false instead
|
||||||
|
bool i2s_write_lr(int16_t left, int16_t right);//combines both channels and calls i2s_write_sample with the result
|
||||||
|
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking); // RX data returned in both 16-bit outputs.
|
||||||
|
bool i2s_is_full();//returns true if DMA is full and can not take more bytes (overflow)
|
||||||
|
bool i2s_is_empty();//returns true if DMA is empty (underflow)
|
||||||
|
bool i2s_rx_is_full();
|
||||||
|
bool i2s_rx_is_empty();
|
||||||
|
uint16_t i2s_available();// returns the number of samples than can be written before blocking
|
||||||
|
uint16_t i2s_rx_available();// returns the number of samples than can be written before blocking
|
||||||
|
void i2s_set_callback(void (*callback) (void));
|
||||||
|
void i2s_rx_set_callback(void (*callback) (void));
|
||||||
|
|
||||||
|
// writes a buffer of frames into the DMA memory, returns the amount of frames written
|
||||||
|
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
|
||||||
|
uint16_t i2s_write_buffer_mono(const int16_t *frames, uint16_t frame_count);
|
||||||
|
uint16_t i2s_write_buffer_mono_nb(const int16_t *frames, uint16_t frame_count);
|
||||||
|
uint16_t i2s_write_buffer(const int16_t *frames, uint16_t frame_count);
|
||||||
|
uint16_t i2s_write_buffer_nb(const int16_t *frames, uint16_t frame_count);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -34,10 +34,14 @@ extern "C" {
|
|||||||
}
|
}
|
||||||
#include <core_version.h>
|
#include <core_version.h>
|
||||||
#include "gdb_hooks.h"
|
#include "gdb_hooks.h"
|
||||||
|
#include "flash_quirks.h"
|
||||||
|
#include <umm_malloc/umm_malloc.h>
|
||||||
|
#include <core_esp8266_non32xfer.h>
|
||||||
|
#include "core_esp8266_vm.h"
|
||||||
|
|
||||||
|
|
||||||
#define LOOP_TASK_PRIORITY 1
|
#define LOOP_TASK_PRIORITY 1
|
||||||
#define LOOP_QUEUE_SIZE 1
|
#define LOOP_QUEUE_SIZE 1
|
||||||
#define OPTIMISTIC_YIELD_TIME_US 16000
|
|
||||||
|
|
||||||
extern "C" void call_user_start();
|
extern "C" void call_user_start();
|
||||||
extern void loop();
|
extern void loop();
|
||||||
@ -58,7 +62,14 @@ cont_t* g_pcont __attribute__((section(".noinit")));
|
|||||||
static os_event_t s_loop_queue[LOOP_QUEUE_SIZE];
|
static os_event_t s_loop_queue[LOOP_QUEUE_SIZE];
|
||||||
|
|
||||||
/* Used to implement optimistic_yield */
|
/* Used to implement optimistic_yield */
|
||||||
static uint32_t s_micros_at_task_start;
|
static uint32_t s_cycles_at_yield_start;
|
||||||
|
|
||||||
|
/* For ets_intr_lock_nest / ets_intr_unlock_nest
|
||||||
|
* Max nesting seen by SDK so far is 2.
|
||||||
|
*/
|
||||||
|
#define ETS_INTR_LOCK_NEST_MAX 7
|
||||||
|
static uint16_t ets_intr_lock_stack[ETS_INTR_LOCK_NEST_MAX];
|
||||||
|
static byte ets_intr_lock_stack_ptr=0;
|
||||||
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -75,29 +86,52 @@ void initVariant() __attribute__((weak));
|
|||||||
void initVariant() {
|
void initVariant() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void preloop_update_frequency() __attribute__((weak));
|
extern "C" void __preloop_update_frequency() {
|
||||||
void preloop_update_frequency() {
|
|
||||||
#if defined(F_CPU) && (F_CPU == 160000000L)
|
#if defined(F_CPU) && (F_CPU == 160000000L)
|
||||||
REG_SET_BIT(0x3ff00014, BIT(0));
|
|
||||||
ets_update_cpu_frequency(160);
|
ets_update_cpu_frequency(160);
|
||||||
|
CPU2X |= 1UL;
|
||||||
|
#elif defined(F_CPU)
|
||||||
|
ets_update_cpu_frequency(80);
|
||||||
|
CPU2X &= ~1UL;
|
||||||
|
#elif !defined(F_CPU)
|
||||||
|
if (system_get_cpu_freq() == 160) {
|
||||||
|
CPU2X |= 1UL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
CPU2X &= ~1UL;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" void preloop_update_frequency() __attribute__((weak, alias("__preloop_update_frequency")));
|
||||||
|
|
||||||
extern "C" void esp_yield() {
|
extern "C" bool can_yield() {
|
||||||
if (cont_can_yield(g_pcont)) {
|
return cont_can_yield(g_pcont);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void esp_yield_within_cont() __attribute__((always_inline));
|
||||||
|
static void esp_yield_within_cont() {
|
||||||
cont_yield(g_pcont);
|
cont_yield(g_pcont);
|
||||||
|
s_cycles_at_yield_start = ESP.getCycleCount();
|
||||||
|
run_scheduled_recurrent_functions();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void __esp_yield() {
|
||||||
|
if (can_yield()) {
|
||||||
|
esp_yield_within_cont();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void esp_schedule() {
|
extern "C" void esp_yield() __attribute__ ((weak, alias("__esp_yield")));
|
||||||
|
|
||||||
|
extern "C" IRAM_ATTR void esp_schedule() {
|
||||||
ets_post(LOOP_TASK_PRIORITY, 0, 0);
|
ets_post(LOOP_TASK_PRIORITY, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void __yield() {
|
extern "C" void __yield() {
|
||||||
if (cont_can_yield(g_pcont)) {
|
if (can_yield()) {
|
||||||
esp_schedule();
|
esp_schedule();
|
||||||
esp_yield();
|
esp_yield_within_cont();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
panic();
|
panic();
|
||||||
@ -107,13 +141,55 @@ extern "C" void __yield() {
|
|||||||
extern "C" void yield(void) __attribute__ ((weak, alias("__yield")));
|
extern "C" void yield(void) __attribute__ ((weak, alias("__yield")));
|
||||||
|
|
||||||
extern "C" void optimistic_yield(uint32_t interval_us) {
|
extern "C" void optimistic_yield(uint32_t interval_us) {
|
||||||
if (cont_can_yield(g_pcont) &&
|
const uint32_t intvl_cycles = interval_us *
|
||||||
(system_get_time() - s_micros_at_task_start) > interval_us)
|
#if defined(F_CPU)
|
||||||
|
clockCyclesPerMicrosecond();
|
||||||
|
#else
|
||||||
|
ESP.getCpuFreqMHz();
|
||||||
|
#endif
|
||||||
|
if ((ESP.getCycleCount() - s_cycles_at_yield_start) > intvl_cycles &&
|
||||||
|
can_yield())
|
||||||
{
|
{
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace ets_intr_(un)lock with nestable versions
|
||||||
|
extern "C" void IRAM_ATTR ets_intr_lock() {
|
||||||
|
if (ets_intr_lock_stack_ptr < ETS_INTR_LOCK_NEST_MAX)
|
||||||
|
ets_intr_lock_stack[ets_intr_lock_stack_ptr++] = xt_rsil(3);
|
||||||
|
else
|
||||||
|
xt_rsil(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void IRAM_ATTR ets_intr_unlock() {
|
||||||
|
if (ets_intr_lock_stack_ptr > 0)
|
||||||
|
xt_wsr_ps(ets_intr_lock_stack[--ets_intr_lock_stack_ptr]);
|
||||||
|
else
|
||||||
|
xt_rsil(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Save / Restore the PS state across the rom ets_post call as the rom code
|
||||||
|
// does not implement this correctly.
|
||||||
|
extern "C" bool ets_post_rom(uint8 prio, ETSSignal sig, ETSParam par);
|
||||||
|
|
||||||
|
extern "C" bool IRAM_ATTR ets_post(uint8 prio, ETSSignal sig, ETSParam par) {
|
||||||
|
uint32_t saved;
|
||||||
|
__asm__ __volatile__ ("rsr %0,ps":"=a" (saved));
|
||||||
|
bool rc=ets_post_rom(prio, sig, par);
|
||||||
|
xt_wsr_ps(saved);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void __loop_end (void)
|
||||||
|
{
|
||||||
|
run_scheduled_functions();
|
||||||
|
run_scheduled_recurrent_functions();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void loop_end (void) __attribute__ ((weak, alias("__loop_end")));
|
||||||
|
|
||||||
static void loop_wrapper() {
|
static void loop_wrapper() {
|
||||||
static bool setup_done = false;
|
static bool setup_done = false;
|
||||||
preloop_update_frequency();
|
preloop_update_frequency();
|
||||||
@ -122,14 +198,19 @@ static void loop_wrapper() {
|
|||||||
setup_done = true;
|
setup_done = true;
|
||||||
}
|
}
|
||||||
loop();
|
loop();
|
||||||
run_scheduled_functions();
|
loop_end();
|
||||||
|
if (serialEventRun) {
|
||||||
|
serialEventRun();
|
||||||
|
}
|
||||||
esp_schedule();
|
esp_schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loop_task(os_event_t *events) {
|
static void loop_task(os_event_t *events) {
|
||||||
(void) events;
|
(void) events;
|
||||||
s_micros_at_task_start = system_get_time();
|
s_cycles_at_yield_start = ESP.getCycleCount();
|
||||||
|
ESP.resetHeap();
|
||||||
cont_run(g_pcont, &loop_wrapper);
|
cont_run(g_pcont, &loop_wrapper);
|
||||||
|
ESP.setDramHeap();
|
||||||
if (cont_check(g_pcont) != 0) {
|
if (cont_check(g_pcont) != 0) {
|
||||||
panic();
|
panic();
|
||||||
}
|
}
|
||||||
@ -181,6 +262,7 @@ void init_done() {
|
|||||||
std::set_terminate(__unhandled_exception_cpp);
|
std::set_terminate(__unhandled_exception_cpp);
|
||||||
do_global_ctors();
|
do_global_ctors();
|
||||||
esp_schedule();
|
esp_schedule();
|
||||||
|
ESP.setDramHeap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This is the entry point of the application.
|
/* This is the entry point of the application.
|
||||||
@ -228,8 +310,8 @@ void init_done() {
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void) __attribute__((weak));
|
extern "C" void app_entry_redefinable(void) __attribute__((weak));
|
||||||
extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void)
|
extern "C" void app_entry_redefinable(void)
|
||||||
{
|
{
|
||||||
/* Allocate continuation context on this SYS stack,
|
/* Allocate continuation context on this SYS stack,
|
||||||
and save pointer to it. */
|
and save pointer to it. */
|
||||||
@ -239,11 +321,11 @@ extern "C" void ICACHE_RAM_ATTR app_entry_redefinable(void)
|
|||||||
/* Call the entry point of the SDK code. */
|
/* Call the entry point of the SDK code. */
|
||||||
call_user_start();
|
call_user_start();
|
||||||
}
|
}
|
||||||
|
static void app_entry_custom (void) __attribute__((weakref("app_entry_redefinable")));
|
||||||
|
|
||||||
static void ICACHE_RAM_ATTR app_entry_custom (void) __attribute__((weakref("app_entry_redefinable")));
|
extern "C" void app_entry (void)
|
||||||
|
|
||||||
extern "C" void ICACHE_RAM_ATTR app_entry (void)
|
|
||||||
{
|
{
|
||||||
|
umm_init();
|
||||||
return app_entry_custom();
|
return app_entry_custom();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,8 +345,21 @@ extern "C" void user_init(void) {
|
|||||||
|
|
||||||
initVariant();
|
initVariant();
|
||||||
|
|
||||||
|
experimental::initFlashQuirks(); // Chip specific flash init.
|
||||||
|
|
||||||
cont_init(g_pcont);
|
cont_init(g_pcont);
|
||||||
|
|
||||||
|
#if defined(UMM_HEAP_EXTERNAL)
|
||||||
|
install_vm_exception_handler();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(NON32XFER_HANDLER) || defined(MMU_IRAM_HEAP)
|
||||||
|
install_non32xfer_exception_handler();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(MMU_IRAM_HEAP)
|
||||||
|
umm_init_iram();
|
||||||
|
#endif
|
||||||
preinit(); // Prior to C++ Dynamic Init (not related to above init() ). Meant to be user redefinable.
|
preinit(); // Prior to C++ Dynamic Init (not related to above init() ). Meant to be user redefinable.
|
||||||
|
|
||||||
ets_task(loop_task,
|
ets_task(loop_task,
|
||||||
|
178
cores/esp8266/core_esp8266_non32xfer.cpp
Normal file
178
cores/esp8266/core_esp8266_non32xfer.cpp
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
/* 020819
|
||||||
|
Based on PR https://github.com/esp8266/Arduino/pull/6978
|
||||||
|
Enhanced to also handle store operations to iRAM and optional range
|
||||||
|
validation. Also improved failed path to generate crash report.
|
||||||
|
And, partially refactored.
|
||||||
|
|
||||||
|
Apologies if this is being pedantic, I was getting confused over these so
|
||||||
|
I tried to understand what makes them different.
|
||||||
|
|
||||||
|
EXCCAUSE_LOAD_STORE_ERROR 3 is a non-32-bit load or store to an address that
|
||||||
|
only supports a full 32-bit aligned transfer like IRAM or ICACHE. i.e., No
|
||||||
|
8-bit char or 16-bit short transfers allowed.
|
||||||
|
|
||||||
|
EXCCAUSE_UNALIGNED 9 is an exception cause when load or store is not on an
|
||||||
|
aligned boundary that matches the element's width.
|
||||||
|
eg. *(short *)0x3FFF8001 = 1; or *(long *)0x3FFF8002 = 1;
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This exception handler handles EXCCAUSE_LOAD_STORE_ERROR. It allows for a
|
||||||
|
* byte or short access to iRAM or PROGMEM to succeed without causing a crash.
|
||||||
|
* When reading, it is still preferred to use the xxx_P macros when possible
|
||||||
|
* since they are probably 30x faster than this exception handler method.
|
||||||
|
*
|
||||||
|
* Code taken directly from @pvvx's public domain code in
|
||||||
|
* https://github.com/pvvx/esp8266web/blob/master/app/sdklib/system/app_main.c
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#define VERIFY_C_ASM_EXCEPTION_FRAME_STRUCTURE
|
||||||
|
#include <esp8266_undocumented.h>
|
||||||
|
#include <core_esp8266_non32xfer.h>
|
||||||
|
#include <mmu_iram.h>
|
||||||
|
#include <Schedule.h>
|
||||||
|
#include <debug.h>
|
||||||
|
|
||||||
|
// All of these optimization were tried and now work
|
||||||
|
// These results were from irammem.ino using GCC 10.2
|
||||||
|
// DRAM reference uint16 9 AVG cycles/transfer
|
||||||
|
// #pragma GCC optimize("O0") // uint16, 289 AVG cycles/transfer, IRAM: +180
|
||||||
|
// #pragma GCC optimize("O1") // uint16, 241 AVG cycles/transfer, IRAM: +16
|
||||||
|
#pragma GCC optimize("O2") // uint16, 230 AVG cycles/transfer, IRAM: +4
|
||||||
|
// #pragma GCC optimize("O3") // uint16, 230 AVG cycles/transfer, IRAM: +4
|
||||||
|
// #pragma GCC optimize("Ofast") // uint16, 230 AVG cycles/transfer, IRAM: +4
|
||||||
|
// #pragma GCC optimize("Os") // uint16, 233 AVG cycles/transfer, IRAM: 27556 +0
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
#define LOAD_MASK 0x00f00fu
|
||||||
|
#define L8UI_MATCH 0x000002u
|
||||||
|
#define L16UI_MATCH 0x001002u
|
||||||
|
#define L16SI_MATCH 0x009002u
|
||||||
|
#define S8I_MATCH 0x004002u
|
||||||
|
#define S16I_MATCH 0x005002u
|
||||||
|
|
||||||
|
#define EXCCAUSE_LOAD_STORE_ERROR 3 /* Non 32-bit read/write error */
|
||||||
|
|
||||||
|
static fn_c_exception_handler_t old_c_handler = NULL;
|
||||||
|
|
||||||
|
static
|
||||||
|
IRAM_ATTR void non32xfer_exception_handler(struct __exception_frame *ef, int cause)
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
uint32_t insn, excvaddr;
|
||||||
|
|
||||||
|
/* Extract instruction and faulting data address */
|
||||||
|
__EXCEPTION_HANDLER_PREAMBLE(ef, excvaddr, insn);
|
||||||
|
|
||||||
|
uint32_t what = insn & LOAD_MASK;
|
||||||
|
uint32_t valmask = 0;
|
||||||
|
|
||||||
|
uint32_t is_read = 1;
|
||||||
|
if (L8UI_MATCH == what || S8I_MATCH == what) {
|
||||||
|
valmask = 0xffu;
|
||||||
|
if (S8I_MATCH == what) {
|
||||||
|
is_read = 0;
|
||||||
|
}
|
||||||
|
} else if (L16UI_MATCH == what || L16SI_MATCH == what || S16I_MATCH == what) {
|
||||||
|
valmask = 0xffffu;
|
||||||
|
if (S16I_MATCH == what) {
|
||||||
|
is_read = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue; /* fail */
|
||||||
|
}
|
||||||
|
|
||||||
|
int regno = (insn & 0x0000f0u) >> 4;
|
||||||
|
if (regno == 1) {
|
||||||
|
continue; /* we can't support storing into a1, just die */
|
||||||
|
} else if (regno != 0) {
|
||||||
|
--regno; /* account for skipped a1 in exception_frame */
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_ESP_MMU
|
||||||
|
/* debug option to validate address so we don't hide memory access bugs in APP */
|
||||||
|
if (mmu_is_iram((void *)excvaddr) || (is_read && mmu_is_icache((void *)excvaddr))) {
|
||||||
|
/* all is good */
|
||||||
|
} else {
|
||||||
|
continue; /* fail */
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
uint32_t *pWord = (uint32_t *)(excvaddr & ~0x3);
|
||||||
|
uint32_t pos = (excvaddr & 0x3) * 8;
|
||||||
|
uint32_t mem_val = *pWord;
|
||||||
|
|
||||||
|
if (is_read) {
|
||||||
|
/* shift and mask down to correct size */
|
||||||
|
mem_val >>= pos;
|
||||||
|
mem_val &= valmask;
|
||||||
|
|
||||||
|
/* Sign-extend for L16SI, if applicable */
|
||||||
|
if (what == L16SI_MATCH && (mem_val & 0x8000)) {
|
||||||
|
mem_val |= 0xffff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
ef->a_reg[regno] = mem_val; /* carry out the load */
|
||||||
|
|
||||||
|
} else { /* is write */
|
||||||
|
uint32_t val = ef->a_reg[regno]; /* get value to store from register */
|
||||||
|
val <<= pos;
|
||||||
|
valmask <<= pos;
|
||||||
|
val &= valmask;
|
||||||
|
|
||||||
|
/* mask out field, and merge */
|
||||||
|
mem_val &= (~valmask);
|
||||||
|
mem_val |= val;
|
||||||
|
*pWord = mem_val; /* carry out the store */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ef->epc += 3; /* resume at following instruction */
|
||||||
|
return;
|
||||||
|
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
/* Fail request, die */
|
||||||
|
/*
|
||||||
|
The old handler points to the SDK. Be alert for HWDT when Calling with
|
||||||
|
INTLEVEL != 0. I cannot create it any more. I thought I saw this as a
|
||||||
|
problem; however, my test case shows no problem ?? Maybe I was confused.
|
||||||
|
*/
|
||||||
|
if (old_c_handler) { // if (0 == (ef->ps & 0x0F)) {
|
||||||
|
DBG_MMU_PRINTF("\ncalling previous load/store handler(%p)\n", old_c_handler);
|
||||||
|
old_c_handler(ef, cause);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calling _xtos_unhandled_exception(ef, cause) in the Boot ROM, gets us a
|
||||||
|
hardware wdt.
|
||||||
|
|
||||||
|
Use panic instead as a fall back. It will produce a stack trace.
|
||||||
|
*/
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To operate reliably, this module requires the new
|
||||||
|
`_xtos_set_exception_handler` from `exc-sethandler.cpp` and
|
||||||
|
`_xtos_c_wrapper_handler` from `exc-c-wrapper-handler.S`. See comment block in
|
||||||
|
`exc-sethandler.cpp` for details on issues with interrupts being enabled by
|
||||||
|
"C" wrapper.
|
||||||
|
*/
|
||||||
|
void install_non32xfer_exception_handler(void) __attribute__((weak));
|
||||||
|
void install_non32xfer_exception_handler(void) {
|
||||||
|
if (NULL == old_c_handler) {
|
||||||
|
// Set the "C" exception handler the wrapper will call
|
||||||
|
old_c_handler =
|
||||||
|
_xtos_set_exception_handler(EXCCAUSE_LOAD_STORE_ERROR,
|
||||||
|
non32xfer_exception_handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
62
cores/esp8266/core_esp8266_non32xfer.h
Normal file
62
cores/esp8266/core_esp8266_non32xfer.h
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#ifndef __CORE_ESP8266_NON32XFER_H
|
||||||
|
#define __CORE_ESP8266_NON32XFER_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern void install_non32xfer_exception_handler();
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
In adapting the public domain version, a crash would come or go away with
|
||||||
|
the slightest unrelated changes elsewhere in the function. Observed that
|
||||||
|
register a15 was used for epc1, then clobbered by `rsr.` I now believe a
|
||||||
|
"&" on the output register would have resolved the problem.
|
||||||
|
|
||||||
|
However, I have refactored the Extended ASM to reduce and consolidate
|
||||||
|
register usage and corrected the issue.
|
||||||
|
|
||||||
|
The positioning of the Extended ASM block (as early as possible in the
|
||||||
|
compiled function) is in part controlled by the immediate need for
|
||||||
|
output variable `insn`. This placement aids in getting excvaddr read as
|
||||||
|
early as possible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
{
|
||||||
|
__asm__ __volatile__ ("rsr.excvaddr %0;" : "=r"(excvaddr):: "memory");
|
||||||
|
/*
|
||||||
|
"C" reference code for the ASM to document intent.
|
||||||
|
May also prove useful when issolating possible issues with Extended ASM,
|
||||||
|
optimizations, new compilers, etc.
|
||||||
|
*/
|
||||||
|
uint32_t epc = ef->epc;
|
||||||
|
uint32_t *pWord = (uint32_t *)(epc & ~3);
|
||||||
|
uint64_t big_word = ((uint64_t)pWord[1] << 32) | pWord[0];
|
||||||
|
uint32_t pos = (epc & 3) * 8;
|
||||||
|
insn = (uint32_t)(big_word >>= pos);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define __EXCEPTION_HANDLER_PREAMBLE(ef, excvaddr, insn) \
|
||||||
|
{ \
|
||||||
|
uint32_t tmp; \
|
||||||
|
__asm__ ( \
|
||||||
|
"rsr.excvaddr %[vaddr]\n\t" /* Read faulting address as early as possible */ \
|
||||||
|
"movi.n %[tmp], ~3\n\t" /* prepare a mask for the EPC */ \
|
||||||
|
"and %[tmp], %[tmp], %[epc]\n\t" /* apply mask for 32-bit aligned base */ \
|
||||||
|
"ssa8l %[epc]\n\t" /* set up shift register for src op */ \
|
||||||
|
"l32i %[insn], %[tmp], 0\n\t" /* load part 1 */ \
|
||||||
|
"l32i %[tmp], %[tmp], 4\n\t" /* load part 2 */ \
|
||||||
|
"src %[insn], %[tmp], %[insn]\n\t" /* right shift to get faulting instruction */ \
|
||||||
|
: [vaddr]"=&r"(excvaddr), [insn]"=&r"(insn), [tmp]"=&r"(tmp) \
|
||||||
|
: [epc]"r"(ef->epc) :); \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -27,6 +27,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <limits>
|
||||||
#include "stdlib_noniso.h"
|
#include "stdlib_noniso.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -77,11 +78,15 @@ char * dtostrf(double number, signed char width, unsigned char prec, char *s) {
|
|||||||
// Figure out how big our number really is
|
// Figure out how big our number really is
|
||||||
double tenpow = 1.0;
|
double tenpow = 1.0;
|
||||||
int digitcount = 1;
|
int digitcount = 1;
|
||||||
while (number >= 10.0 * tenpow) {
|
double nextpow;
|
||||||
tenpow *= 10.0;
|
while (number >= (nextpow = (10.0 * tenpow))) {
|
||||||
|
tenpow = nextpow;
|
||||||
digitcount++;
|
digitcount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// minimal compensation for possible lack of precision (#7087 addition)
|
||||||
|
number *= 1 + std::numeric_limits<decltype(number)>::epsilon();
|
||||||
|
|
||||||
number /= tenpow;
|
number /= tenpow;
|
||||||
fillme -= digitcount;
|
fillme -= digitcount;
|
||||||
|
|
||||||
@ -112,4 +117,36 @@ char * dtostrf(double number, signed char width, unsigned char prec, char *s) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
strrstr (static)
|
||||||
|
|
||||||
|
Backwards search for p_pcPattern in p_pcString
|
||||||
|
Based on: https://stackoverflow.com/a/1634398/2778898
|
||||||
|
|
||||||
|
*/
|
||||||
|
const char* strrstr(const char*__restrict p_pcString,
|
||||||
|
const char*__restrict p_pcPattern)
|
||||||
|
{
|
||||||
|
const char* pcResult = 0;
|
||||||
|
|
||||||
|
size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0);
|
||||||
|
size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0);
|
||||||
|
|
||||||
|
if ((stStringLength) &&
|
||||||
|
(stPatternLength) &&
|
||||||
|
(stPatternLength <= stStringLength))
|
||||||
|
{
|
||||||
|
// Pattern is shorter or has the same length than the string
|
||||||
|
for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s)
|
||||||
|
{
|
||||||
|
if (0 == strncmp(s, p_pcPattern, stPatternLength))
|
||||||
|
{
|
||||||
|
pcResult = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pcResult;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -300,10 +300,10 @@ static const uint8_t ICACHE_FLASH_ATTR phy_init_data[128] =
|
|||||||
static bool spoof_init_data = false;
|
static bool spoof_init_data = false;
|
||||||
|
|
||||||
extern int __real_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size);
|
extern int __real_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size);
|
||||||
extern int ICACHE_RAM_ATTR __wrap_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size);
|
extern int IRAM_ATTR __wrap_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size);
|
||||||
extern int __get_adc_mode();
|
extern int __get_adc_mode();
|
||||||
|
|
||||||
extern int ICACHE_RAM_ATTR __wrap_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size)
|
extern int IRAM_ATTR __wrap_spi_flash_read(uint32_t addr, uint32_t* dst, size_t size)
|
||||||
{
|
{
|
||||||
if (!spoof_init_data || size != 128) {
|
if (!spoof_init_data || size != 128) {
|
||||||
return __real_spi_flash_read(addr, dst, size);
|
return __real_spi_flash_read(addr, dst, size);
|
||||||
@ -354,6 +354,6 @@ void user_rf_pre_init()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR user_spi_flash_dio_to_qio_pre_init() {}
|
void IRAM_ATTR user_spi_flash_dio_to_qio_pre_init() {}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -32,11 +32,10 @@
|
|||||||
#include "pgmspace.h"
|
#include "pgmspace.h"
|
||||||
#include "gdb_hooks.h"
|
#include "gdb_hooks.h"
|
||||||
#include "StackThunk.h"
|
#include "StackThunk.h"
|
||||||
|
#include "coredecls.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
extern void __real_system_restart_local();
|
|
||||||
|
|
||||||
// These will be pointers to PROGMEM const strings
|
// These will be pointers to PROGMEM const strings
|
||||||
static const char* s_panic_file = 0;
|
static const char* s_panic_file = 0;
|
||||||
static int s_panic_line = 0;
|
static int s_panic_line = 0;
|
||||||
@ -46,15 +45,29 @@ static const char* s_panic_what = 0;
|
|||||||
static bool s_abort_called = false;
|
static bool s_abort_called = false;
|
||||||
static const char* s_unhandled_exception = NULL;
|
static const char* s_unhandled_exception = NULL;
|
||||||
|
|
||||||
|
static uint32_t s_stacksmash_addr = 0;
|
||||||
|
|
||||||
void abort() __attribute__((noreturn));
|
void abort() __attribute__((noreturn));
|
||||||
static void uart_write_char_d(char c);
|
static void uart_write_char_d(char c);
|
||||||
static void uart0_write_char_d(char c);
|
static void uart0_write_char_d(char c);
|
||||||
static void uart1_write_char_d(char c);
|
static void uart1_write_char_d(char c);
|
||||||
static void print_stack(uint32_t start, uint32_t end);
|
static void print_stack(uint32_t start, uint32_t end);
|
||||||
|
|
||||||
|
// using numbers different from "REASON_" in user_interface.h (=0..6)
|
||||||
|
enum rst_reason_sw
|
||||||
|
{
|
||||||
|
REASON_USER_STACK_SMASH = 253,
|
||||||
|
REASON_USER_SWEXCEPTION_RST = 254
|
||||||
|
};
|
||||||
|
static int s_user_reset_reason = REASON_DEFAULT_RST;
|
||||||
|
|
||||||
// From UMM, the last caller of a malloc/realloc/calloc which failed:
|
// From UMM, the last caller of a malloc/realloc/calloc which failed:
|
||||||
extern void *umm_last_fail_alloc_addr;
|
extern void *umm_last_fail_alloc_addr;
|
||||||
extern int umm_last_fail_alloc_size;
|
extern int umm_last_fail_alloc_size;
|
||||||
|
#if defined(DEBUG_ESP_OOM)
|
||||||
|
extern const char *umm_last_fail_alloc_file;
|
||||||
|
extern int umm_last_fail_alloc_line;
|
||||||
|
#endif
|
||||||
|
|
||||||
static void raise_exception() __attribute__((noreturn));
|
static void raise_exception() __attribute__((noreturn));
|
||||||
|
|
||||||
@ -78,35 +91,44 @@ static void ets_printf_P(const char *str, ...) {
|
|||||||
vsnprintf(destStr, sizeof(destStr), str, argPtr);
|
vsnprintf(destStr, sizeof(destStr), str, argPtr);
|
||||||
va_end(argPtr);
|
va_end(argPtr);
|
||||||
while (*c) {
|
while (*c) {
|
||||||
ets_putc(*(c++));
|
ets_uart_putc1(*(c++));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void cut_here() {
|
||||||
|
ets_putc('\n');
|
||||||
|
for (auto i = 0; i < 15; i++ ) {
|
||||||
|
ets_putc('-');
|
||||||
|
}
|
||||||
|
ets_printf_P(PSTR(" CUT HERE FOR EXCEPTION DECODER "));
|
||||||
|
for (auto i = 0; i < 15; i++ ) {
|
||||||
|
ets_putc('-');
|
||||||
|
}
|
||||||
|
ets_putc('\n');
|
||||||
|
}
|
||||||
|
|
||||||
void __wrap_system_restart_local() {
|
void __wrap_system_restart_local() {
|
||||||
register uint32_t sp asm("a1");
|
register uint32_t sp asm("a1");
|
||||||
uint32_t sp_dump = sp;
|
uint32_t sp_dump = sp;
|
||||||
|
|
||||||
if (gdb_present()) {
|
|
||||||
/* When GDBStub is present, exceptions are handled by GDBStub,
|
|
||||||
but Soft WDT will still call this function.
|
|
||||||
Trigger an exception to break into GDB.
|
|
||||||
TODO: check why gdb_do_break() or asm("break.n 0") do not
|
|
||||||
break into GDB here. */
|
|
||||||
raise_exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
struct rst_info rst_info;
|
struct rst_info rst_info;
|
||||||
memset(&rst_info, 0, sizeof(rst_info));
|
memset(&rst_info, 0, sizeof(rst_info));
|
||||||
|
if (s_user_reset_reason == REASON_DEFAULT_RST)
|
||||||
|
{
|
||||||
system_rtc_mem_read(0, &rst_info, sizeof(rst_info));
|
system_rtc_mem_read(0, &rst_info, sizeof(rst_info));
|
||||||
if (rst_info.reason != REASON_SOFT_WDT_RST &&
|
if (rst_info.reason != REASON_SOFT_WDT_RST &&
|
||||||
rst_info.reason != REASON_EXCEPTION_RST &&
|
rst_info.reason != REASON_EXCEPTION_RST &&
|
||||||
rst_info.reason != REASON_WDT_RST)
|
rst_info.reason != REASON_WDT_RST)
|
||||||
{
|
{
|
||||||
return;
|
rst_info.reason = REASON_DEFAULT_RST;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
rst_info.reason = s_user_reset_reason;
|
||||||
|
|
||||||
// TODO: ets_install_putc1 definition is wrong in ets_sys.h, need cast
|
ets_install_putc1(&uart_write_char_d);
|
||||||
ets_install_putc1((void *)&uart_write_char_d);
|
|
||||||
|
cut_here();
|
||||||
|
|
||||||
if (s_panic_line) {
|
if (s_panic_line) {
|
||||||
ets_printf_P(PSTR("\nPanic %S:%d %S"), s_panic_file, s_panic_line, s_panic_func);
|
ets_printf_P(PSTR("\nPanic %S:%d %S"), s_panic_file, s_panic_line, s_panic_func);
|
||||||
@ -122,12 +144,23 @@ void __wrap_system_restart_local() {
|
|||||||
ets_printf_P(PSTR("\nAbort called\n"));
|
ets_printf_P(PSTR("\nAbort called\n"));
|
||||||
}
|
}
|
||||||
else if (rst_info.reason == REASON_EXCEPTION_RST) {
|
else if (rst_info.reason == REASON_EXCEPTION_RST) {
|
||||||
|
// The GCC divide routine in ROM jumps to the address below and executes ILL (00 00 00) on div-by-zero
|
||||||
|
// In that case, print the exception as (6) which is IntegerDivZero
|
||||||
|
bool div_zero = (rst_info.exccause == 0) && (rst_info.epc1 == 0x4000dce5);
|
||||||
ets_printf_P(PSTR("\nException (%d):\nepc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n"),
|
ets_printf_P(PSTR("\nException (%d):\nepc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n"),
|
||||||
rst_info.exccause, rst_info.epc1, rst_info.epc2, rst_info.epc3, rst_info.excvaddr, rst_info.depc);
|
div_zero ? 6 : rst_info.exccause, rst_info.epc1, rst_info.epc2, rst_info.epc3, rst_info.excvaddr, rst_info.depc);
|
||||||
}
|
}
|
||||||
else if (rst_info.reason == REASON_SOFT_WDT_RST) {
|
else if (rst_info.reason == REASON_SOFT_WDT_RST) {
|
||||||
ets_printf_P(PSTR("\nSoft WDT reset\n"));
|
ets_printf_P(PSTR("\nSoft WDT reset\n"));
|
||||||
}
|
}
|
||||||
|
else if (rst_info.reason == REASON_USER_STACK_SMASH) {
|
||||||
|
ets_printf_P(PSTR("\nStack overflow detected.\n"));
|
||||||
|
ets_printf_P(PSTR("\nException (%d):\nepc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n"),
|
||||||
|
5 /* Alloca exception, closest thing to stack fault*/, s_stacksmash_addr, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ets_printf_P(PSTR("\nGeneric Reset\n"));
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t cont_stack_start = (uint32_t) &(g_pcont->stack);
|
uint32_t cont_stack_start = (uint32_t) &(g_pcont->stack);
|
||||||
uint32_t cont_stack_end = (uint32_t) g_pcont->stack_end;
|
uint32_t cont_stack_end = (uint32_t) g_pcont->stack_end;
|
||||||
@ -138,10 +171,10 @@ void __wrap_system_restart_local() {
|
|||||||
// (determined empirically, might break)
|
// (determined empirically, might break)
|
||||||
uint32_t offset = 0;
|
uint32_t offset = 0;
|
||||||
if (rst_info.reason == REASON_SOFT_WDT_RST) {
|
if (rst_info.reason == REASON_SOFT_WDT_RST) {
|
||||||
offset = 0x1b0;
|
offset = 0x1a0;
|
||||||
}
|
}
|
||||||
else if (rst_info.reason == REASON_EXCEPTION_RST) {
|
else if (rst_info.reason == REASON_EXCEPTION_RST) {
|
||||||
offset = 0x1a0;
|
offset = 0x190;
|
||||||
}
|
}
|
||||||
else if (rst_info.reason == REASON_WDT_RST) {
|
else if (rst_info.reason == REASON_WDT_RST) {
|
||||||
offset = 0x10;
|
offset = 0x10;
|
||||||
@ -176,7 +209,21 @@ void __wrap_system_restart_local() {
|
|||||||
|
|
||||||
// Use cap-X formatting to ensure the standard EspExceptionDecoder doesn't match the address
|
// Use cap-X formatting to ensure the standard EspExceptionDecoder doesn't match the address
|
||||||
if (umm_last_fail_alloc_addr) {
|
if (umm_last_fail_alloc_addr) {
|
||||||
|
#if defined(DEBUG_ESP_OOM)
|
||||||
|
ets_printf_P(PSTR("\nlast failed alloc call: %08X(%d)@%S:%d\n"),
|
||||||
|
(uint32_t)umm_last_fail_alloc_addr, umm_last_fail_alloc_size,
|
||||||
|
umm_last_fail_alloc_file, umm_last_fail_alloc_line);
|
||||||
|
#else
|
||||||
ets_printf_P(PSTR("\nlast failed alloc call: %08X(%d)\n"), (uint32_t)umm_last_fail_alloc_addr, umm_last_fail_alloc_size);
|
ets_printf_P(PSTR("\nlast failed alloc call: %08X(%d)\n"), (uint32_t)umm_last_fail_alloc_addr, umm_last_fail_alloc_size);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
cut_here();
|
||||||
|
|
||||||
|
if (s_unhandled_exception && umm_last_fail_alloc_addr) {
|
||||||
|
// now outside from the "cut-here" zone, print correctly the `new` caller address,
|
||||||
|
// idf-monitor.py will be able to decode this one and show exact location in sources
|
||||||
|
ets_printf_P(PSTR("\nlast failed alloc caller: 0x%08x\n"), (uint32_t)umm_last_fail_alloc_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
custom_crash_callback( &rst_info, sp_dump + offset, stack_end );
|
custom_crash_callback( &rst_info, sp_dump + offset, stack_end );
|
||||||
@ -222,7 +269,13 @@ static void uart1_write_char_d(char c) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void raise_exception() {
|
static void raise_exception() {
|
||||||
__asm__ __volatile__ ("syscall");
|
if (gdb_present())
|
||||||
|
__asm__ __volatile__ ("syscall"); // triggers GDB when enabled
|
||||||
|
|
||||||
|
s_user_reset_reason = REASON_USER_SWEXCEPTION_RST;
|
||||||
|
ets_printf_P(PSTR("\nUser exception (panic/abort/assert)"));
|
||||||
|
__wrap_system_restart_local();
|
||||||
|
|
||||||
while (1); // never reached, needed to satisfy "noreturn" attribute
|
while (1); // never reached, needed to satisfy "noreturn" attribute
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,4 +307,20 @@ void __panic_func(const char* file, int line, const char* func) {
|
|||||||
raise_exception();
|
raise_exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uintptr_t __stack_chk_guard = 0x08675309 ^ RANDOM_REG32;
|
||||||
|
void __stack_chk_fail(void) {
|
||||||
|
s_user_reset_reason = REASON_USER_STACK_SMASH;
|
||||||
|
ets_printf_P(PSTR("\nPANIC: Stack overrun"));
|
||||||
|
|
||||||
|
s_stacksmash_addr = (uint32_t)__builtin_return_address(0);
|
||||||
|
|
||||||
|
if (gdb_present())
|
||||||
|
__asm__ __volatile__ ("syscall"); // triggers GDB when enabled
|
||||||
|
|
||||||
|
__wrap_system_restart_local();
|
||||||
|
|
||||||
|
while (1); // never reached, needed to satisfy "noreturn" attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
File diff suppressed because it is too large
Load Diff
197
cores/esp8266/core_esp8266_spi_utils.cpp
Normal file
197
cores/esp8266/core_esp8266_spi_utils.cpp
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
core_esp8266_spi_utils.cpp
|
||||||
|
|
||||||
|
Copyright (c) 2019 Mike Nix. 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 <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// register names
|
||||||
|
#include "esp8266_peri.h"
|
||||||
|
|
||||||
|
// for flashchip
|
||||||
|
#include "spi_flash.h"
|
||||||
|
|
||||||
|
// for PRECACHE_*
|
||||||
|
#include "core_esp8266_features.h"
|
||||||
|
|
||||||
|
#include "spi_utils.h"
|
||||||
|
|
||||||
|
extern "C" uint32_t Wait_SPI_Idle(SpiFlashChip *fc);
|
||||||
|
|
||||||
|
namespace experimental {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* critical part of SPICommand.
|
||||||
|
* Kept in a separate function to aid with precaching
|
||||||
|
* PRECACHE_* saves having to make the function IRAM_ATTR.
|
||||||
|
*
|
||||||
|
* spiIfNum needs to be volatile to keep the optimiser from
|
||||||
|
* deciding it can be treated as a constant (due to this being a
|
||||||
|
* static function only called with spiIfNum set to 0)
|
||||||
|
*
|
||||||
|
* Note: if porting to ESP32 mosi/miso bits are set in 2 registers, not 1.
|
||||||
|
*/
|
||||||
|
static SpiOpResult PRECACHE_ATTR
|
||||||
|
_SPICommand(volatile uint32_t spiIfNum,
|
||||||
|
uint32_t spic,uint32_t spiu,uint32_t spiu1,uint32_t spiu2,
|
||||||
|
uint32_t *data,uint32_t writeWords,uint32_t readWords)
|
||||||
|
{
|
||||||
|
if (spiIfNum>1)
|
||||||
|
return SPI_RESULT_ERR;
|
||||||
|
|
||||||
|
// force SPI register access via base+offest.
|
||||||
|
// Prevents loading individual address constants from flash.
|
||||||
|
uint32_t *spibase = (uint32_t*)(spiIfNum ? &(SPI1CMD) : &(SPI0CMD));
|
||||||
|
#define SPIREG(reg) (*((volatile uint32_t *)(spibase+(&(reg) - &(SPI0CMD)))))
|
||||||
|
|
||||||
|
// preload any constants and functions we need into variables
|
||||||
|
// Everything defined here must be volatile or the optimizer can
|
||||||
|
// treat them as constants, resulting in the flash reads we're
|
||||||
|
// trying to avoid
|
||||||
|
uint32_t (* volatile Wait_SPI_Idlep)(SpiFlashChip *) = Wait_SPI_Idle;
|
||||||
|
volatile SpiFlashChip *fchip=flashchip;
|
||||||
|
volatile uint32_t spicmdusr=SPICMDUSR;
|
||||||
|
|
||||||
|
uint32_t saved_ps=0;
|
||||||
|
|
||||||
|
if (!spiIfNum) {
|
||||||
|
// Only need to disable interrupts and precache when using SPI0
|
||||||
|
saved_ps = xt_rsil(15);
|
||||||
|
PRECACHE_START();
|
||||||
|
Wait_SPI_Idlep((SpiFlashChip *)fchip);
|
||||||
|
}
|
||||||
|
|
||||||
|
// preserve essential controller state such as incoming/outgoing
|
||||||
|
// data lengths and IO mode.
|
||||||
|
uint32_t oldSPI0U = SPIREG(SPI0U);
|
||||||
|
uint32_t oldSPI0U2= SPIREG(SPI0U2);
|
||||||
|
uint32_t oldSPI0C = SPIREG(SPI0C);
|
||||||
|
|
||||||
|
//SPI0S &= ~(SPISE|SPISBE|SPISSE|SPISCD);
|
||||||
|
SPIREG(SPI0C) = spic;
|
||||||
|
SPIREG(SPI0U) = spiu;
|
||||||
|
SPIREG(SPI0U1)= spiu1;
|
||||||
|
SPIREG(SPI0U2)= spiu2;
|
||||||
|
|
||||||
|
if (writeWords>0) {
|
||||||
|
// copy the outgoing data to the SPI hardware
|
||||||
|
uint32_t *src=data;
|
||||||
|
volatile uint32_t *dst=&SPIREG(SPI0W0);
|
||||||
|
for (uint32_t i=0; i<writeWords; i++)
|
||||||
|
*dst++ = *src++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the transfer
|
||||||
|
SPIREG(SPI0CMD) = spicmdusr;
|
||||||
|
|
||||||
|
// wait for the command to complete (typically only 1-3 iterations)
|
||||||
|
uint32_t timeout = 1000;
|
||||||
|
while ((SPIREG(SPI0CMD) & spicmdusr) && timeout--);
|
||||||
|
|
||||||
|
if ((readWords>0) && (timeout>0)) {
|
||||||
|
// copy the response back to the buffer
|
||||||
|
uint32_t *dst=data;
|
||||||
|
volatile uint32_t *src=&SPIREG(SPI0W0);
|
||||||
|
for (uint32_t i=0; i<readWords; i++)
|
||||||
|
*dst++ = *src++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore saved registers
|
||||||
|
SPIREG(SPI0U) = oldSPI0U;
|
||||||
|
SPIREG(SPI0U2)= oldSPI0U2;
|
||||||
|
SPIREG(SPI0C) = oldSPI0C;
|
||||||
|
|
||||||
|
PRECACHE_END();
|
||||||
|
if (!spiIfNum) {
|
||||||
|
xt_wsr_ps(saved_ps);
|
||||||
|
}
|
||||||
|
return (timeout>0 ? SPI_RESULT_OK : SPI_RESULT_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* SPI0Command: send a custom SPI command.
|
||||||
|
* This part calculates register values and passes them to _SPI0Command().
|
||||||
|
* Parameters:
|
||||||
|
* cmd The command byte (first 8 bits) to send to the SPI device
|
||||||
|
* *data The buffer containing the outgoing data for the SPI bus.
|
||||||
|
* The data is expected to be mosi_bits long, and the buffer
|
||||||
|
* is overwritten by the incoming bus data, which will be
|
||||||
|
* miso_bits long.
|
||||||
|
* mosi_bits
|
||||||
|
* Number of bits to be sent after the command byte.
|
||||||
|
* miso_bits
|
||||||
|
* Number of bits to read from the SPI bus after the outgoing
|
||||||
|
* data has been sent.
|
||||||
|
*
|
||||||
|
* Note: This code has only been tested with SPI bus 0, but should work
|
||||||
|
* equally well with other busses. The ESP8266 has bus 0 and 1,
|
||||||
|
* newer chips may have more one day.
|
||||||
|
*/
|
||||||
|
SpiOpResult SPI0Command(uint8_t cmd, uint32_t *data, uint32_t mosi_bits, uint32_t miso_bits) {
|
||||||
|
if (mosi_bits>(64*8))
|
||||||
|
return SPI_RESULT_ERR;
|
||||||
|
if (miso_bits>(64*8))
|
||||||
|
return SPI_RESULT_ERR;
|
||||||
|
|
||||||
|
// Calculate the number of data words (aka registers) that need to be copied
|
||||||
|
// to/from the SPI controller.
|
||||||
|
uint32_t mosi_words=mosi_bits/32;
|
||||||
|
uint32_t miso_words=miso_bits/32;
|
||||||
|
if (mosi_bits % 32 != 0)
|
||||||
|
mosi_words++;
|
||||||
|
if (miso_bits % 32 != 0)
|
||||||
|
miso_words++;
|
||||||
|
|
||||||
|
// Select user defined command mode in the controller
|
||||||
|
uint32_t spiu=SPIUCOMMAND; //SPI_USR_COMMAND
|
||||||
|
|
||||||
|
// Set the command byte to send
|
||||||
|
uint32_t spiu2 = ((7 & SPIMCOMMAND)<<SPILCOMMAND) | cmd;
|
||||||
|
|
||||||
|
uint32_t spiu1 = 0;
|
||||||
|
if (mosi_bits>0) {
|
||||||
|
// set the number of outgoing data bits to send
|
||||||
|
spiu1 |= ((mosi_bits-1) & SPIMMOSI) << SPILMOSI;
|
||||||
|
spiu |= SPIUMOSI; // SPI_USR_MOSI
|
||||||
|
}
|
||||||
|
if (miso_bits>0) {
|
||||||
|
// set the number of incoming bits to read
|
||||||
|
spiu1 |= ((miso_bits-1) & SPIMMISO) << SPILMISO;
|
||||||
|
spiu |= SPIUMISO; // SPI_USR_MISO
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t spic = SPI0C;
|
||||||
|
// Select the most basic IO mode for maximum compatibility
|
||||||
|
// Some flash commands are only available in this mode.
|
||||||
|
spic &= ~(SPICQIO | SPICDIO | SPICQOUT | SPICDOUT | SPICAHB | SPICFASTRD);
|
||||||
|
spic |= (SPICRESANDRES | SPICSHARE | SPICWPR | SPIC2BSE);
|
||||||
|
|
||||||
|
SpiOpResult rc =_SPICommand(0,spic,spiu,spiu1,spiu2,data,mosi_words,miso_words);
|
||||||
|
|
||||||
|
if (rc==SPI_RESULT_OK) {
|
||||||
|
// clear any bits we did not read in the last word.
|
||||||
|
if (miso_bits % 32) {
|
||||||
|
data[miso_bits/32] &= ~(0xFFFFFFFF << (miso_bits % 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace experimental
|
@ -31,8 +31,9 @@ extern "C" {
|
|||||||
|
|
||||||
static volatile timercallback timer1_user_cb = NULL;
|
static volatile timercallback timer1_user_cb = NULL;
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_isr_handler(void *para){
|
void IRAM_ATTR timer1_isr_handler(void *para, void *frame) {
|
||||||
(void) para;
|
(void) para;
|
||||||
|
(void) frame;
|
||||||
if ((T1C & ((1 << TCAR) | (1 << TCIT))) == 0) TEIE &= ~TEIE1;//edge int disable
|
if ((T1C & ((1 << TCAR) | (1 << TCIT))) == 0) TEIE &= ~TEIE1;//edge int disable
|
||||||
T1I = 0;
|
T1I = 0;
|
||||||
if (timer1_user_cb) {
|
if (timer1_user_cb) {
|
||||||
@ -44,32 +45,32 @@ void ICACHE_RAM_ATTR timer1_isr_handler(void *para){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_isr_init(){
|
void IRAM_ATTR timer1_isr_init(){
|
||||||
ETS_FRC_TIMER1_INTR_ATTACH(timer1_isr_handler, NULL);
|
ETS_FRC_TIMER1_INTR_ATTACH(timer1_isr_handler, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void timer1_attachInterrupt(timercallback userFunc) {
|
void IRAM_ATTR timer1_attachInterrupt(timercallback userFunc) {
|
||||||
timer1_user_cb = userFunc;
|
timer1_user_cb = userFunc;
|
||||||
ETS_FRC1_INTR_ENABLE();
|
ETS_FRC1_INTR_ENABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_detachInterrupt() {
|
void IRAM_ATTR timer1_detachInterrupt() {
|
||||||
timer1_user_cb = 0;
|
timer1_user_cb = 0;
|
||||||
TEIE &= ~TEIE1;//edge int disable
|
TEIE &= ~TEIE1;//edge int disable
|
||||||
ETS_FRC1_INTR_DISABLE();
|
ETS_FRC1_INTR_DISABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
void timer1_enable(uint8_t divider, uint8_t int_type, uint8_t reload){
|
void IRAM_ATTR timer1_enable(uint8_t divider, uint8_t int_type, uint8_t reload){
|
||||||
T1C = (1 << TCTE) | ((divider & 3) << TCPD) | ((int_type & 1) << TCIT) | ((reload & 1) << TCAR);
|
T1C = (1 << TCTE) | ((divider & 3) << TCPD) | ((int_type & 1) << TCIT) | ((reload & 1) << TCAR);
|
||||||
T1I = 0;
|
T1I = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_write(uint32_t ticks){
|
void IRAM_ATTR timer1_write(uint32_t ticks){
|
||||||
T1L = ((ticks)& 0x7FFFFF);
|
T1L = ((ticks)& 0x7FFFFF);
|
||||||
if ((T1C & (1 << TCIT)) == 0) TEIE |= TEIE1;//edge int enable
|
if ((T1C & (1 << TCIT)) == 0) TEIE |= TEIE1;//edge int enable
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_disable(){
|
void IRAM_ATTR timer1_disable(){
|
||||||
T1C = 0;
|
T1C = 0;
|
||||||
T1I = 0;
|
T1I = 0;
|
||||||
}
|
}
|
||||||
@ -79,8 +80,9 @@ void ICACHE_RAM_ATTR timer1_disable(){
|
|||||||
|
|
||||||
static volatile timercallback timer0_user_cb = NULL;
|
static volatile timercallback timer0_user_cb = NULL;
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer0_isr_handler(void* para){
|
void IRAM_ATTR timer0_isr_handler(void *para, void *frame) {
|
||||||
(void) para;
|
(void) para;
|
||||||
|
(void) frame;
|
||||||
if (timer0_user_cb) {
|
if (timer0_user_cb) {
|
||||||
// to make ISR compatible to Arduino AVR model where interrupts are disabled
|
// to make ISR compatible to Arduino AVR model where interrupts are disabled
|
||||||
// we disable them before we call the client ISR
|
// we disable them before we call the client ISR
|
||||||
@ -90,16 +92,16 @@ void ICACHE_RAM_ATTR timer0_isr_handler(void* para){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void timer0_isr_init(){
|
void IRAM_ATTR timer0_isr_init(){
|
||||||
ETS_CCOMPARE0_INTR_ATTACH(timer0_isr_handler, NULL);
|
ETS_CCOMPARE0_INTR_ATTACH(timer0_isr_handler, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void timer0_attachInterrupt(timercallback userFunc) {
|
void IRAM_ATTR timer0_attachInterrupt(timercallback userFunc) {
|
||||||
timer0_user_cb = userFunc;
|
timer0_user_cb = userFunc;
|
||||||
ETS_CCOMPARE0_ENABLE();
|
ETS_CCOMPARE0_ENABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer0_detachInterrupt() {
|
void IRAM_ATTR timer0_detachInterrupt() {
|
||||||
timer0_user_cb = NULL;
|
timer0_user_cb = NULL;
|
||||||
ETS_CCOMPARE0_DISABLE();
|
ETS_CCOMPARE0_DISABLE();
|
||||||
}
|
}
|
||||||
|
399
cores/esp8266/core_esp8266_vm.cpp
Normal file
399
cores/esp8266/core_esp8266_vm.cpp
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
/*
|
||||||
|
core_esp8266_vm - Implements logic to enable external SRAM/PSRAM to be used
|
||||||
|
as if it were on-chip memory by code.
|
||||||
|
|
||||||
|
Copyright (c) 2020 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
|
||||||
|
|
||||||
|
|
||||||
|
The original exception handler idea was taken from @pvvx's public domain
|
||||||
|
misaligned-flash-read exception handler, available here:
|
||||||
|
https://github.com/pvvx/esp8266web/blob/master/app/sdklib/system/app_main.c
|
||||||
|
|
||||||
|
|
||||||
|
Theory of Operation:
|
||||||
|
|
||||||
|
The Xtensa core generates a hardware exception (unrelated to C++ exceptions)
|
||||||
|
when an address that's defined as invalid for load or store. The XTOS ROM
|
||||||
|
routines capture the machine state and call a standard C exception handler
|
||||||
|
routine (or the default one which resets the system).
|
||||||
|
|
||||||
|
We hook into this exception callback and decode the EXCVADDR (the address
|
||||||
|
being accessed) and use the exception PC to read out the faulting
|
||||||
|
instruction. We decode that instruction and simulate it's behavior
|
||||||
|
(i.e. either loading or storing some data to a register/external memory)
|
||||||
|
and then return to the calling application.
|
||||||
|
|
||||||
|
We use the hardware SPI interface to talk to an external SRAM/PSRAM, and
|
||||||
|
implement a simple cache to minimize the amount of times we actually need
|
||||||
|
to go out over the (slow) SPI bus. The SPI is set up in a DIO mode which
|
||||||
|
uses no more pins than normal SPI, but provides for ~2X faster transfers.
|
||||||
|
|
||||||
|
NOTE: This works fine for processor accesses, but cannot be used by any
|
||||||
|
of the peripherals' DMA. For that, we'd need a real MMU.
|
||||||
|
|
||||||
|
Hardware Configuration (make sure you have 3.3V compatible SRAMs):
|
||||||
|
* SPI interfaced byte-addressible SRAM/PSRAM: 24LC1024 or smaller
|
||||||
|
CS -> GPIO15
|
||||||
|
SCK -> GPIO14
|
||||||
|
MOSI -> GPIO13
|
||||||
|
MISO -> GPIO12
|
||||||
|
(note these are GPIO numbers, not the Arduion Dxx ones. Refer to your
|
||||||
|
ESP8266 board schematic for the mapping of GPIO to pin.)
|
||||||
|
* Higher density PSRAM (ESP-PSRAM64H/etc.) works as well, but may be too
|
||||||
|
large to effectively use with UMM. Only 256K is available vial malloc,
|
||||||
|
but addresses above 256K do work and can be used for fixed buffers.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef MMU_EXTERNAL_HEAP
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <esp8266_undocumented.h>
|
||||||
|
#include "esp8266_peri.h"
|
||||||
|
#include "core_esp8266_vm.h"
|
||||||
|
#include "core_esp8266_non32xfer.h"
|
||||||
|
#include "umm_malloc/umm_malloc.h"
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
#define SHORT_MASK 0x000008u
|
||||||
|
#define LOAD_MASK 0x00f00fu
|
||||||
|
#define L8UI_MATCH 0x000002u
|
||||||
|
#define L16UI_MATCH 0x001002u
|
||||||
|
#define L16SI_MATCH 0x009002u
|
||||||
|
#define L16_MASK 0x001000u
|
||||||
|
#define SIGNED_MASK 0x008000u
|
||||||
|
#define L32IN_MATCH 0x000008u
|
||||||
|
#define L32I_MATCH 0x002002u
|
||||||
|
#define L32R_MATCH 0x000001u
|
||||||
|
#define L32_MASK 0x002009u
|
||||||
|
|
||||||
|
#define STORE_MASK 0x00f00fu
|
||||||
|
#define S8I_MATCH 0x004002u
|
||||||
|
#define S16I_MATCH 0x005002u
|
||||||
|
#define S16_MASK 0x001000u
|
||||||
|
#define S32I_MATCH 0x006002u
|
||||||
|
#define S32IN_MATCH 0x000009u
|
||||||
|
#define S32_MASK 0x002001u
|
||||||
|
|
||||||
|
#define EXCCAUSE_LOAD_PROHIBITED 28 // Cache Attribute does not allow Load
|
||||||
|
#define EXCCAUSE_STORE_PROHIBITED 29 // Cache Attribute does not allow Store
|
||||||
|
#define EXCCAUSE_STORE_MASK 1 // Fast way of deciding if it's a ld or s that faulted
|
||||||
|
|
||||||
|
// MINI SPI implementation inlined to have max performance and minimum code
|
||||||
|
// bloat. Can't include a library (SPI) in the core, anyway.
|
||||||
|
|
||||||
|
// Place in a struct so hopefully compiler will generate smaller, base+offset
|
||||||
|
// based code to access it
|
||||||
|
typedef struct {
|
||||||
|
volatile uint32_t spi_cmd; // The SPI can change this behind our backs, so volatile!
|
||||||
|
uint32_t spi_addr;
|
||||||
|
uint32_t spi_ctrl;
|
||||||
|
uint32_t spi_ctrl1; // undocumented? Not shown in the reg map
|
||||||
|
uint32_t spi_rd_status;
|
||||||
|
uint32_t spi_ctrl2;
|
||||||
|
uint32_t spi_clock;
|
||||||
|
uint32_t spi_user;
|
||||||
|
uint32_t spi_user1;
|
||||||
|
uint32_t spi_user2;
|
||||||
|
uint32_t spi_wr_status;
|
||||||
|
uint32_t spi_pin;
|
||||||
|
uint32_t spi_slave;
|
||||||
|
uint32_t spi_slave1;
|
||||||
|
uint32_t spi_slave2;
|
||||||
|
uint32_t spi_slave3;
|
||||||
|
uint32_t spi_w[16]; // NOTE: You need a memory barrier before reading these after a read xaction
|
||||||
|
uint32_t spi_ext3;
|
||||||
|
} spi_regs;
|
||||||
|
|
||||||
|
// The standard HSPI bus pins are used
|
||||||
|
constexpr uint8_t cs = 15;
|
||||||
|
constexpr uint8_t miso = 12;
|
||||||
|
constexpr uint8_t mosi = 13;
|
||||||
|
constexpr uint8_t sck = 14;
|
||||||
|
|
||||||
|
#define DECLARE_SPI1 spi_regs *spi1 = (spi_regs*)&SPI1CMD
|
||||||
|
|
||||||
|
typedef enum { spi_5mhz = 0x001c1001, spi_10mhz = 0x000c1001, spi_20mhz = 0x00041001, spi_30mhz = 0x00002001, spi_40mhz = 0x00001001 } spi_clocking;
|
||||||
|
typedef enum { sio = 0, dio = 1 } iotype;
|
||||||
|
|
||||||
|
#if MMU_EXTERNAL_HEAP > 128
|
||||||
|
constexpr uint32_t spi_clkval = spi_40mhz;
|
||||||
|
constexpr iotype hspi_mode = sio;
|
||||||
|
#else
|
||||||
|
constexpr uint32_t spi_clkval = spi_20mhz;
|
||||||
|
constexpr iotype hspi_mode = dio;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr int read_delay = (hspi_mode == dio) ? 4-1 : 0;
|
||||||
|
|
||||||
|
constexpr int cache_ways = 4; // N-way, fully associative cache
|
||||||
|
constexpr int cache_words = 16; // Must be 16 words or smaller to fit in SPI buffer
|
||||||
|
|
||||||
|
static struct cache_line {
|
||||||
|
int32_t addr; // Address, lower bits masked off
|
||||||
|
int dirty; // Needs writeback
|
||||||
|
struct cache_line *next; // We'll keep linked list in MRU order
|
||||||
|
union {
|
||||||
|
uint32_t w[cache_words];
|
||||||
|
uint16_t s[cache_words * 2];
|
||||||
|
uint8_t b[cache_words * 4];
|
||||||
|
};
|
||||||
|
} __vm_cache_line[cache_ways];
|
||||||
|
static struct cache_line *__vm_cache; // Always points to MRU (hence the line being read/written)
|
||||||
|
|
||||||
|
constexpr int addrmask = ~(sizeof(__vm_cache[0].w)-1); // Helper to mask off bits present in cache entry
|
||||||
|
|
||||||
|
|
||||||
|
static void spi_init(spi_regs *spi1)
|
||||||
|
{
|
||||||
|
pinMode(sck, SPECIAL);
|
||||||
|
pinMode(miso, SPECIAL);
|
||||||
|
pinMode(mosi, SPECIAL);
|
||||||
|
pinMode(cs, SPECIAL);
|
||||||
|
spi1->spi_cmd = 0;
|
||||||
|
GPMUX &= ~(1 << 9);
|
||||||
|
spi1->spi_clock = spi_clkval;
|
||||||
|
spi1->spi_ctrl = 0 ; // MSB first + plain SPI mode
|
||||||
|
spi1->spi_ctrl1 = 0; // undocumented, clear for safety?
|
||||||
|
spi1->spi_ctrl2 = 0; // No add'l delays on signals
|
||||||
|
spi1->spi_user2 = 0; // No insn or insn_bits to set
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: GCC optimization -O2 and -O3 tried and returned *slower* code than the default
|
||||||
|
|
||||||
|
// The SPI hardware cannot make the "command" portion dual or quad, only the addr and data
|
||||||
|
// So using the command portion of the cycle will not work. Comcatenate the address
|
||||||
|
// and command into a single 32-bit chunk "address" which will be sent across both bits.
|
||||||
|
|
||||||
|
inline IRAM_ATTR void spi_writetransaction(spi_regs *spi1, int addr, int addr_bits, int dummy_bits, int data_bits, iotype dual)
|
||||||
|
{
|
||||||
|
// Ensure no writes are still ongoing
|
||||||
|
while (spi1->spi_cmd & SPIBUSY) { /* busywait */ }
|
||||||
|
|
||||||
|
spi1->spi_addr = addr;
|
||||||
|
spi1->spi_user = (addr_bits? SPIUADDR : 0) | (dummy_bits ? SPIUDUMMY : 0) | (data_bits ? SPIUMOSI : 0) | (dual ? SPIUFWDIO : 0);
|
||||||
|
spi1->spi_user1 = (addr_bits << 26) | (data_bits << 17) | dummy_bits;
|
||||||
|
// No need to set spi_user2, insn field never used
|
||||||
|
__asm ( "" ::: "memory" );
|
||||||
|
spi1->spi_cmd = SPIBUSY;
|
||||||
|
// The write may continue on in the background, letting core do useful work instead of waiting, unless we're in cacheless mode
|
||||||
|
if (cache_ways == 0) {
|
||||||
|
while (spi1->spi_cmd & SPIBUSY) { /* busywait */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline IRAM_ATTR uint32_t spi_readtransaction(spi_regs *spi1, int addr, int addr_bits, int dummy_bits, int data_bits, iotype dual)
|
||||||
|
{
|
||||||
|
// Ensure no writes are still ongoing
|
||||||
|
while (spi1->spi_cmd & SPIBUSY) { /* busywait */ }
|
||||||
|
|
||||||
|
spi1->spi_addr = addr;
|
||||||
|
spi1->spi_user = (addr_bits? SPIUADDR : 0) | (dummy_bits ? SPIUDUMMY : 0) | SPIUMISO | (dual ? SPIUFWDIO : 0);
|
||||||
|
spi1->spi_user1 = (addr_bits << 26) | (data_bits << 8) | dummy_bits;
|
||||||
|
// No need to set spi_user2, insn field never used
|
||||||
|
__asm ( "" ::: "memory" );
|
||||||
|
spi1->spi_cmd = SPIBUSY;
|
||||||
|
while (spi1->spi_cmd & SPIBUSY) { /* busywait */ }
|
||||||
|
__asm ( "" ::: "memory" );
|
||||||
|
return spi1->spi_w[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline IRAM_ATTR void cache_flushrefill(spi_regs *spi1, int addr)
|
||||||
|
{
|
||||||
|
addr &= addrmask;
|
||||||
|
struct cache_line *way = __vm_cache;
|
||||||
|
|
||||||
|
if (__vm_cache->addr == addr) return; // Fast case, it already is the MRU
|
||||||
|
struct cache_line *last = way;
|
||||||
|
way = way->next;
|
||||||
|
|
||||||
|
for (auto i = 1; i < cache_ways; i++) {
|
||||||
|
if (way->addr == addr) {
|
||||||
|
last->next = way->next;
|
||||||
|
way->next = __vm_cache;
|
||||||
|
__vm_cache = way;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
last = way;
|
||||||
|
way = way->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point we know the line is not in the cache and way points to the LRU.
|
||||||
|
|
||||||
|
// We allow reads to go before writes since the write can happen in the background.
|
||||||
|
// We need to keep the data to be written back since it will be overwritten with read data
|
||||||
|
uint32_t wb[cache_words];
|
||||||
|
if (last->dirty) {
|
||||||
|
memcpy(wb, last->w, sizeof(last->w));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update MRU info, list
|
||||||
|
last->next = __vm_cache;
|
||||||
|
__vm_cache = last;
|
||||||
|
|
||||||
|
// Do the actual read
|
||||||
|
spi_readtransaction(spi1, (0x03 << 24) | addr, 32-1, read_delay, sizeof(last->w) * 8 - 1, hspi_mode);
|
||||||
|
memcpy(last->w, spi1->spi_w, sizeof(last->w));
|
||||||
|
|
||||||
|
// We fire a background writeback now, if needed
|
||||||
|
if (last->dirty) {
|
||||||
|
memcpy(spi1->spi_w, wb, sizeof(wb));
|
||||||
|
spi_writetransaction(spi1, (0x02 << 24) | last->addr, 32-1, 0, sizeof(last->w) * 8 - 1, hspi_mode);
|
||||||
|
last->dirty = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the addr at this point since we no longer need the old one
|
||||||
|
last->addr = addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline IRAM_ATTR void spi_ramwrite(spi_regs *spi1, int addr, int data_bits, uint32_t val)
|
||||||
|
{
|
||||||
|
if (cache_ways == 0) {
|
||||||
|
spi1->spi_w[0] = val;
|
||||||
|
spi_writetransaction(spi1, (0x02<<24) | addr, 32-1, 0, data_bits, hspi_mode);
|
||||||
|
} else {
|
||||||
|
cache_flushrefill(spi1, addr);
|
||||||
|
__vm_cache->dirty = 1;
|
||||||
|
addr -= __vm_cache->addr;
|
||||||
|
switch (data_bits) {
|
||||||
|
case 31: __vm_cache->w[addr >> 2] = val; break;
|
||||||
|
case 7: __vm_cache->b[addr] = val; break;
|
||||||
|
default: __vm_cache->s[addr >> 1] = val; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline IRAM_ATTR uint32_t spi_ramread(spi_regs *spi1, int addr, int data_bits)
|
||||||
|
{
|
||||||
|
if (cache_ways == 0) {
|
||||||
|
spi1->spi_w[0] = 0;
|
||||||
|
return spi_readtransaction(spi1, (0x03 << 24) | addr, 32-1, read_delay, data_bits, hspi_mode);
|
||||||
|
} else {
|
||||||
|
cache_flushrefill(spi1, addr);
|
||||||
|
addr -= __vm_cache->addr;
|
||||||
|
switch (data_bits) {
|
||||||
|
case 31: return __vm_cache->w[addr >> 2];
|
||||||
|
case 7: return __vm_cache->b[addr];
|
||||||
|
default: return __vm_cache->s[addr >> 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void (*__old_handler)(struct __exception_frame *ef, int cause);
|
||||||
|
|
||||||
|
static IRAM_ATTR void loadstore_exception_handler(struct __exception_frame *ef, int cause)
|
||||||
|
{
|
||||||
|
uint32_t excvaddr;
|
||||||
|
uint32_t insn;
|
||||||
|
|
||||||
|
/* Extract instruction and faulting data address */
|
||||||
|
__EXCEPTION_HANDLER_PREAMBLE(ef, excvaddr, insn);
|
||||||
|
|
||||||
|
// Check that we're really accessing VM and not some other illegal range
|
||||||
|
if ((excvaddr >> 28) != 1) {
|
||||||
|
// Reinstall the old handler, and retry the instruction to keep us out of the stack dump
|
||||||
|
_xtos_set_exception_handler(EXCCAUSE_LOAD_PROHIBITED, __old_handler);
|
||||||
|
_xtos_set_exception_handler(EXCCAUSE_STORE_PROHIBITED, __old_handler);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DECLARE_SPI1;
|
||||||
|
ef->epc += (insn & SHORT_MASK) ? 2 : 3; // resume at following instruction
|
||||||
|
|
||||||
|
int regno = (insn & 0x0000f0u) >> 4;
|
||||||
|
if (regno != 0) --regno; // account for skipped a1 in exception_frame
|
||||||
|
|
||||||
|
if (cause & EXCCAUSE_STORE_MASK) {
|
||||||
|
uint32_t val = ef->a_reg[regno];
|
||||||
|
uint32_t what = insn & STORE_MASK;
|
||||||
|
if (what == S8I_MATCH) {
|
||||||
|
spi_ramwrite(spi1, excvaddr & 0x1ffff, 8-1, val);
|
||||||
|
} else if (what == S16I_MATCH) {
|
||||||
|
spi_ramwrite(spi1, excvaddr & 0x1ffff, 16-1, val);
|
||||||
|
} else {
|
||||||
|
spi_ramwrite(spi1, excvaddr & 0x1ffff, 32-1, val);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (insn & L32_MASK) {
|
||||||
|
ef->a_reg[regno] = spi_ramread(spi1, excvaddr & 0x1ffff, 32-1);
|
||||||
|
} else if (insn & L16_MASK) {
|
||||||
|
ef->a_reg[regno] = spi_ramread(spi1, excvaddr & 0x1ffff, 16-1);
|
||||||
|
if ((insn & SIGNED_MASK ) && (ef->a_reg[regno] & 0x8000))
|
||||||
|
ef->a_reg[regno] |= 0xffff0000;
|
||||||
|
} else {
|
||||||
|
ef->a_reg[regno] = spi_ramread(spi1, excvaddr & 0x1ffff, 8-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void install_vm_exception_handler()
|
||||||
|
{
|
||||||
|
__old_handler = _xtos_set_exception_handler(EXCCAUSE_LOAD_PROHIBITED, loadstore_exception_handler);
|
||||||
|
_xtos_set_exception_handler(EXCCAUSE_STORE_PROHIBITED, loadstore_exception_handler);
|
||||||
|
|
||||||
|
DECLARE_SPI1;
|
||||||
|
|
||||||
|
// Manually reset chip from DIO to SIO mode (HW SPI has issues with <8 bits/clocks total output)
|
||||||
|
digitalWrite(cs, HIGH);
|
||||||
|
digitalWrite(mosi, HIGH);
|
||||||
|
digitalWrite(miso, HIGH);
|
||||||
|
digitalWrite(sck, LOW);
|
||||||
|
pinMode(cs, OUTPUT);
|
||||||
|
pinMode(miso, OUTPUT);
|
||||||
|
pinMode(mosi, OUTPUT);
|
||||||
|
pinMode(sck, OUTPUT);
|
||||||
|
digitalWrite(cs, LOW);
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
digitalWrite(sck, HIGH);
|
||||||
|
digitalWrite(sck, LOW);
|
||||||
|
}
|
||||||
|
digitalWrite(cs, HIGH);
|
||||||
|
|
||||||
|
// Set up the SPI regs
|
||||||
|
spi_init(spi1);
|
||||||
|
|
||||||
|
// Enable streaming read/write mode
|
||||||
|
spi1->spi_w[0] = 0x40;
|
||||||
|
spi_writetransaction(spi1, 0x01<<24, 8-1, 0, 8-1, sio);
|
||||||
|
|
||||||
|
if (hspi_mode == dio) {
|
||||||
|
// Ramp up to DIO mode
|
||||||
|
spi_writetransaction(spi1, 0x3b<<24, 8-1, 0, 0, sio);
|
||||||
|
spi1->spi_ctrl |= SPICDIO | SPICFASTRD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring cache structures to baseline
|
||||||
|
if (cache_ways > 0) {
|
||||||
|
for (auto i = 0; i < cache_ways; i++) {
|
||||||
|
__vm_cache_line[i].addr = -1; // Invalid, bits set in lower region so will never match
|
||||||
|
__vm_cache_line[i].next = &__vm_cache_line[i+1];
|
||||||
|
}
|
||||||
|
__vm_cache = &__vm_cache_line[0];
|
||||||
|
__vm_cache_line[cache_ways - 1].next = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook into memory manager
|
||||||
|
umm_init_vm( (void *)0x10000000, MMU_EXTERNAL_HEAP * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -1,8 +1,11 @@
|
|||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
#include <sys/utmp.h>
|
|
||||||
|
extern void install_vm_exception_handler();
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1,309 +0,0 @@
|
|||||||
/*
|
|
||||||
esp8266_waveform - General purpose waveform generation and control,
|
|
||||||
supporting outputs on all pins in parallel.
|
|
||||||
|
|
||||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
|
||||||
|
|
||||||
The core idea is to have a programmable waveform generator with a unique
|
|
||||||
high and low period (defined in microseconds). TIMER1 is set to 1-shot
|
|
||||||
mode and is always loaded with the time until the next edge of any live
|
|
||||||
waveforms.
|
|
||||||
|
|
||||||
Up to one waveform generator per pin supported.
|
|
||||||
|
|
||||||
Each waveform generator is synchronized to the ESP cycle counter, not the
|
|
||||||
timer. This allows for removing interrupt jitter and delay as the counter
|
|
||||||
always increments once per 80MHz clock. Changes to a waveform are
|
|
||||||
contiguous and only take effect on the next waveform transition,
|
|
||||||
allowing for smooth transitions.
|
|
||||||
|
|
||||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
|
||||||
|
|
||||||
Everywhere in the code where "cycles" is used, it means ESP.getCycleTime()
|
|
||||||
cycles, not TIMER1 cycles (which may be 2 CPU clocks @ 160MHz).
|
|
||||||
|
|
||||||
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 "ets_sys.h"
|
|
||||||
#include "core_esp8266_waveform.h"
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
// Maximum delay between IRQs
|
|
||||||
#define MAXIRQUS (10000)
|
|
||||||
|
|
||||||
// Set/clear GPIO 0-15 by bitmask
|
|
||||||
#define SetGPIO(a) do { GPOS = a; } while (0)
|
|
||||||
#define ClearGPIO(a) do { GPOC = a; } while (0)
|
|
||||||
|
|
||||||
// Waveform generator can create tones, PWM, and servos
|
|
||||||
typedef struct {
|
|
||||||
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
|
|
||||||
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
|
|
||||||
uint32_t nextTimeHighCycles; // Copy over low->high to keep smooth waveform
|
|
||||||
uint32_t nextTimeLowCycles; // Copy over high->low to keep smooth waveform
|
|
||||||
} Waveform;
|
|
||||||
|
|
||||||
static Waveform waveform[17]; // State of all possible pins
|
|
||||||
static volatile uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
|
||||||
static volatile uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
|
||||||
|
|
||||||
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
|
|
||||||
static volatile uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
|
|
||||||
static volatile uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
|
|
||||||
|
|
||||||
static uint32_t (*timer1CB)() = NULL;
|
|
||||||
|
|
||||||
|
|
||||||
// Non-speed critical bits
|
|
||||||
#pragma GCC optimize ("Os")
|
|
||||||
|
|
||||||
static inline ICACHE_RAM_ATTR uint32_t GetCycleCount() {
|
|
||||||
uint32_t ccount;
|
|
||||||
__asm__ __volatile__("esync; rsr %0,ccount":"=a"(ccount));
|
|
||||||
return ccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interrupt on/off control
|
|
||||||
static ICACHE_RAM_ATTR void timer1Interrupt();
|
|
||||||
static bool timerRunning = false;
|
|
||||||
|
|
||||||
static void initTimer() {
|
|
||||||
timer1_disable();
|
|
||||||
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
|
||||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
|
||||||
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
|
||||||
timerRunning = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ICACHE_RAM_ATTR deinitTimer() {
|
|
||||||
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
|
||||||
timer1_disable();
|
|
||||||
timer1_isr_init();
|
|
||||||
timerRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a callback. Pass in NULL to stop it
|
|
||||||
void setTimer1Callback(uint32_t (*fn)()) {
|
|
||||||
timer1CB = fn;
|
|
||||||
if (!timerRunning && fn) {
|
|
||||||
initTimer();
|
|
||||||
timer1_write(microsecondsToClockCycles(1)); // Cause an interrupt post-haste
|
|
||||||
} else if (timerRunning && !fn && !waveformEnabled) {
|
|
||||||
deinitTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start up a waveform on a pin, or change the current one. Will change to the new
|
|
||||||
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
|
||||||
// first, then it will immediately begin.
|
|
||||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS) {
|
|
||||||
if ((pin > 16) || isFlashInterfacePin(pin)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Waveform *wave = &waveform[pin];
|
|
||||||
// Adjust to shave off some of the IRQ time, approximately
|
|
||||||
wave->nextTimeHighCycles = microsecondsToClockCycles(timeHighUS);
|
|
||||||
wave->nextTimeLowCycles = microsecondsToClockCycles(timeLowUS);
|
|
||||||
wave->expiryCycle = runTimeUS ? GetCycleCount() + microsecondsToClockCycles(runTimeUS) : 0;
|
|
||||||
if (runTimeUS && !wave->expiryCycle) {
|
|
||||||
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t mask = 1<<pin;
|
|
||||||
if (!(waveformEnabled & mask)) {
|
|
||||||
// Actually set the pin high or low in the IRQ service to guarantee times
|
|
||||||
wave->nextServiceCycle = GetCycleCount() + microsecondsToClockCycles(1);
|
|
||||||
waveformToEnable |= mask;
|
|
||||||
if (!timerRunning) {
|
|
||||||
initTimer();
|
|
||||||
timer1_write(microsecondsToClockCycles(10));
|
|
||||||
} else {
|
|
||||||
// Ensure timely service....
|
|
||||||
if (T1L > microsecondsToClockCycles(10)) {
|
|
||||||
timer1_write(microsecondsToClockCycles(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (waveformToEnable) {
|
|
||||||
delay(0); // Wait for waveform to update
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Speed critical bits
|
|
||||||
#pragma GCC optimize ("O2")
|
|
||||||
// Normally would not want two copies like this, but due to different
|
|
||||||
// optimization levels the inline attribute gets lost if we try the
|
|
||||||
// other version.
|
|
||||||
|
|
||||||
static inline ICACHE_RAM_ATTR uint32_t GetCycleCountIRQ() {
|
|
||||||
uint32_t ccount;
|
|
||||||
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
|
|
||||||
return ccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline ICACHE_RAM_ATTR uint32_t min_u32(uint32_t a, uint32_t b) {
|
|
||||||
if (a < b) {
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stops a waveform on a pin
|
|
||||||
int ICACHE_RAM_ATTR stopWaveform(uint8_t pin) {
|
|
||||||
// Can't possibly need to stop anything if there is no timer active
|
|
||||||
if (!timerRunning) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If user sends in a pin >16 but <32, this will always point to a 0 bit
|
|
||||||
// If they send >=32, then the shift will result in 0 and it will also return false
|
|
||||||
uint32_t mask = 1<<pin;
|
|
||||||
if (!(waveformEnabled & mask)) {
|
|
||||||
return false; // It's not running, nothing to do here
|
|
||||||
}
|
|
||||||
waveformToDisable |= mask;
|
|
||||||
// Ensure timely service....
|
|
||||||
if (T1L > microsecondsToClockCycles(10)) {
|
|
||||||
timer1_write(microsecondsToClockCycles(10));
|
|
||||||
}
|
|
||||||
while (waveformToDisable) {
|
|
||||||
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
|
||||||
}
|
|
||||||
if (!waveformEnabled && !timer1CB) {
|
|
||||||
deinitTimer();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The SDK and hardware take some time to actually get to our NMI code, so
|
|
||||||
// decrement the next IRQ's timer value by a bit so we can actually catch the
|
|
||||||
// real CPU cycle counter we want for the waveforms.
|
|
||||||
#if F_CPU == 80000000
|
|
||||||
#define DELTAIRQ (microsecondsToClockCycles(3))
|
|
||||||
#else
|
|
||||||
#define DELTAIRQ (microsecondsToClockCycles(2))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
|
||||||
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
|
|
||||||
// are generating. In the common case (1 PWM) these may be the same pin and
|
|
||||||
// we can avoid looking at the other pins.
|
|
||||||
static int startPin = 0;
|
|
||||||
static int endPin = 0;
|
|
||||||
|
|
||||||
uint32_t nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
|
||||||
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
|
|
||||||
|
|
||||||
if (waveformToEnable || waveformToDisable) {
|
|
||||||
// Handle enable/disable requests from main app.
|
|
||||||
waveformEnabled = (waveformEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off
|
|
||||||
waveformState &= ~waveformToEnable; // And clear the state of any just started
|
|
||||||
waveformToEnable = 0;
|
|
||||||
waveformToDisable = 0;
|
|
||||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
|
||||||
startPin = __builtin_ffs(waveformEnabled) - 1;
|
|
||||||
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
|
|
||||||
endPin = 32 - __builtin_clz(waveformEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool done = false;
|
|
||||||
if (waveformEnabled) {
|
|
||||||
do {
|
|
||||||
nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
|
||||||
for (int i = startPin; i <= endPin; i++) {
|
|
||||||
uint32_t mask = 1<<i;
|
|
||||||
|
|
||||||
// If it's not on, ignore!
|
|
||||||
if (!(waveformEnabled & mask)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Waveform *wave = &waveform[i];
|
|
||||||
uint32_t now = GetCycleCountIRQ();
|
|
||||||
|
|
||||||
// Disable any waveforms that are done
|
|
||||||
if (wave->expiryCycle) {
|
|
||||||
int32_t expiryToGo = wave->expiryCycle - now;
|
|
||||||
if (expiryToGo < 0) {
|
|
||||||
// Done, remove!
|
|
||||||
waveformEnabled &= ~mask;
|
|
||||||
if (i == 16) {
|
|
||||||
GP16O &= ~1;
|
|
||||||
} else {
|
|
||||||
ClearGPIO(mask);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for toggles
|
|
||||||
int32_t cyclesToGo = wave->nextServiceCycle - now;
|
|
||||||
if (cyclesToGo < 0) {
|
|
||||||
waveformState ^= mask;
|
|
||||||
if (waveformState & mask) {
|
|
||||||
if (i == 16) {
|
|
||||||
GP16O |= 1; // GPIO16 write slow as it's RMW
|
|
||||||
} else {
|
|
||||||
SetGPIO(mask);
|
|
||||||
}
|
|
||||||
wave->nextServiceCycle = now + wave->nextTimeHighCycles;
|
|
||||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles);
|
|
||||||
} else {
|
|
||||||
if (i == 16) {
|
|
||||||
GP16O &= ~1; // GPIO16 write slow as it's RMW
|
|
||||||
} else {
|
|
||||||
ClearGPIO(mask);
|
|
||||||
}
|
|
||||||
wave->nextServiceCycle = now + wave->nextTimeLowCycles;
|
|
||||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uint32_t deltaCycles = wave->nextServiceCycle - now;
|
|
||||||
nextEventCycles = min_u32(nextEventCycles, deltaCycles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
|
|
||||||
uint32_t now = GetCycleCountIRQ();
|
|
||||||
int32_t cycleDeltaNextEvent = timeoutCycle - (now + nextEventCycles);
|
|
||||||
int32_t cyclesLeftTimeout = timeoutCycle - now;
|
|
||||||
done = (cycleDeltaNextEvent < 0) || (cyclesLeftTimeout < 0);
|
|
||||||
} while (!done);
|
|
||||||
} // if (waveformEnabled)
|
|
||||||
|
|
||||||
if (timer1CB) {
|
|
||||||
nextEventCycles = min_u32(nextEventCycles, timer1CB());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextEventCycles < microsecondsToClockCycles(10)) {
|
|
||||||
nextEventCycles = microsecondsToClockCycles(10);
|
|
||||||
}
|
|
||||||
nextEventCycles -= DELTAIRQ;
|
|
||||||
|
|
||||||
// Do it here instead of global function to save time and because we know it's edge-IRQ
|
|
||||||
#if F_CPU == 160000000
|
|
||||||
T1L = nextEventCycles >> 1; // Already know we're in range by MAXIRQUS
|
|
||||||
#else
|
|
||||||
T1L = nextEventCycles; // Already know we're in range by MAXIRQUS
|
|
||||||
#endif
|
|
||||||
TEIE |= TEIE1; // Edge int enable
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
@ -2,16 +2,17 @@
|
|||||||
esp8266_waveform - General purpose waveform generation and control,
|
esp8266_waveform - General purpose waveform generation and control,
|
||||||
supporting outputs on all pins in parallel.
|
supporting outputs on all pins in parallel.
|
||||||
|
|
||||||
|
-- Default, PWM locked version --
|
||||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||||
|
|
||||||
The core idea is to have a programmable waveform generator with a unique
|
The core idea is to have a programmable waveform generator with a unique
|
||||||
high and low period (defined in microseconds). TIMER1 is set to 1-shot
|
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||||
mode and is always loaded with the time until the next edge of any live
|
set to 1-shot mode and is always loaded with the time until the next edge
|
||||||
waveforms.
|
of any live waveforms.
|
||||||
|
|
||||||
Up to one waveform generator per pin supported.
|
Up to one waveform generator per pin supported.
|
||||||
|
|
||||||
Each waveform generator is synchronized to the ESP cycle counter, not the
|
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||||
timer. This allows for removing interrupt jitter and delay as the counter
|
timer. This allows for removing interrupt jitter and delay as the counter
|
||||||
always increments once per 80MHz clock. Changes to a waveform are
|
always increments once per 80MHz clock. Changes to a waveform are
|
||||||
contiguous and only take effect on the next waveform transition,
|
contiguous and only take effect on the next waveform transition,
|
||||||
@ -19,8 +20,33 @@
|
|||||||
|
|
||||||
This replaces older tone(), analogWrite(), and the Servo classes.
|
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||||
|
|
||||||
Everywhere in the code where "cycles" is used, it means ESP.getCycleTime()
|
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
||||||
cycles, not TIMER1 cycles (which may be 2 CPU clocks @ 160MHz).
|
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
||||||
|
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||||
|
----------
|
||||||
|
|
||||||
|
-- Phase locked version --
|
||||||
|
Copyright (c) 2020 Dirk O. Kaar.
|
||||||
|
|
||||||
|
The core idea is to have a programmable waveform generator with a unique
|
||||||
|
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||||
|
set to 1-shot mode and is always loaded with the time until the next edge
|
||||||
|
of any live waveforms.
|
||||||
|
|
||||||
|
Up to one waveform generator per pin supported.
|
||||||
|
|
||||||
|
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||||
|
timer. This allows for removing interrupt jitter and delay as the counter
|
||||||
|
always increments once per 80MHz clock. Changes to a waveform are
|
||||||
|
contiguous and only take effect on the next waveform transition,
|
||||||
|
allowing for smooth transitions.
|
||||||
|
|
||||||
|
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||||
|
|
||||||
|
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
||||||
|
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
||||||
|
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||||
|
----------
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
This library is free software; you can redistribute it and/or
|
||||||
modify it under the terms of the GNU Lesser General Public
|
modify it under the terms of the GNU Lesser General Public
|
||||||
@ -46,24 +72,55 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Call this function in your setup() to cause the phase locked version of the generator to
|
||||||
|
// be linked in automatically. Otherwise, the default PWM locked version will be used.
|
||||||
|
void enablePhaseLockedWaveform(void);
|
||||||
|
|
||||||
|
|
||||||
// Start or change a waveform of the specified high and low times on specific pin.
|
// Start or change a waveform of the specified high and low times on specific pin.
|
||||||
// If runtimeUS > 0 then automatically stop it after that many usecs.
|
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
|
||||||
|
// full period.
|
||||||
|
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
|
||||||
|
// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that.
|
||||||
|
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
|
||||||
|
// under load, for applications where frequency or duty cycle must not change, leave false.
|
||||||
// Returns true or false on success or failure.
|
// Returns true or false on success or failure.
|
||||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS);
|
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS = 0,
|
||||||
|
// Following parameters are ignored unless in PhaseLocked mode
|
||||||
|
int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false);
|
||||||
|
|
||||||
|
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
|
||||||
|
// If runtimeCycles > 0 then automatically stop it after that many CPU clock cycles, relative to the next
|
||||||
|
// full period.
|
||||||
|
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
|
||||||
|
// the new waveform is started at phaseOffsetCcys phase offset, in CPU clock cycles, to that.
|
||||||
|
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
|
||||||
|
// under load, for applications where frequency or duty cycle must not change, leave false.
|
||||||
|
// Returns true or false on success or failure.
|
||||||
|
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, uint32_t runTimeCcys = 0,
|
||||||
|
// Following parameters are ignored unless in PhaseLocked mode
|
||||||
|
int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false);
|
||||||
|
|
||||||
// Stop a waveform, if any, on the specified pin.
|
// Stop a waveform, if any, on the specified pin.
|
||||||
// Returns true or false on success or failure.
|
// Returns true or false on success or failure.
|
||||||
int stopWaveform(uint8_t pin);
|
int stopWaveform(uint8_t pin);
|
||||||
|
|
||||||
// Add a callback function to be called on *EVERY* timer1 trigger. The
|
// Add a callback function to be called on *EVERY* timer1 trigger. The
|
||||||
// callback returns the number of microseconds until the next desired call.
|
// callback must return the number of CPU clock cycles until the next desired call.
|
||||||
// However, since it is called every timer1 interrupt, it may be called
|
// However, since it is called every timer1 interrupt, it may be called
|
||||||
// again before this period. It should therefore use the ESP Cycle Counter
|
// again before this period. It should therefore use the ESP Cycle Counter
|
||||||
// to determine whether or not to perform an operation.
|
// to determine whether or not to perform an operation.
|
||||||
// Pass in NULL to disable the callback and, if no other waveforms being
|
// Pass in NULL to disable the callback and, if no other waveforms being
|
||||||
// generated, stop the timer as well.
|
// generated, stop the timer as well.
|
||||||
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
|
// Make sure the CB function has the IRAM_ATTR decorator.
|
||||||
void setTimer1Callback(uint32_t (*fn)());
|
void setTimer1Callback(uint32_t (*fn)());
|
||||||
|
|
||||||
|
|
||||||
|
// Internal-only calls, not for applications
|
||||||
|
extern void _setPWMFreq(uint32_t freq);
|
||||||
|
extern bool _stopPWM(uint8_t pin);
|
||||||
|
extern bool _setPWM(int pin, uint32_t val, uint32_t range);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
444
cores/esp8266/core_esp8266_waveform_phase.cpp
Normal file
444
cores/esp8266/core_esp8266_waveform_phase.cpp
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
/*
|
||||||
|
esp8266_waveform - General purpose waveform generation and control,
|
||||||
|
supporting outputs on all pins in parallel.
|
||||||
|
|
||||||
|
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||||
|
Copyright (c) 2020 Dirk O. Kaar.
|
||||||
|
|
||||||
|
The core idea is to have a programmable waveform generator with a unique
|
||||||
|
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||||
|
set to 1-shot mode and is always loaded with the time until the next edge
|
||||||
|
of any live waveforms.
|
||||||
|
|
||||||
|
Up to one waveform generator per pin supported.
|
||||||
|
|
||||||
|
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||||
|
timer. This allows for removing interrupt jitter and delay as the counter
|
||||||
|
always increments once per 80MHz clock. Changes to a waveform are
|
||||||
|
contiguous and only take effect on the next waveform transition,
|
||||||
|
allowing for smooth transitions.
|
||||||
|
|
||||||
|
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||||
|
|
||||||
|
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
||||||
|
clock cycle time, or an interval measured in clock cycles, but not TIMER1
|
||||||
|
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||||
|
|
||||||
|
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 "core_esp8266_waveform.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "debug.h"
|
||||||
|
#include "ets_sys.h"
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" void enablePhaseLockedWaveform (void)
|
||||||
|
{
|
||||||
|
// Does nothing, added to app to enable linking these versions
|
||||||
|
// of the waveform functions instead of the default.
|
||||||
|
DEBUGV("Enabling phase locked waveform generator\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// No-op calls to override the PWM implementation
|
||||||
|
extern "C" void _setPWMFreq_weak(uint32_t freq) { (void) freq; }
|
||||||
|
extern "C" bool _stopPWM_weak(int pin) { (void) pin; return false; }
|
||||||
|
extern "C" bool _setPWM_weak(int pin, uint32_t val, uint32_t range) { (void) pin; (void) val; (void) range; return false; }
|
||||||
|
|
||||||
|
|
||||||
|
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
||||||
|
constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
|
||||||
|
// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
|
||||||
|
constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000);
|
||||||
|
// Maximum servicing time for any single IRQ
|
||||||
|
constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18);
|
||||||
|
// The latency between in-ISR rearming of the timer and the earliest firing
|
||||||
|
constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2);
|
||||||
|
// The SDK and hardware take some time to actually get to our NMI code
|
||||||
|
constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ?
|
||||||
|
microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2);
|
||||||
|
|
||||||
|
// for INFINITE, the NMI proceeds on the waveform without expiry deadline.
|
||||||
|
// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy.
|
||||||
|
// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES.
|
||||||
|
// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY.
|
||||||
|
enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3};
|
||||||
|
|
||||||
|
// Waveform generator can create tones, PWM, and servos
|
||||||
|
typedef struct {
|
||||||
|
uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count
|
||||||
|
uint32_t endDutyCcy; // ESP clock cycle when going from duty to off
|
||||||
|
int32_t dutyCcys; // Set next off cycle at low->high to maintain phase
|
||||||
|
int32_t adjDutyCcys; // Temporary correction for next period
|
||||||
|
int32_t periodCcys; // Set next phase cycle at low->high to maintain phase
|
||||||
|
uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count
|
||||||
|
WaveformMode mode;
|
||||||
|
int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin
|
||||||
|
bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings
|
||||||
|
} Waveform;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
Waveform pins[17]; // State of all possible pins
|
||||||
|
uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||||
|
uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||||
|
|
||||||
|
// Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine
|
||||||
|
int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform
|
||||||
|
int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation
|
||||||
|
|
||||||
|
uint32_t(*timer1CB)() = nullptr;
|
||||||
|
|
||||||
|
bool timer1Running = false;
|
||||||
|
|
||||||
|
uint32_t nextEventCcy;
|
||||||
|
} waveform;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interrupt on/off control
|
||||||
|
static IRAM_ATTR void timer1Interrupt();
|
||||||
|
|
||||||
|
// Non-speed critical bits
|
||||||
|
#pragma GCC optimize ("Os")
|
||||||
|
|
||||||
|
static void initTimer() {
|
||||||
|
timer1_disable();
|
||||||
|
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||||
|
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
||||||
|
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
||||||
|
waveform.timer1Running = true;
|
||||||
|
timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IRAM_ATTR deinitTimer() {
|
||||||
|
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||||
|
timer1_disable();
|
||||||
|
timer1_isr_init();
|
||||||
|
waveform.timer1Running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
// Set a callback. Pass in NULL to stop it
|
||||||
|
void setTimer1Callback_weak(uint32_t (*fn)()) {
|
||||||
|
waveform.timer1CB = fn;
|
||||||
|
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||||
|
if (!waveform.timer1Running && fn) {
|
||||||
|
initTimer();
|
||||||
|
} else if (waveform.timer1Running && !fn && !waveform.enabled) {
|
||||||
|
deinitTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start up a waveform on a pin, or change the current one. Will change to the new
|
||||||
|
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
||||||
|
// first, then it will immediately begin.
|
||||||
|
int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCcys,
|
||||||
|
uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) {
|
||||||
|
uint32_t periodCcys = highCcys + lowCcys;
|
||||||
|
if (periodCcys < MAXIRQTICKSCCYS) {
|
||||||
|
if (!highCcys) {
|
||||||
|
periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
|
||||||
|
}
|
||||||
|
else if (!lowCcys) {
|
||||||
|
highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sanity checks, including mixed signed/unsigned arithmetic safety
|
||||||
|
if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) ||
|
||||||
|
static_cast<int32_t>(periodCcys) <= 0 ||
|
||||||
|
static_cast<int32_t>(highCcys) < 0 || static_cast<int32_t>(lowCcys) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Waveform& wave = waveform.pins[pin];
|
||||||
|
wave.dutyCcys = highCcys;
|
||||||
|
wave.adjDutyCcys = 0;
|
||||||
|
wave.periodCcys = periodCcys;
|
||||||
|
wave.autoPwm = autoPwm;
|
||||||
|
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
const uint32_t pinBit = 1UL << pin;
|
||||||
|
if (!(waveform.enabled & pinBit)) {
|
||||||
|
// wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR
|
||||||
|
wave.nextPeriodCcy = phaseOffsetCcys;
|
||||||
|
wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count
|
||||||
|
wave.mode = WaveformMode::INIT;
|
||||||
|
wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase;
|
||||||
|
if (!wave.dutyCcys) {
|
||||||
|
// If initially at zero duty cycle, force GPIO off
|
||||||
|
if (pin == 16) {
|
||||||
|
GP16O = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GPOC = pinBit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
waveform.toSetBits = 1UL << pin;
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
if (!waveform.timer1Running) {
|
||||||
|
initTimer();
|
||||||
|
}
|
||||||
|
else if (T1V > IRQLATENCYCCYS) {
|
||||||
|
// Must not interfere if Timer is due shortly
|
||||||
|
timer1_write(IRQLATENCYCCYS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count
|
||||||
|
if (runTimeCcys) {
|
||||||
|
wave.mode = WaveformMode::UPDATEEXPIRY;
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
waveform.toSetBits = 1UL << pin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||||
|
while (waveform.toSetBits) {
|
||||||
|
delay(0); // Wait for waveform to update
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stops a waveform on a pin
|
||||||
|
IRAM_ATTR int stopWaveform_weak(uint8_t pin) {
|
||||||
|
// Can't possibly need to stop anything if there is no timer active
|
||||||
|
if (!waveform.timer1Running) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If user sends in a pin >16 but <32, this will always point to a 0 bit
|
||||||
|
// If they send >=32, then the shift will result in 0 and it will also return false
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
const uint32_t pinBit = 1UL << pin;
|
||||||
|
if (waveform.enabled & pinBit) {
|
||||||
|
waveform.toDisableBits = 1UL << pin;
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
// Must not interfere if Timer is due shortly
|
||||||
|
if (T1V > IRQLATENCYCCYS) {
|
||||||
|
timer1_write(IRQLATENCYCCYS);
|
||||||
|
}
|
||||||
|
while (waveform.toDisableBits) {
|
||||||
|
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!waveform.enabled && !waveform.timer1CB) {
|
||||||
|
deinitTimer();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// Speed critical bits
|
||||||
|
#pragma GCC optimize ("O2")
|
||||||
|
|
||||||
|
// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted.
|
||||||
|
// Using constexpr makes sure that the CPU clock frequency is compile-time fixed.
|
||||||
|
static inline IRAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) {
|
||||||
|
if (ISCPUFREQ160MHZ) {
|
||||||
|
return isCPU2X ? ccys : (ccys >> 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return isCPU2X ? (ccys << 1) : ccys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static IRAM_ATTR void timer1Interrupt() {
|
||||||
|
const uint32_t isrStartCcy = ESP.getCycleCount();
|
||||||
|
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
|
||||||
|
const bool isCPU2X = CPU2X & 1;
|
||||||
|
if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) {
|
||||||
|
// Handle enable/disable requests from main app.
|
||||||
|
waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off
|
||||||
|
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||||
|
waveform.toDisableBits = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waveform.toSetBits) {
|
||||||
|
const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1;
|
||||||
|
Waveform& wave = waveform.pins[toSetPin];
|
||||||
|
switch (wave.mode) {
|
||||||
|
case WaveformMode::INIT:
|
||||||
|
waveform.states &= ~waveform.toSetBits; // Clear the state of any just started
|
||||||
|
if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) {
|
||||||
|
wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wave.nextPeriodCcy = waveform.nextEventCcy;
|
||||||
|
}
|
||||||
|
if (!wave.expiryCcy) {
|
||||||
|
wave.mode = WaveformMode::INFINITE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// fall through
|
||||||
|
case WaveformMode::UPDATEEXPIRY:
|
||||||
|
// in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count
|
||||||
|
wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X);
|
||||||
|
wave.mode = WaveformMode::EXPIRES;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
waveform.toSetBits = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit the loop if the next event, if any, is sufficiently distant.
|
||||||
|
const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS;
|
||||||
|
uint32_t busyPins = waveform.enabled;
|
||||||
|
waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS;
|
||||||
|
|
||||||
|
uint32_t now = ESP.getCycleCount();
|
||||||
|
uint32_t isrNextEventCcy = now;
|
||||||
|
while (busyPins) {
|
||||||
|
if (static_cast<int32_t>(isrNextEventCcy - now) > IRQLATENCYCCYS) {
|
||||||
|
waveform.nextEventCcy = isrNextEventCcy;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
isrNextEventCcy = waveform.nextEventCcy;
|
||||||
|
uint32_t loopPins = busyPins;
|
||||||
|
while (loopPins) {
|
||||||
|
const int pin = __builtin_ffsl(loopPins) - 1;
|
||||||
|
const uint32_t pinBit = 1UL << pin;
|
||||||
|
loopPins ^= pinBit;
|
||||||
|
|
||||||
|
Waveform& wave = waveform.pins[pin];
|
||||||
|
|
||||||
|
if (clockDrift) {
|
||||||
|
wave.endDutyCcy += clockDrift;
|
||||||
|
wave.nextPeriodCcy += clockDrift;
|
||||||
|
wave.expiryCcy += clockDrift;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy;
|
||||||
|
if (WaveformMode::EXPIRES == wave.mode &&
|
||||||
|
static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) >= 0 &&
|
||||||
|
static_cast<int32_t>(now - wave.expiryCcy) >= 0) {
|
||||||
|
// Disable any waveforms that are done
|
||||||
|
waveform.enabled ^= pinBit;
|
||||||
|
busyPins ^= pinBit;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const int32_t overshootCcys = now - waveNextEventCcy;
|
||||||
|
if (overshootCcys >= 0) {
|
||||||
|
const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X);
|
||||||
|
if (waveform.states & pinBit) {
|
||||||
|
// active configuration and forward are 100% duty
|
||||||
|
if (wave.periodCcys == wave.dutyCcys) {
|
||||||
|
wave.nextPeriodCcy += periodCcys;
|
||||||
|
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (wave.autoPwm) {
|
||||||
|
wave.adjDutyCcys += overshootCcys;
|
||||||
|
}
|
||||||
|
waveform.states ^= pinBit;
|
||||||
|
if (16 == pin) {
|
||||||
|
GP16O = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GPOC = pinBit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
waveNextEventCcy = wave.nextPeriodCcy;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wave.nextPeriodCcy += periodCcys;
|
||||||
|
if (!wave.dutyCcys) {
|
||||||
|
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X);
|
||||||
|
if (dutyCcys <= wave.adjDutyCcys) {
|
||||||
|
dutyCcys >>= 1;
|
||||||
|
wave.adjDutyCcys -= dutyCcys;
|
||||||
|
}
|
||||||
|
else if (wave.adjDutyCcys) {
|
||||||
|
dutyCcys -= wave.adjDutyCcys;
|
||||||
|
wave.adjDutyCcys = 0;
|
||||||
|
}
|
||||||
|
wave.endDutyCcy = now + dutyCcys;
|
||||||
|
if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
|
||||||
|
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||||
|
}
|
||||||
|
waveform.states |= pinBit;
|
||||||
|
if (16 == pin) {
|
||||||
|
GP16O = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GPOS = pinBit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
waveNextEventCcy = wave.endDutyCcy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WaveformMode::EXPIRES == wave.mode && static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) > 0) {
|
||||||
|
waveNextEventCcy = wave.expiryCcy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static_cast<int32_t>(waveNextEventCcy - isrTimeoutCcy) >= 0) {
|
||||||
|
busyPins ^= pinBit;
|
||||||
|
if (static_cast<int32_t>(waveform.nextEventCcy - waveNextEventCcy) > 0) {
|
||||||
|
waveform.nextEventCcy = waveNextEventCcy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (static_cast<int32_t>(isrNextEventCcy - waveNextEventCcy) > 0) {
|
||||||
|
isrNextEventCcy = waveNextEventCcy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
now = ESP.getCycleCount();
|
||||||
|
}
|
||||||
|
clockDrift = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t callbackCcys = 0;
|
||||||
|
if (waveform.timer1CB) {
|
||||||
|
callbackCcys = scaleCcys(waveform.timer1CB(), isCPU2X);
|
||||||
|
}
|
||||||
|
now = ESP.getCycleCount();
|
||||||
|
int32_t nextEventCcys = waveform.nextEventCcy - now;
|
||||||
|
// Account for unknown duration of timer1CB().
|
||||||
|
if (waveform.timer1CB && nextEventCcys > callbackCcys) {
|
||||||
|
waveform.nextEventCcy = now + callbackCcys;
|
||||||
|
nextEventCcys = callbackCcys;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
||||||
|
int32_t deltaIrqCcys = DELTAIRQCCYS;
|
||||||
|
int32_t irqLatencyCcys = IRQLATENCYCCYS;
|
||||||
|
if (isCPU2X) {
|
||||||
|
nextEventCcys >>= 1;
|
||||||
|
deltaIrqCcys >>= 1;
|
||||||
|
irqLatencyCcys >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Firing timer too soon, the NMI occurs before ISR has returned.
|
||||||
|
if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) {
|
||||||
|
waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS;
|
||||||
|
nextEventCcys = irqLatencyCcys;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nextEventCcys -= deltaIrqCcys;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register access is fast and edge IRQ was configured before.
|
||||||
|
T1L = nextEventCcys;
|
||||||
|
}
|
666
cores/esp8266/core_esp8266_waveform_pwm.cpp
Normal file
666
cores/esp8266/core_esp8266_waveform_pwm.cpp
Normal file
@ -0,0 +1,666 @@
|
|||||||
|
/*
|
||||||
|
esp8266_waveform - General purpose waveform generation and control,
|
||||||
|
supporting outputs on all pins in parallel.
|
||||||
|
|
||||||
|
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||||
|
|
||||||
|
The core idea is to have a programmable waveform generator with a unique
|
||||||
|
high and low period (defined in microseconds or CPU clock cycles). TIMER1
|
||||||
|
is set to 1-shot mode and is always loaded with the time until the next
|
||||||
|
edge of any live waveforms.
|
||||||
|
|
||||||
|
Up to one waveform generator per pin supported.
|
||||||
|
|
||||||
|
Each waveform generator is synchronized to the ESP clock cycle counter, not
|
||||||
|
the timer. This allows for removing interrupt jitter and delay as the
|
||||||
|
counter always increments once per 80MHz clock. Changes to a waveform are
|
||||||
|
contiguous and only take effect on the next waveform transition,
|
||||||
|
allowing for smooth transitions.
|
||||||
|
|
||||||
|
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||||
|
|
||||||
|
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
||||||
|
clock cycle count, or an interval measured in CPU clock cycles, but not
|
||||||
|
TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||||
|
|
||||||
|
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 "ets_sys.h"
|
||||||
|
#include "core_esp8266_waveform.h"
|
||||||
|
#include "user_interface.h"
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
// Maximum delay between IRQs
|
||||||
|
#define MAXIRQUS (10000)
|
||||||
|
|
||||||
|
// Waveform generator can create tones, PWM, and servos
|
||||||
|
typedef struct {
|
||||||
|
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
|
||||||
|
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
|
||||||
|
uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles)
|
||||||
|
uint32_t timeLowCycles; //
|
||||||
|
uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal
|
||||||
|
uint32_t desiredLowCycles; //
|
||||||
|
uint32_t lastEdge; // Cycle when this generator last changed
|
||||||
|
} Waveform;
|
||||||
|
|
||||||
|
class WVFState {
|
||||||
|
public:
|
||||||
|
Waveform waveform[17]; // State of all possible pins
|
||||||
|
uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||||
|
uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||||
|
|
||||||
|
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
|
||||||
|
uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
|
||||||
|
uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
|
||||||
|
|
||||||
|
uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI
|
||||||
|
uint32_t waveformNewHigh = 0;
|
||||||
|
uint32_t waveformNewLow = 0;
|
||||||
|
|
||||||
|
uint32_t (*timer1CB)() = NULL;
|
||||||
|
|
||||||
|
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
|
||||||
|
// are generating. In the common case (1 PWM) these may be the same pin and
|
||||||
|
// we can avoid looking at the other pins.
|
||||||
|
uint16_t startPin = 0;
|
||||||
|
uint16_t endPin = 0;
|
||||||
|
};
|
||||||
|
static WVFState wvfState;
|
||||||
|
|
||||||
|
|
||||||
|
// Ensure everything is read/written to RAM
|
||||||
|
#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); }
|
||||||
|
|
||||||
|
// Non-speed critical bits
|
||||||
|
#pragma GCC optimize ("Os")
|
||||||
|
|
||||||
|
// Interrupt on/off control
|
||||||
|
static IRAM_ATTR void timer1Interrupt();
|
||||||
|
static bool timerRunning = false;
|
||||||
|
|
||||||
|
static __attribute__((noinline)) void initTimer() {
|
||||||
|
if (!timerRunning) {
|
||||||
|
timer1_disable();
|
||||||
|
ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||||
|
ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt);
|
||||||
|
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE);
|
||||||
|
timerRunning = true;
|
||||||
|
timer1_write(microsecondsToClockCycles(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static IRAM_ATTR void forceTimerInterrupt() {
|
||||||
|
if (T1L > microsecondsToClockCycles(10)) {
|
||||||
|
T1L = microsecondsToClockCycles(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PWM implementation using special purpose state machine
|
||||||
|
//
|
||||||
|
// Keep an ordered list of pins with the delta in cycles between each
|
||||||
|
// element, with a terminal entry making up the remainder of the PWM
|
||||||
|
// period. With this method sum(all deltas) == PWM period clock cycles.
|
||||||
|
//
|
||||||
|
// At t=0 set all pins high and set the timeout for the 1st edge.
|
||||||
|
// On interrupt, if we're at the last element reset to t=0 state
|
||||||
|
// Otherwise, clear that pin down and set delay for next element
|
||||||
|
// and so forth.
|
||||||
|
|
||||||
|
constexpr int maxPWMs = 8;
|
||||||
|
|
||||||
|
// PWM machine state
|
||||||
|
typedef struct PWMState {
|
||||||
|
uint32_t mask; // Bitmask of active pins
|
||||||
|
uint32_t cnt; // How many entries
|
||||||
|
uint32_t idx; // Where the state machine is along the list
|
||||||
|
uint8_t pin[maxPWMs + 1];
|
||||||
|
uint32_t delta[maxPWMs + 1];
|
||||||
|
uint32_t nextServiceCycle; // Clock cycle for next step
|
||||||
|
struct PWMState *pwmUpdate; // Set by main code, cleared by ISR
|
||||||
|
} PWMState;
|
||||||
|
|
||||||
|
static PWMState pwmState;
|
||||||
|
static uint32_t _pwmFreq = 1000;
|
||||||
|
static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / _pwmFreq;
|
||||||
|
|
||||||
|
|
||||||
|
// If there are no more scheduled activities, shut down Timer 1.
|
||||||
|
// Otherwise, do nothing.
|
||||||
|
static IRAM_ATTR void disableIdleTimer() {
|
||||||
|
if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) {
|
||||||
|
ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL);
|
||||||
|
timer1_disable();
|
||||||
|
timer1_isr_init();
|
||||||
|
timerRunning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify the NMI that a new PWM state is available through the mailbox.
|
||||||
|
// Wait for mailbox to be emptied (either busy or delay() as needed)
|
||||||
|
static IRAM_ATTR void _notifyPWM(PWMState *p, bool idle) {
|
||||||
|
p->pwmUpdate = nullptr;
|
||||||
|
pwmState.pwmUpdate = p;
|
||||||
|
MEMBARRIER();
|
||||||
|
forceTimerInterrupt();
|
||||||
|
while (pwmState.pwmUpdate) {
|
||||||
|
if (idle) {
|
||||||
|
delay(0);
|
||||||
|
}
|
||||||
|
MEMBARRIER();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range);
|
||||||
|
|
||||||
|
|
||||||
|
// Called when analogWriteFreq() changed to update the PWM total period
|
||||||
|
extern void _setPWMFreq_weak(uint32_t freq) __attribute__((weak));
|
||||||
|
void _setPWMFreq_weak(uint32_t freq) {
|
||||||
|
_pwmFreq = freq;
|
||||||
|
|
||||||
|
// Convert frequency into clock cycles
|
||||||
|
uint32_t cc = microsecondsToClockCycles(1000000UL) / freq;
|
||||||
|
|
||||||
|
// Simple static adjustment to bring period closer to requested due to overhead
|
||||||
|
// Empirically determined as a constant PWM delay and a function of the number of PWMs
|
||||||
|
#if F_CPU == 80000000
|
||||||
|
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 13) >> 4) + 110;
|
||||||
|
#else
|
||||||
|
cc -= ((microsecondsToClockCycles(pwmState.cnt) * 10) >> 4) + 75;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (cc == _pwmPeriod) {
|
||||||
|
return; // No change
|
||||||
|
}
|
||||||
|
|
||||||
|
_pwmPeriod = cc;
|
||||||
|
|
||||||
|
if (pwmState.cnt) {
|
||||||
|
PWMState p; // The working copy since we can't edit the one in use
|
||||||
|
p.mask = 0;
|
||||||
|
p.cnt = 0;
|
||||||
|
for (uint32_t i = 0; i < pwmState.cnt; i++) {
|
||||||
|
auto pin = pwmState.pin[i];
|
||||||
|
_addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles);
|
||||||
|
}
|
||||||
|
// Update and wait for mailbox to be emptied
|
||||||
|
initTimer();
|
||||||
|
_notifyPWM(&p, true);
|
||||||
|
disableIdleTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void _setPWMFreq_bound(uint32_t freq) __attribute__((weakref("_setPWMFreq_weak")));
|
||||||
|
void _setPWMFreq(uint32_t freq) {
|
||||||
|
_setPWMFreq_bound(freq);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Helper routine to remove an entry from the state machine
|
||||||
|
// and clean up any marked-off entries
|
||||||
|
static void _cleanAndRemovePWM(PWMState *p, int pin) {
|
||||||
|
uint32_t leftover = 0;
|
||||||
|
uint32_t in, out;
|
||||||
|
for (in = 0, out = 0; in < p->cnt; in++) {
|
||||||
|
if ((p->pin[in] != pin) && (p->mask & (1<<p->pin[in]))) {
|
||||||
|
p->pin[out] = p->pin[in];
|
||||||
|
p->delta[out] = p->delta[in] + leftover;
|
||||||
|
leftover = 0;
|
||||||
|
out++;
|
||||||
|
} else {
|
||||||
|
leftover += p->delta[in];
|
||||||
|
p->mask &= ~(1<<p->pin[in]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p->cnt = out;
|
||||||
|
// Final pin is never used: p->pin[out] = 0xff;
|
||||||
|
p->delta[out] = p->delta[in] + leftover;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%))
|
||||||
|
extern bool _stopPWM_weak(uint8_t pin) __attribute__((weak));
|
||||||
|
IRAM_ATTR bool _stopPWM_weak(uint8_t pin) {
|
||||||
|
if (!((1<<pin) & pwmState.mask)) {
|
||||||
|
return false; // Pin not actually active
|
||||||
|
}
|
||||||
|
|
||||||
|
PWMState p; // The working copy since we can't edit the one in use
|
||||||
|
p = pwmState;
|
||||||
|
|
||||||
|
// In _stopPWM we just clear the mask but keep everything else
|
||||||
|
// untouched to save IRAM. The main startPWM will handle cleanup.
|
||||||
|
p.mask &= ~(1<<pin);
|
||||||
|
if (!p.mask) {
|
||||||
|
// If all have been stopped, then turn PWM off completely
|
||||||
|
p.cnt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update and wait for mailbox to be emptied, no delay (could be in ISR)
|
||||||
|
_notifyPWM(&p, false);
|
||||||
|
// Possibly shut down the timer completely if we're done
|
||||||
|
disableIdleTimer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool _stopPWM_bound(uint8_t pin) __attribute__((weakref("_stopPWM_weak")));
|
||||||
|
bool _stopPWM(uint8_t pin) {
|
||||||
|
return _stopPWM_bound(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range) {
|
||||||
|
// Stash the val and range so we can re-evaluate the fraction
|
||||||
|
// should the user change PWM frequency. This allows us to
|
||||||
|
// give as great a precision as possible. We know by construction
|
||||||
|
// that the waveform for this pin will be inactive so we can borrow
|
||||||
|
// memory from that structure.
|
||||||
|
wvfState.waveform[pin].desiredHighCycles = val; // Numerator == high
|
||||||
|
wvfState.waveform[pin].desiredLowCycles = range; // Denominator == low
|
||||||
|
|
||||||
|
uint32_t cc = (_pwmPeriod * val) / range;
|
||||||
|
|
||||||
|
// Clip to sane values in the case we go from OK to not-OK when adjusting frequencies
|
||||||
|
if (cc == 0) {
|
||||||
|
cc = 1;
|
||||||
|
} else if (cc >= _pwmPeriod) {
|
||||||
|
cc = _pwmPeriod - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.cnt == 0) {
|
||||||
|
// Starting up from scratch, special case 1st element and PWM period
|
||||||
|
p.pin[0] = pin;
|
||||||
|
p.delta[0] = cc;
|
||||||
|
// Final pin is never used: p.pin[1] = 0xff;
|
||||||
|
p.delta[1] = _pwmPeriod - cc;
|
||||||
|
} else {
|
||||||
|
uint32_t ttl = 0;
|
||||||
|
uint32_t i;
|
||||||
|
// Skip along until we're at the spot to insert
|
||||||
|
for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) {
|
||||||
|
ttl += p.delta[i];
|
||||||
|
}
|
||||||
|
// Shift everything out by one to make space for new edge
|
||||||
|
for (int32_t j = p.cnt; j >= (int)i; j--) {
|
||||||
|
p.pin[j + 1] = p.pin[j];
|
||||||
|
p.delta[j + 1] = p.delta[j];
|
||||||
|
}
|
||||||
|
int off = cc - ttl; // The delta from the last edge to the one we're inserting
|
||||||
|
p.pin[i] = pin;
|
||||||
|
p.delta[i] = off; // Add the delta to this new pin
|
||||||
|
p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant
|
||||||
|
}
|
||||||
|
p.cnt++;
|
||||||
|
p.mask |= 1<<pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by analogWrite(1...99%) to set the PWM duty in clock cycles
|
||||||
|
extern bool _setPWM_weak(int pin, uint32_t val, uint32_t range) __attribute__((weak));
|
||||||
|
bool _setPWM_weak(int pin, uint32_t val, uint32_t range) {
|
||||||
|
stopWaveform(pin);
|
||||||
|
PWMState p; // Working copy
|
||||||
|
p = pwmState;
|
||||||
|
// Get rid of any entries for this pin
|
||||||
|
_cleanAndRemovePWM(&p, pin);
|
||||||
|
// And add it to the list, in order
|
||||||
|
if (p.cnt >= maxPWMs) {
|
||||||
|
return false; // No space left
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check for all-on/off
|
||||||
|
uint32_t cc = (_pwmPeriod * val) / range;
|
||||||
|
if ((cc == 0) || (cc >= _pwmPeriod)) {
|
||||||
|
digitalWrite(pin, cc ? HIGH : LOW);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_addPWMtoList(p, pin, val, range);
|
||||||
|
|
||||||
|
// Set mailbox and wait for ISR to copy it over
|
||||||
|
initTimer();
|
||||||
|
_notifyPWM(&p, true);
|
||||||
|
disableIdleTimer();
|
||||||
|
|
||||||
|
// Potentially recalculate the PWM period if we've added another pin
|
||||||
|
_setPWMFreq(_pwmFreq);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static bool _setPWM_bound(int pin, uint32_t val, uint32_t range) __attribute__((weakref("_setPWM_weak")));
|
||||||
|
bool _setPWM(int pin, uint32_t val, uint32_t range) {
|
||||||
|
return _setPWM_bound(pin, val, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start up a waveform on a pin, or change the current one. Will change to the new
|
||||||
|
// waveform smoothly on next low->high transition. For immediate change, stopWaveform()
|
||||||
|
// first, then it will immediately begin.
|
||||||
|
extern int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weak));
|
||||||
|
int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles,
|
||||||
|
int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
|
||||||
|
(void) alignPhase;
|
||||||
|
(void) phaseOffsetUS;
|
||||||
|
(void) autoPwm;
|
||||||
|
|
||||||
|
if ((pin > 16) || isFlashInterfacePin(pin)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Waveform *wave = &wvfState.waveform[pin];
|
||||||
|
wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0;
|
||||||
|
if (runTimeCycles && !wave->expiryCycle) {
|
||||||
|
wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it
|
||||||
|
}
|
||||||
|
|
||||||
|
_stopPWM(pin); // Make sure there's no PWM live here
|
||||||
|
|
||||||
|
uint32_t mask = 1<<pin;
|
||||||
|
MEMBARRIER();
|
||||||
|
if (wvfState.waveformEnabled & mask) {
|
||||||
|
// Make sure no waveform changes are waiting to be applied
|
||||||
|
while (wvfState.waveformToChange) {
|
||||||
|
delay(0); // Wait for waveform to update
|
||||||
|
// No mem barrier here, the call to a global function implies global state updated
|
||||||
|
}
|
||||||
|
wvfState.waveformNewHigh = timeHighCycles;
|
||||||
|
wvfState.waveformNewLow = timeLowCycles;
|
||||||
|
MEMBARRIER();
|
||||||
|
wvfState.waveformToChange = mask;
|
||||||
|
// The waveform will be updated some time in the future on the next period for the signal
|
||||||
|
} else { // if (!(wvfState.waveformEnabled & mask)) {
|
||||||
|
wave->timeHighCycles = timeHighCycles;
|
||||||
|
wave->desiredHighCycles = timeHighCycles;
|
||||||
|
wave->timeLowCycles = timeLowCycles;
|
||||||
|
wave->desiredLowCycles = timeLowCycles;
|
||||||
|
wave->lastEdge = 0;
|
||||||
|
wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1);
|
||||||
|
wvfState.waveformToEnable |= mask;
|
||||||
|
MEMBARRIER();
|
||||||
|
initTimer();
|
||||||
|
forceTimerInterrupt();
|
||||||
|
while (wvfState.waveformToEnable) {
|
||||||
|
delay(0); // Wait for waveform to update
|
||||||
|
// No mem barrier here, the call to a global function implies global state updated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static int startWaveformClockCycles_bound(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weakref("startWaveformClockCycles_weak")));
|
||||||
|
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
|
||||||
|
return startWaveformClockCycles_bound(pin, timeHighCycles, timeLowCycles, runTimeCycles, alignPhase, phaseOffsetUS, autoPwm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This version falls-thru to the proper startWaveformClockCycles call and is invariant across waveform generators
|
||||||
|
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS,
|
||||||
|
int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) {
|
||||||
|
return startWaveformClockCycles_bound(pin,
|
||||||
|
microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS),
|
||||||
|
microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a callback. Pass in NULL to stop it
|
||||||
|
extern void setTimer1Callback_weak(uint32_t (*fn)()) __attribute__((weak));
|
||||||
|
void setTimer1Callback_weak(uint32_t (*fn)()) {
|
||||||
|
wvfState.timer1CB = fn;
|
||||||
|
if (fn) {
|
||||||
|
initTimer();
|
||||||
|
forceTimerInterrupt();
|
||||||
|
}
|
||||||
|
disableIdleTimer();
|
||||||
|
}
|
||||||
|
static void setTimer1Callback_bound(uint32_t (*fn)()) __attribute__((weakref("setTimer1Callback_weak")));
|
||||||
|
void setTimer1Callback(uint32_t (*fn)()) {
|
||||||
|
setTimer1Callback_bound(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stops a waveform on a pin
|
||||||
|
extern int stopWaveform_weak(uint8_t pin) __attribute__((weak));
|
||||||
|
IRAM_ATTR int stopWaveform_weak(uint8_t pin) {
|
||||||
|
// Can't possibly need to stop anything if there is no timer active
|
||||||
|
if (!timerRunning) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// If user sends in a pin >16 but <32, this will always point to a 0 bit
|
||||||
|
// If they send >=32, then the shift will result in 0 and it will also return false
|
||||||
|
uint32_t mask = 1<<pin;
|
||||||
|
if (wvfState.waveformEnabled & mask) {
|
||||||
|
wvfState.waveformToDisable = mask;
|
||||||
|
// Cancel any pending updates for this waveform, too.
|
||||||
|
if (wvfState.waveformToChange & mask) {
|
||||||
|
wvfState.waveformToChange = 0;
|
||||||
|
}
|
||||||
|
forceTimerInterrupt();
|
||||||
|
while (wvfState.waveformToDisable) {
|
||||||
|
MEMBARRIER(); // If it wasn't written yet, it has to be by now
|
||||||
|
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disableIdleTimer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static int stopWaveform_bound(uint8_t pin) __attribute__((weakref("stopWaveform_weak")));
|
||||||
|
IRAM_ATTR int stopWaveform(uint8_t pin) {
|
||||||
|
return stopWaveform_bound(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Speed critical bits
|
||||||
|
#pragma GCC optimize ("O2")
|
||||||
|
|
||||||
|
// Normally would not want two copies like this, but due to different
|
||||||
|
// optimization levels the inline attribute gets lost if we try the
|
||||||
|
// other version.
|
||||||
|
static inline IRAM_ATTR uint32_t GetCycleCountIRQ() {
|
||||||
|
uint32_t ccount;
|
||||||
|
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
|
||||||
|
return ccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the earliest cycle as compared to right now
|
||||||
|
static inline IRAM_ATTR uint32_t earliest(uint32_t a, uint32_t b) {
|
||||||
|
uint32_t now = GetCycleCountIRQ();
|
||||||
|
int32_t da = a - now;
|
||||||
|
int32_t db = b - now;
|
||||||
|
return (da < db) ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The SDK and hardware take some time to actually get to our NMI code, so
|
||||||
|
// decrement the next IRQ's timer value by a bit so we can actually catch the
|
||||||
|
// real CPU cycle counter we want for the waveforms.
|
||||||
|
|
||||||
|
// The SDK also sometimes is running at a different speed the the Arduino core
|
||||||
|
// so the ESP cycle counter is actually running at a variable speed.
|
||||||
|
// adjust(x) takes care of adjusting a delta clock cycle amount accordingly.
|
||||||
|
#if F_CPU == 80000000
|
||||||
|
#define DELTAIRQ (microsecondsToClockCycles(9)/4)
|
||||||
|
#define adjust(x) ((x) << (turbo ? 1 : 0))
|
||||||
|
#else
|
||||||
|
#define DELTAIRQ (microsecondsToClockCycles(9)/8)
|
||||||
|
#define adjust(x) ((x) >> 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage
|
||||||
|
#define MINIRQTIME microsecondsToClockCycles(4)
|
||||||
|
|
||||||
|
static IRAM_ATTR void timer1Interrupt() {
|
||||||
|
// Flag if the core is at 160 MHz, for use by adjust()
|
||||||
|
bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false;
|
||||||
|
|
||||||
|
uint32_t nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
|
||||||
|
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
|
||||||
|
|
||||||
|
if (wvfState.waveformToEnable || wvfState.waveformToDisable) {
|
||||||
|
// Handle enable/disable requests from main app
|
||||||
|
wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off
|
||||||
|
wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started
|
||||||
|
wvfState.waveformToEnable = 0;
|
||||||
|
wvfState.waveformToDisable = 0;
|
||||||
|
// No mem barrier. Globals must be written to RAM on ISR exit.
|
||||||
|
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||||
|
wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1;
|
||||||
|
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
|
||||||
|
wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled);
|
||||||
|
} else if (!pwmState.cnt && pwmState.pwmUpdate) {
|
||||||
|
// Start up the PWM generator by copying from the mailbox
|
||||||
|
pwmState.cnt = 1;
|
||||||
|
pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0
|
||||||
|
pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop!
|
||||||
|
// No need for mem barrier here. Global must be written by IRQ exit
|
||||||
|
}
|
||||||
|
|
||||||
|
bool done = false;
|
||||||
|
if (wvfState.waveformEnabled || pwmState.cnt) {
|
||||||
|
do {
|
||||||
|
nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS);
|
||||||
|
|
||||||
|
// PWM state machine implementation
|
||||||
|
if (pwmState.cnt) {
|
||||||
|
int32_t cyclesToGo;
|
||||||
|
do {
|
||||||
|
cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ();
|
||||||
|
if (cyclesToGo < 0) {
|
||||||
|
if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new
|
||||||
|
if (pwmState.pwmUpdate) {
|
||||||
|
// Do the memory copy from temp to global and clear mailbox
|
||||||
|
pwmState = *(PWMState*)pwmState.pwmUpdate;
|
||||||
|
}
|
||||||
|
GPOS = pwmState.mask; // Set all active pins high
|
||||||
|
if (pwmState.mask & (1<<16)) {
|
||||||
|
GP16O = 1;
|
||||||
|
}
|
||||||
|
pwmState.idx = 0;
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
// Drop the pin at this edge
|
||||||
|
if (pwmState.mask & (1<<pwmState.pin[pwmState.idx])) {
|
||||||
|
GPOC = 1<<pwmState.pin[pwmState.idx];
|
||||||
|
if (pwmState.pin[pwmState.idx] == 16) {
|
||||||
|
GP16O = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pwmState.idx++;
|
||||||
|
// Any other pins at this same PWM value will have delta==0, drop them too.
|
||||||
|
} while (pwmState.delta[pwmState.idx] == 0);
|
||||||
|
}
|
||||||
|
// Preserve duty cycle over PWM period by using now+xxx instead of += delta
|
||||||
|
cyclesToGo = adjust(pwmState.delta[pwmState.idx]);
|
||||||
|
pwmState.nextServiceCycle = GetCycleCountIRQ() + cyclesToGo;
|
||||||
|
}
|
||||||
|
nextEventCycle = earliest(nextEventCycle, pwmState.nextServiceCycle);
|
||||||
|
} while (pwmState.cnt && (cyclesToGo < 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = wvfState.startPin; i <= wvfState.endPin; i++) {
|
||||||
|
uint32_t mask = 1<<i;
|
||||||
|
|
||||||
|
// If it's not on, ignore!
|
||||||
|
if (!(wvfState.waveformEnabled & mask)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Waveform *wave = &wvfState.waveform[i];
|
||||||
|
uint32_t now = GetCycleCountIRQ();
|
||||||
|
|
||||||
|
// Disable any waveforms that are done
|
||||||
|
if (wave->expiryCycle) {
|
||||||
|
int32_t expiryToGo = wave->expiryCycle - now;
|
||||||
|
if (expiryToGo < 0) {
|
||||||
|
// Done, remove!
|
||||||
|
if (i == 16) {
|
||||||
|
GP16O = 0;
|
||||||
|
}
|
||||||
|
GPOC = mask;
|
||||||
|
wvfState.waveformEnabled &= ~mask;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for toggles
|
||||||
|
int32_t cyclesToGo = wave->nextServiceCycle - now;
|
||||||
|
if (cyclesToGo < 0) {
|
||||||
|
uint32_t nextEdgeCycles;
|
||||||
|
uint32_t desired = 0;
|
||||||
|
uint32_t *timeToUpdate;
|
||||||
|
wvfState.waveformState ^= mask;
|
||||||
|
if (wvfState.waveformState & mask) {
|
||||||
|
if (i == 16) {
|
||||||
|
GP16O = 1;
|
||||||
|
}
|
||||||
|
GPOS = mask;
|
||||||
|
|
||||||
|
if (wvfState.waveformToChange & mask) {
|
||||||
|
// Copy over next full-cycle timings
|
||||||
|
wave->timeHighCycles = wvfState.waveformNewHigh;
|
||||||
|
wave->desiredHighCycles = wvfState.waveformNewHigh;
|
||||||
|
wave->timeLowCycles = wvfState.waveformNewLow;
|
||||||
|
wave->desiredLowCycles = wvfState.waveformNewLow;
|
||||||
|
wave->lastEdge = 0;
|
||||||
|
wvfState.waveformToChange = 0;
|
||||||
|
}
|
||||||
|
if (wave->lastEdge) {
|
||||||
|
desired = wave->desiredLowCycles;
|
||||||
|
timeToUpdate = &wave->timeLowCycles;
|
||||||
|
}
|
||||||
|
nextEdgeCycles = wave->timeHighCycles;
|
||||||
|
} else {
|
||||||
|
if (i == 16) {
|
||||||
|
GP16O = 0;
|
||||||
|
}
|
||||||
|
GPOC = mask;
|
||||||
|
desired = wave->desiredHighCycles;
|
||||||
|
timeToUpdate = &wave->timeHighCycles;
|
||||||
|
nextEdgeCycles = wave->timeLowCycles;
|
||||||
|
}
|
||||||
|
if (desired) {
|
||||||
|
desired = adjust(desired);
|
||||||
|
int32_t err = desired - (now - wave->lastEdge);
|
||||||
|
if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal
|
||||||
|
err /= 2;
|
||||||
|
*timeToUpdate += err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextEdgeCycles = adjust(nextEdgeCycles);
|
||||||
|
wave->nextServiceCycle = now + nextEdgeCycles;
|
||||||
|
wave->lastEdge = now;
|
||||||
|
}
|
||||||
|
nextEventCycle = earliest(nextEventCycle, wave->nextServiceCycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
|
||||||
|
uint32_t now = GetCycleCountIRQ();
|
||||||
|
int32_t cycleDeltaNextEvent = nextEventCycle - now;
|
||||||
|
int32_t cyclesLeftTimeout = timeoutCycle - now;
|
||||||
|
done = (cycleDeltaNextEvent > MINIRQTIME) || (cyclesLeftTimeout < 0);
|
||||||
|
} while (!done);
|
||||||
|
} // if (wvfState.waveformEnabled)
|
||||||
|
|
||||||
|
if (wvfState.timer1CB) {
|
||||||
|
nextEventCycle = earliest(nextEventCycle, GetCycleCountIRQ() + wvfState.timer1CB());
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t nextEventCycles = nextEventCycle - GetCycleCountIRQ();
|
||||||
|
|
||||||
|
if (nextEventCycles < MINIRQTIME) {
|
||||||
|
nextEventCycles = MINIRQTIME;
|
||||||
|
}
|
||||||
|
nextEventCycles -= DELTAIRQ;
|
||||||
|
|
||||||
|
// Do it here instead of global function to save time and because we know it's edge-IRQ
|
||||||
|
T1L = nextEventCycles >> (turbo ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
@ -43,7 +43,7 @@ void delay_end(void* arg) {
|
|||||||
esp_schedule();
|
esp_schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
void delay(unsigned long ms) {
|
void __delay(unsigned long ms) {
|
||||||
if(ms) {
|
if(ms) {
|
||||||
os_timer_setfn(&delay_timer, (os_timer_func_t*) &delay_end, 0);
|
os_timer_setfn(&delay_timer, (os_timer_func_t*) &delay_end, 0);
|
||||||
os_timer_arm(&delay_timer, ms, ONCE);
|
os_timer_arm(&delay_timer, ms, ONCE);
|
||||||
@ -56,6 +56,8 @@ void delay(unsigned long ms) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void delay(unsigned long ms) __attribute__ ((weak, alias("__delay")));
|
||||||
|
|
||||||
void micros_overflow_tick(void* arg) {
|
void micros_overflow_tick(void* arg) {
|
||||||
(void) arg;
|
(void) arg;
|
||||||
uint32_t m = system_get_time();
|
uint32_t m = system_get_time();
|
||||||
@ -147,7 +149,7 @@ void micros_overflow_tick(void* arg) {
|
|||||||
//
|
//
|
||||||
// Reference function: corrected millis(), 64-bit arithmetic,
|
// Reference function: corrected millis(), 64-bit arithmetic,
|
||||||
// truncated to 32-bits by return
|
// truncated to 32-bits by return
|
||||||
// unsigned long ICACHE_RAM_ATTR millis_corr_DEBUG( void )
|
// unsigned long IRAM_ATTR millis_corr_DEBUG( void )
|
||||||
// {
|
// {
|
||||||
// // Get usec system time, usec overflow conter
|
// // Get usec system time, usec overflow conter
|
||||||
// ......
|
// ......
|
||||||
@ -161,7 +163,7 @@ void micros_overflow_tick(void* arg) {
|
|||||||
#define MAGIC_1E3_wLO 0x4bc6a7f0 // LS part
|
#define MAGIC_1E3_wLO 0x4bc6a7f0 // LS part
|
||||||
#define MAGIC_1E3_wHI 0x00418937 // MS part, magic multiplier
|
#define MAGIC_1E3_wHI 0x00418937 // MS part, magic multiplier
|
||||||
|
|
||||||
unsigned long ICACHE_RAM_ATTR millis()
|
unsigned long IRAM_ATTR millis()
|
||||||
{
|
{
|
||||||
union {
|
union {
|
||||||
uint64_t q; // Accumulator, 64-bit, little endian
|
uint64_t q; // Accumulator, 64-bit, little endian
|
||||||
@ -192,18 +194,18 @@ unsigned long ICACHE_RAM_ATTR millis()
|
|||||||
|
|
||||||
} //millis
|
} //millis
|
||||||
|
|
||||||
unsigned long ICACHE_RAM_ATTR micros() {
|
unsigned long IRAM_ATTR micros() {
|
||||||
return system_get_time();
|
return system_get_time();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t ICACHE_RAM_ATTR micros64() {
|
uint64_t IRAM_ATTR micros64() {
|
||||||
uint32_t low32_us = system_get_time();
|
uint32_t low32_us = system_get_time();
|
||||||
uint32_t high32_us = micros_overflow_count + ((low32_us < micros_at_last_overflow_tick) ? 1 : 0);
|
uint32_t high32_us = micros_overflow_count + ((low32_us < micros_at_last_overflow_tick) ? 1 : 0);
|
||||||
uint64_t duration64_us = (uint64_t)high32_us << 32 | low32_us;
|
uint64_t duration64_us = (uint64_t)high32_us << 32 | low32_us;
|
||||||
return duration64_us;
|
return duration64_us;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR delayMicroseconds(unsigned int us) {
|
void IRAM_ATTR delayMicroseconds(unsigned int us) {
|
||||||
os_delay_us(us);
|
os_delay_us(us);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,4 +39,16 @@ extern int __analogRead(uint8_t pin)
|
|||||||
|
|
||||||
extern int analogRead(uint8_t pin) __attribute__ ((weak, alias("__analogRead")));
|
extern int analogRead(uint8_t pin) __attribute__ ((weak, alias("__analogRead")));
|
||||||
|
|
||||||
|
|
||||||
|
void __analogReference(uint8_t mode)
|
||||||
|
{
|
||||||
|
// Only DEFAULT is supported on the ESP8266
|
||||||
|
if (mode != DEFAULT) {
|
||||||
|
DEBUGV("analogReference called with illegal mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern void analogReference(uint8_t mode) __attribute__ ((weak, alias("__analogReference")));
|
||||||
|
|
||||||
};
|
};
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user