1
0
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:
david gauchard 2021-04-04 23:02:43 +02:00 committed by GitHub
commit 0593cbdc00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1040 changed files with 78839 additions and 101155 deletions

271
.github/workflows/pull-request.yml vendored Normal file
View 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

View 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

View 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
View File

@ -2,7 +2,9 @@
tools/dist/
tools/xtensa-lx106-elf/
tools/mkspiffs/
tools/mklittlefs/
tools/python/
tools/python3/
package/versions/
exclude.txt
tools/sdk/lib/liblwip_src.a
@ -23,3 +25,10 @@ boards.local.txt
*.gcda
*.o
*.a
#Ignore files built by Visual Studio/Visual Micro
[Dd]ebug*/
[Rr]elease*/
.vs/
__vm/
*.vcxproj*

9
.gitmodules vendored
View File

@ -7,6 +7,9 @@
[submodule "libraries/SoftwareSerial"]
path = libraries/SoftwareSerial
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"]
path = libraries/ESP8266SdFat
url = https://github.com/earlephilhower/ESP8266SdFat.git
@ -16,3 +19,9 @@
[submodule "tools/esptool"]
path = tools/esptool
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

View File

@ -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
View File

@ -3,20 +3,20 @@ Arduino core for ESP8266 WiFi chip
# 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/)
- [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
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
- Installing options:
- [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)
- [Building with make](#building-with-make)
- [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).
- 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).
- Start Arduino and open 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.
- 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 the Preferences window.
- 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).
#### Latest release [![Latest release](https://img.shields.io/github/release/esp8266/Arduino.svg)](https://github.com/esp8266/Arduino/releases/latest/)
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
[![Linux build status](https://travis-ci.org/esp8266/Arduino.svg)](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).
- Go to Arduino directory
- For Mac OS X, it is `Arduino.app` showing as the Arduino icon.
This location may be your `~/Downloads`, `~/Desktop` or even `/Applications`.
```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
Also known as latest git or master branch.
- 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).
- Follow the [instructions in the documentation](https://arduino-esp8266.readthedocs.io/en/latest/installing.html#using-git-version).
### Using PlatformIO
[PlatformIO](https://platformio.org?utm_source=github&utm_medium=arduino-esp8266) is an open source ecosystem for IoT
development with cross platform build system, library manager and full support
for Espressif (ESP8266) development. It works on the popular host OS: macOS, Windows,
Linux 32/64, Linux ARM (like Raspberry Pi, BeagleBone, CubieBoard).
[PlatformIO](https://platformio.org?utm_source=arduino-esp8266) is an open source ecosystem for IoT
development with a cross-platform build system, a library manager, and full support
for Espressif (ESP8266) development. It works on the following popular host operating systems: macOS, Windows,
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)
- [PlatformIO IDE](https://platformio.org/platformio-ide?utm_source=github&utm_medium=arduino-esp8266)
- [PlatformIO Core](https://docs.platformio.org/en/latest/core.html?utm_source=github&utm_medium=arduino-esp8266) (command line tool)
- [Advanced usage](https://docs.platformio.org/en/latest/platforms/espressif8266.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=arduino-esp8266)
- [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=arduino-esp8266) -
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
- [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
@ -95,7 +73,7 @@ Documentation for latest development version: https://arduino-esp8266.readthedoc
### 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 />
[![Donate](https://img.shields.io/badge/paypal-donate-yellow.svg)](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)
- 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.
- for issues which happen at compile time, enable verbose compiler output in the IDE preferences, and attach that output (also inside a code block)
- 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 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
- IDE settings (board choice, flash size)
- etc
### 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.
@ -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.
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.
[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.
[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).
[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

File diff suppressed because it is too large Load Diff

View File

@ -6,49 +6,57 @@ TARGET_DIR := ./
TARGET_OBJ_FILES := \
eboot.o \
eboot_command.o \
eboot_command.o
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
CXX := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-g++
AR := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-ar
LD := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-gcc
OBJDUMP := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-objdump
INC += -I../../tools/sdk/include
CFLAGS += -std=gnu99
INC += -I../../tools/sdk/include -I../../tools/sdk/uzlib/src
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)
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
APP_OUT:= eboot.elf
APP_AR := eboot.a
APP_FW := eboot.bin
APP_OUT := eboot.elf
APP_AR := eboot.a
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 $@ $^
$(APP_OUT): $(APP_AR)
$(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
$(APP_OUT): $(APP_AR) eboot.ld | Makefile
$(LD) $(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group -Wl,--sort-common $(APP_AR) -Wl,--end-group -o $@
clean:
rm -f *.o
rm -f $(APP_AR)
rm -f $(APP_OUT)
rm -f *.map
.PHONY: all clean default

View File

@ -12,6 +12,8 @@
#include <string.h>
#include "flash.h"
#include "eboot_command.h"
#include <uzlib.h>
#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))) {
return 1;
}
const char* __attribute__ ((aligned (4))) fmtt = "v%08x\n\0\0";
uint32_t fmt[2];
fmt[0] = ((uint32_t*) fmtt)[0];
fmt[1] = ((uint32_t*) fmtt)[1];
ets_printf((const char*) fmt, ver);
ets_printf("v%08x\n", ver);
return 0;
}
@ -61,7 +59,9 @@ int load_app_from_flash_raw(const uint32_t flash_addr)
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;
}
@ -80,40 +80,124 @@ int load_app_from_flash_raw(const uint32_t flash_addr)
pos += section_header.size;
}
register uint32_t sp asm("a1") = 0x3ffffff0;
register uint32_t pc asm("a3") = image_header.entry;
__asm__ __volatile__ ("jx a3");
asm volatile("" ::: "memory");
asm volatile ("mov.n a1, %0\n"
"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;
}
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,
const uint32_t dst_addr,
const uint32_t size)
const uint32_t size,
const bool verify)
{
// require regions to be aligned
if (src_addr & 0xfff != 0 ||
dst_addr & 0xfff != 0) {
if ((src_addr & 0xfff) != 0 ||
(dst_addr & 0xfff) != 0) {
return 1;
}
const uint32_t buffer_size = FLASH_SECTOR_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 daddr = dst_addr;
struct uzlib_uncomp m_uncomp;
bool gzip = false;
while (left) {
if (SPIEraseSector(daddr/buffer_size)) {
return 2;
// Check if we are uncompressing a GZIP upload or not
if ((read_flash_byte(saddr) == 0x1f) && (read_flash_byte(saddr + 1) == 0x8b)) {
// 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
}
if (SPIRead(saddr, buffer, buffer_size)) {
return 3;
gzip = true;
}
while (left > 0) {
if (!gzip) {
if (SPIRead(saddr, buffer, buffer_size)) {
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 (SPIWrite(daddr, buffer, buffer_size)) {
return 4;
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)) {
return 4;
}
}
}
saddr += buffer_size;
daddr += buffer_size;
@ -123,18 +207,27 @@ int copy_raw(const uint32_t src_addr,
return 0;
}
void main()
int main()
{
int res = 9;
bool clear_cmd = false;
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);
if (eboot_command_read(&cmd) == 0) {
// valid command was passed via RTC_MEM
eboot_command_clear();
clear_cmd = true;
ets_putc('@');
} else {
// no valid command found
@ -144,22 +237,42 @@ void main()
}
if (cmd.action == ACTION_COPY_RAW) {
ets_putc('c'); ets_putc('p'); ets_putc(':');
ets_printf("cp:");
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_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) {
cmd.action = ACTION_LOAD_APP;
cmd.args[0] = cmd.args[1];
}
}
if (clear_cmd) {
eboot_command_clear();
}
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]);
//we will get to this only on load fail
ets_putc('e'); ets_putc(':'); ets_putc('0'+res); ets_putc('\n');
// We will get to this only on load fail
ets_printf("e:%d\n", res);
}
if (res) {
@ -167,4 +280,7 @@ void main()
}
while(true){}
__builtin_unreachable();
return 0;
}

Binary file not shown.

View File

@ -42,51 +42,13 @@ PROVIDE(_memmap_cacheattr_reset = _memmap_cacheattr_wb_trapnull);
SECTIONS
{
.dport0.rodata : ALIGN(4)
.globals : ALIGN(4)
{
_dport0_rodata_start = ABSOLUTE(.);
*(.dport0.rodata)
*(.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
*(COMMON) /* Global vars */
} >dram0_0_seg :dram0_0_bss_phdr
.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)
*(.data.*)
@ -100,7 +62,10 @@ SECTIONS
*(.gnu.linkonce.s2.*)
*(.jcr)
_data_end = ABSOLUTE(.);
. = ALIGN (8);
} >dram0_0_seg :dram0_0_bss_phdr
.rodata : ALIGN(4)
{
_rodata_start = ABSOLUTE(.);
*(.rodata)
*(.rodata.*)
@ -129,14 +94,11 @@ SECTIONS
*(.xt_except_desc_end)
*(.dynamic)
*(.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(.);
} >dram0_0_seg :dram0_0_bss_phdr
. = ALIGN (8);
.bss : ALIGN(4)
{
_bss_start = ABSOLUTE(.);
*(.dynsbss)
*(.sbss)
@ -150,9 +112,24 @@ SECTIONS
*(.bss)
*(.bss.*)
*(.gnu.linkonce.b.*)
*(COMMON)
. = ALIGN (8);
_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
.lit4 : ALIGN(4)

View File

@ -37,7 +37,7 @@ int eboot_command_read(struct eboot_command* 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) {
return 1;
}

View 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__

View File

@ -127,6 +127,8 @@ struct netifWrapper
const char* ifhostname () const { return _netif->hostname?: emptyString.c_str(); }
const char* ifmac () const { return (const char*)_netif->hwaddr; }
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
{
@ -166,8 +168,6 @@ public:
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 ret = *this;

View File

@ -37,14 +37,13 @@ extern "C" {
#include "binary.h"
#include "esp8266_peri.h"
#include "twi.h"
#include "core_esp8266_features.h"
#include "core_esp8266_version.h"
#define HIGH 0x1
#define LOW 0x0
#define PWMRANGE 1023
//GPIO FUNCTIONS
#define INPUT 0x00
#define INPUT_PULLUP 0x02
@ -127,45 +126,14 @@ void timer0_isr_init(void);
void timer0_attachInterrupt(timercallback userFunc);
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 round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))
#define radians(deg) ((deg)*DEG_TO_RAD)
#define degrees(rad) ((rad)*RAD_TO_DEG)
#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 noInterrupts() xt_rsil(15)
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
@ -191,25 +159,23 @@ typedef uint16_t word;
typedef bool boolean;
typedef uint8_t byte;
void ets_intr_lock();
void ets_intr_unlock();
void init(void);
void initVariant(void);
int atexit(void (*func)()) __attribute__((weak));
void pinMode(uint8_t pin, uint8_t mode);
void digitalWrite(uint8_t pin, uint8_t val);
int digitalRead(uint8_t pin);
int analogRead(uint8_t pin);
void analogReference(uint8_t mode);
void analogWrite(uint8_t pin, int val);
void analogWriteMode(uint8_t pin, int val, bool openDrain);
void analogWriteFreq(uint32_t freq);
void analogWriteResolution(int res);
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 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 detachInterrupt(uint8_t pin);
void attachInterruptArg(uint8_t pin, void (*)(void*), void* arg, int mode);
void preinit(void);
void setup(void);
void loop(void);
void yield(void);
void optimistic_yield(uint32_t interval_us);
#define _PORT_GPIO16 1
@ -243,35 +211,37 @@ void optimistic_yield(uint32_t interval_us);
} // extern "C"
#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
#ifdef __cplusplus
extern "C"
#endif
const int TIM_DIV265 __attribute__((deprecated, weak)) = TIM_DIV256;
// from this point onward, we need to configure the c++ environment
#ifdef __cplusplus
#include <algorithm>
#include <pgmspace.h>
#include <cstdlib>
#include <cmath>
#include "WCharacter.h"
#include "WString.h"
#include "HardwareSerial.h"
#include "Esp.h"
#include "Updater.h"
#include "debug.h"
#include "mmu_iram.h"
using std::min;
using std::max;
using std::round;
using std::isinf;
using std::isnan;
#define _min(a,b) ((a)<(b)?(a):(b))
#define _max(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
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(byte h, byte l);
@ -294,11 +264,34 @@ long secureRandom(long);
long secureRandom(long, long);
long map(long, long, long, long, long);
extern "C" void configTime(long timezone, int daylightOffset_sec,
const char* server1, const char* server2 = nullptr, const char* server3 = nullptr);
void setTZ(const char* tz);
#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"
#endif

View 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__

View File

@ -26,15 +26,15 @@
class Client: public Stream {
public:
virtual int connect(IPAddress ip, 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(const uint8_t *buf, size_t size) =0;
virtual int available() = 0;
virtual int read() = 0;
virtual int read(uint8_t *buf, size_t size) = 0;
virtual int peek() = 0;
virtual void flush() = 0;
virtual int connect(IPAddress ip, uint16_t port) = 0;
virtual int connect(const char *host, uint16_t port) = 0;
virtual size_t write(uint8_t) override = 0;
virtual size_t write(const uint8_t *buf, size_t size) override = 0;
virtual int available() override = 0;
virtual int read() override = 0;
virtual int read(uint8_t *buf, size_t size) override = 0;
virtual int peek() override = 0;
virtual void flush() override = 0;
virtual void stop() = 0;
virtual uint8_t connected() = 0;
virtual operator bool() = 0;
@ -42,11 +42,9 @@ class Client: public Stream {
uint8_t* rawIPAddress(IPAddress& addr) {
return addr.raw_address();
}
#if LWIP_VERSION_MAJOR != 1
const uint8_t* rawIPAddress(const IPAddress& addr) {
return addr.raw_address();
}
#endif
};
#endif

553
cores/esp8266/Crypto.cpp Normal file
View 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
View 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

View File

@ -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
// 100 * (1 - sqrt(sum(hole-size²)) / sum(hole-size))
umm_info(NULL, 0);
uint8_t block_size = umm_block_size();
uint32_t fh = ummHeapInfo.freeBlocks * block_size;
umm_info(NULL, false);
uint32_t free_size = umm_free_heap_size_core(umm_get_current_heap());
if (hfree)
*hfree = fh;
*hfree = free_size;
if (hmax)
*hmax = ummHeapInfo.maxFreeContiguousBlocks * block_size;
if (hfrag)
*hfrag = 100 - (sqrt32(ummHeapInfo.freeSize2) * 100) / fh;
*hmax = (uint16_t)umm_max_block_size_core(umm_get_current_heap());
if (hfrag) {
if (free_size) {
*hfrag = umm_fragmentation_metric_core(umm_get_current_heap());
} else {
*hfrag = 0;
}
}
}
uint8_t EspClass::getHeapFragmentation()
{
uint8_t hfrag;
getHeapStats(nullptr, nullptr, &hfrag);
return hfrag;
return (uint8_t)umm_fragmentation_metric();
}

View File

@ -21,36 +21,29 @@
#include <Arduino.h>
#include <user_interface.h>
#include <core_version.h>
#include <lwip/init.h> // LWIP_VERSION_*
#include <lwipopts.h> // LWIP_HASH_STR (lwip2)
#include <bearssl/bearssl_git.h> // BEARSSL_GIT short hash
#define STRHELPER(x) #x
#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);
String EspClass::getFullVersion()
{
return String(F("SDK:")) + system_get_sdk_version()
+ F("/Core:") + FPSTR(arduino_esp8266_git_ver)
+ F("=") + String(esp8266::coreVersionNumeric())
#if LWIP_VERSION_MAJOR == 1
+ F("/lwIP:") + String(LWIP_VERSION_MAJOR) + "." + String(LWIP_VERSION_MINOR) + "." + String(LWIP_VERSION_REVISION)
#if LWIP_VERSION_IS_DEVELOPMENT
+ F("-dev")
#endif
#if LWIP_VERSION_IS_RC
+ 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)
;
String EspClass::getFullVersion() {
String s(F("SDK:"));
s.reserve(127);
s += system_get_sdk_version();
s += FPSTR(arduino_esp8266_git_ver);
s += String(esp8266::coreVersionNumeric());
s += FPSTR(lwip_version);
s += FPSTR(bearssl_version);
return s;
}

View File

@ -18,7 +18,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "Arduino.h"
#include "Esp.h"
#include "flash_utils.h"
#include "eboot_command.h"
#include <memory>
@ -27,6 +27,11 @@
#include "umm_malloc/umm_malloc.h"
#include "cont.h"
#include "coredecls.h"
#include "umm_malloc/umm_malloc.h"
#include <pgmspace.h>
#include "reboot_uart_dwnld.h"
extern "C" {
#include "user_interface.h"
@ -36,6 +41,9 @@ extern struct rst_info resetInfo;
//#define DEBUG_SERIAL Serial
#ifndef PUYA_SUPPORT
#define PUYA_SUPPORT 1
#endif
/**
* 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)
{
__real_system_restart_local();
@ -198,9 +203,18 @@ void EspClass::restart(void)
esp_yield();
}
[[noreturn]] void EspClass::rebootIntoUartDownloadMode()
{
wdtDisable();
/* disable hardware watchdog */
CLEAR_PERI_REG_MASK(PERIPHS_HW_WDT, 0x1);
esp8266RebootIntoUartDownloadMode();
}
uint16_t EspClass::getVcc(void)
{
InterruptLock lock;
esp8266::InterruptLock lock;
(void)lock;
return system_get_vdd33();
}
@ -258,12 +272,6 @@ uint8_t EspClass::getBootMode(void)
return system_get_boot_mode();
}
uint8_t EspClass::getCpuFreqMHz(void)
{
return system_get_cpu_freq();
}
uint32_t EspClass::getFlashChipId(void)
{
static uint32_t flash_chip_id = 0;
@ -397,6 +405,8 @@ uint32_t EspClass::getFlashChipSizeByChipId(void) {
return (64_kB);
// Winbond
case 0x1840EF: // W25Q128
return (16_MB);
case 0x1640EF: // W25Q32
return (4_MB);
case 0x1540EF: // W25Q16
@ -416,6 +426,10 @@ uint32_t EspClass::getFlashChipSizeByChipId(void) {
case 0x1340E0: // BG25Q40
return (512_kB);
// XMC - Wuhan Xinxin Semiconductor Manufacturing Corp
case 0x164020: // XM25QH32B
return (4_MB);
default:
return 0;
}
@ -439,35 +453,59 @@ bool EspClass::checkFlashConfig(bool needsEquals) {
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) {
char buff[32];
if (resetInfo.reason == REASON_DEFAULT_RST) { // normal startup by power on
strcpy_P(buff, PSTR("Power on"));
} else if (resetInfo.reason == REASON_WDT_RST) { // hardware watch dog reset
strcpy_P(buff, PSTR("Hardware Watchdog"));
} else if (resetInfo.reason == REASON_EXCEPTION_RST) { // exception reset, GPIO status wont change
strcpy_P(buff, PSTR("Exception"));
} else if (resetInfo.reason == REASON_SOFT_WDT_RST) { // software watch dog reset, GPIO status wont change
strcpy_P(buff, PSTR("Software Watchdog"));
} else if (resetInfo.reason == REASON_SOFT_RESTART) { // software restart ,system_restart , GPIO status wont change
strcpy_P(buff, PSTR("Software/System restart"));
} else if (resetInfo.reason == REASON_DEEP_SLEEP_AWAKE) { // wake up from deep-sleep
strcpy_P(buff, PSTR("Deep-Sleep Wake"));
} else if (resetInfo.reason == REASON_EXT_SYS_RST) { // external system reset
strcpy_P(buff, PSTR("External System"));
} else {
strcpy_P(buff, PSTR("Unknown"));
const __FlashStringHelper* buff;
switch(resetInfo.reason) {
// normal startup by power on
case REASON_DEFAULT_RST: buff = F("Power On"); break;
// hardware watch dog reset
case REASON_WDT_RST: buff = F("Hardware Watchdog"); break;
// exception reset, GPIO status wont change
case REASON_EXCEPTION_RST: buff = F("Exception"); break;
// software watch dog reset, GPIO status wont change
case REASON_SOFT_WDT_RST: buff = F("Software Watchdog"); break;
// software restart ,system_restart , GPIO status wont change
case REASON_SOFT_RESTART: buff = F("Software/System restart"); break;
// wake up from deep-sleep
case REASON_DEEP_SLEEP_AWAKE: buff = F("Deep-Sleep Wake"); break;
// // external system reset
case REASON_EXT_SYS_RST: buff = F("External System"); break;
default: buff = F("Unknown"); break;
}
return String(buff);
}
String EspClass::getResetInfo(void) {
if(resetInfo.reason != 0) {
if (resetInfo.reason >= REASON_WDT_RST && resetInfo.reason <= REASON_SOFT_WDT_RST) {
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("flag: 0");
return getResetReason();
}
struct rst_info * EspClass::getResetInfoPtr(void) {
@ -487,6 +525,63 @@ bool EspClass::eraseConfig(void) {
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() {
static uint32_t result = 0;
if (result)
@ -494,7 +589,7 @@ uint32_t EspClass::getSketchSize() {
image_header_t image_header;
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;
}
pos += sizeof(image_header);
@ -506,7 +601,7 @@ uint32_t EspClass::getSketchSize() {
++section_index)
{
section_header_t section_header = {0, 0};
if (spi_flash_read(pos, (uint32_t*) &section_header, sizeof(section_header))) {
if (spi_flash_read(pos, (uint32_t*) &section_header, sizeof(section_header)) != SPI_FLASH_RESULT_OK) {
return 0;
}
pos += sizeof(section_header);
@ -519,14 +614,14 @@ uint32_t EspClass::getSketchSize() {
return result;
}
extern "C" uint32_t _SPIFFS_start;
extern "C" uint32_t _FS_start;
uint32_t EspClass::getFreeSketchSpace() {
uint32_t usedSize = getSketchSize();
// round one sector up
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
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
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) {
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.
static uint32_t *flash_write_puya_buf = nullptr;
int rc = 0;
uint32_t* ptr = data;
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.
if (flash_write_puya_buf == nullptr) {
// 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;
uint32_t pos = offset;
while (bytesLeft > 0 && rc == 0) {
while (bytesLeft > 0 && rc == SPI_FLASH_RESULT_OK) {
size_t bytesNow = bytesLeft;
if (bytesNow > PUYA_BUFFER_SIZE) {
bytesNow = PUYA_BUFFER_SIZE;
bytesLeft -= PUYA_BUFFER_SIZE;
if (bytesNow > FLASH_PAGE_SIZE) {
bytesNow = FLASH_PAGE_SIZE;
bytesLeft -= FLASH_PAGE_SIZE;
} else {
bytesLeft = 0;
}
rc = spi_flash_read(pos, flash_write_puya_buf, bytesNow);
if (rc != 0) {
if (rc != SPI_FLASH_RESULT_OK) {
return rc;
}
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
bool EspClass::flashWrite(uint32_t offset, uint32_t *data, size_t size) {
int rc = 0;
bool EspClass::flashReplaceBlock(uint32_t address, const uint8_t *value, uint32_t byteCount) {
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 (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
#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;
}
memcpy((uint8_t *)&tempData + alignmentOffset, value, byteCount);
if (spi_flash_write(alignedAddress, &tempData, 4) != SPI_FLASH_RESULT_OK) {
return false;
}
}
return rc == 0;
return true;
}
bool EspClass::flashRead(uint32_t offset, uint32_t *data, size_t size) {
int rc = spi_flash_read(offset, (uint32_t*) data, size);
return rc == 0;
size_t EspClass::flashWriteUnalignedMemory(uint32_t address, const uint8_t *data, size_t size) {
size_t sizeLeft = (size & ~3);
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()
@ -646,17 +962,17 @@ String EspClass::getSketchMD5()
}
uint32_t lengthLeft = getSketchSize();
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;
if(!buf.get()) {
return String();
return emptyString;
}
MD5Builder md5;
md5.begin();
while( lengthLeft > 0) {
size_t readBytes = (lengthLeft < bufSize) ? lengthLeft : bufSize;
if (!flashRead(offset, reinterpret_cast<uint32_t*>(buf.get()), (readBytes + 3) & ~3)) {
return String();
return emptyString;
}
md5.add(buf.get(), readBytes);
lengthLeft -= readBytes;
@ -666,3 +982,47 @@ String EspClass::getSketchMD5()
result = md5.toString();
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
}

View File

@ -22,52 +22,8 @@
#define ESP_H
#include <Arduino.h>
#ifndef PUYA_SUPPORT
#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;
#include "core_esp8266_features.h"
#include "spi_vendors.h"
/**
* AVR macros for WDT managment
@ -147,6 +103,12 @@ class EspClass {
void reset();
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();
uint32_t getChipId();
@ -166,7 +128,13 @@ class EspClass {
uint8_t getBootVersion();
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();
uint8_t getFlashChipVendorId();
@ -185,9 +153,51 @@ class EspClass {
bool checkFlashConfig(bool needsEquals = false);
bool checkFlashCRC();
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();
String getSketchMD5();
@ -200,20 +210,81 @@ class EspClass {
bool eraseConfig();
#ifndef CORE_MOCK
inline
#endif
uint32_t getCycleCount();
};
uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes) const;
uint32_t random() const;
#ifndef CORE_MOCK
uint32_t EspClass::getCycleCount()
{
uint32_t ccount;
__asm__ __volatile__("esync; rsr %0,ccount":"=a" (ccount));
return ccount;
}
#endif
#if !defined(CORE_MOCK)
inline uint32_t getCycleCount() __attribute__((always_inline))
{
return esp_get_cycle_count();
}
#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;

View File

@ -46,6 +46,14 @@ int File::available() {
return _p->size() - _p->position();
}
int File::availableForWrite() {
if (!_p)
return false;
return _p->availableForWrite();
}
int File::read() {
if (!_p)
return -1;
@ -58,9 +66,9 @@ int File::read() {
return result;
}
size_t File::read(uint8_t* buf, size_t size) {
int File::read(uint8_t* buf, size_t size) {
if (!_p)
return -1;
return 0;
return _p->read(buf, size);
}
@ -180,6 +188,27 @@ String File::readString()
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) {
if (!_impl) {
return File();
@ -192,7 +221,9 @@ File Dir::openFile(const char* mode) {
return File();
}
return File(_impl->openFile(om, am), _baseFS);
File f(_impl->openFile(om, am), _baseFS);
f.setTimeCallback(_timeCallback);
return f;
}
String Dir::fileName() {
@ -203,6 +234,18 @@ String Dir::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() {
if (!_impl) {
return 0;
@ -241,6 +284,14 @@ bool Dir::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) {
if (!_impl) {
return false;
@ -251,9 +302,13 @@ bool FS::setConfig(const FSConfig &cfg) {
bool FS::begin() {
if (!_impl) {
DEBUGV("#error: FS: no implementation");
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() {
@ -269,6 +324,13 @@ bool FS::gc() {
return _impl->gc();
}
bool FS::check() {
if (!_impl) {
return false;
}
return _impl->check();
}
bool FS::format() {
if (!_impl) {
return false;
@ -283,6 +345,13 @@ bool FS::info(FSInfo& 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) {
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);
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) {
@ -317,7 +388,9 @@ Dir FS::openDir(const char* path) {
return Dir();
}
DirImplPtr p = _impl->openDir(path);
return Dir(p, this);
Dir d(p, this);
d.setTimeCallback(_timeCallback);
return d;
}
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());
}
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) {

View File

@ -23,6 +23,7 @@
#include <memory>
#include <Arduino.h>
#include <../include/time.h> // See issue #6714
class SDClass;
@ -56,22 +57,24 @@ public:
// Print methods:
size_t write(uint8_t) override;
size_t write(const uint8_t *buf, size_t size) override;
int availableForWrite() override;
// Stream methods:
int available() override;
int read() override;
int peek() override;
void flush() override;
size_t readBytes(char *buffer, size_t length) override {
size_t readBytes(char *buffer, size_t length) override {
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) {
return seek(pos, SeekSet);
}
size_t position() const;
size_t size() const;
virtual ssize_t streamRemaining() override { return (ssize_t)size() - (ssize_t)position(); }
void close();
operator bool() const;
const char* name() const;
@ -82,6 +85,7 @@ public:
bool isDirectory() const;
// Arduino "class SD" methods for compatibility
//TODO use stream::send / check read(buf,size) result
template<typename T> size_t write(T &src){
uint8_t obuf[256];
size_t doneLen = 0;
@ -110,8 +114,13 @@ public:
String readString() override;
time_t getLastWrite();
time_t getCreationTime();
void setTimeCallback(time_t (*cb)(void));
protected:
FileImplPtr _p;
time_t (*_timeCallback)(void) = nullptr;
// Arduino SD class emulation
std::shared_ptr<Dir> _fakeDir;
@ -126,17 +135,23 @@ public:
String fileName();
size_t fileSize();
time_t fileTime();
time_t fileCreationTime();
bool isFile() const;
bool isDirectory() const;
bool next();
bool rewind();
void setTimeCallback(time_t (*cb)(void));
protected:
DirImplPtr _impl;
FS *_baseFS;
time_t (*_timeCallback)(void) = nullptr;
};
// Backwards compatible, <4GB filesystem usage
struct FSInfo {
size_t totalBytes;
size_t usedBytes;
@ -146,15 +161,24 @@ struct FSInfo {
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
{
public:
FSConfig(bool autoFormat = true) {
_type = FSConfig::fsid::FSId;
_autoFormat = autoFormat;
}
static constexpr uint32_t FSId = 0x00000000;
FSConfig(uint32_t type = FSId, bool autoFormat = true) : _type(type), _autoFormat(autoFormat) { }
enum fsid { FSId = 0x00000000 };
FSConfig setAutoFormat(bool val = true) {
_autoFormat = val;
return *this;
@ -167,17 +191,17 @@ public:
class SPIFFSConfig : public FSConfig
{
public:
SPIFFSConfig(bool autoFormat = true) {
_type = SPIFFSConfig::fsid::FSId;
_autoFormat = autoFormat;
}
enum fsid { FSId = 0x53504946 };
static constexpr uint32_t FSId = 0x53504946;
SPIFFSConfig(bool autoFormat = true) : FSConfig(FSId, autoFormat) { }
// Inherit _type and _autoFormat
// nothing yet, enableTime TBD when SPIFFS has metadate
};
class FS
{
public:
FS(FSImplPtr impl) : _impl(impl) { }
FS(FSImplPtr impl) : _impl(impl) { _timeCallback = _defaultTimeCB; }
bool setConfig(const FSConfig &cfg);
@ -186,6 +210,7 @@ public:
bool format();
bool info(FSInfo& info);
bool info64(FSInfo64& info);
File open(const char* 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 String& path);
// Low-level FS routines, not needed by most applications
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
protected:
FSImplPtr _impl;
FSImplPtr getImpl() { return _impl; }
time_t (*_timeCallback)(void) = nullptr;
static time_t _defaultTimeCB(void) { return time(NULL); }
};
} // namespace fs
extern "C"
{
void close_all_fs(void);
void littlefs_request_end(void);
void spiffs_request_end(void);
}
#ifndef FS_NO_GLOBALS
using fs::FS;
using fs::File;
@ -228,10 +268,11 @@ using fs::SeekCur;
using fs::SeekEnd;
using fs::FSInfo;
using fs::FSConfig;
using fs::SPIFFSConfig;
#endif //FS_NO_GLOBALS
#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 //FS_H

View File

@ -22,6 +22,7 @@
#include <stddef.h>
#include <stdint.h>
#include <FS.h>
namespace fs {
@ -29,17 +30,33 @@ class FileImpl {
public:
virtual ~FileImpl() { }
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 bool seek(uint32_t pos, SeekMode mode) = 0;
virtual size_t position() const = 0;
virtual size_t size() const = 0;
virtual int availableForWrite() { return 0; }
virtual bool truncate(uint32_t size) = 0;
virtual void close() = 0;
virtual const char* name() const = 0;
virtual const char* fullName() const = 0;
virtual bool isFile() 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 {
@ -61,10 +78,23 @@ public:
virtual FileImplPtr openFile(OpenMode openMode, AccessMode accessMode) = 0;
virtual const char* fileName() = 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 isDirectory() const = 0;
virtual bool next() = 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 {
@ -75,6 +105,7 @@ public:
virtual void end() = 0;
virtual bool format() = 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 bool exists(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 rmdir(const char* path) = 0;
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

33
cores/esp8266/FSnoop.cpp Normal file
View 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");
}
}

View File

@ -1,23 +1,21 @@
#include <FunctionalInterrupt.h>
#include <Schedule.h>
#include "Arduino.h"
#include <ScheduledFunctions.h>
// Duplicate typedefs from core_esp8266_wiring_digital_c
typedef void (*voidFuncPtr)(void);
typedef void (*voidFuncPtrArg)(void*);
// 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;
if (localArg->functionInfo->reqScheduledFunction)
{
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)
{
@ -49,15 +47,11 @@ void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode
as->interruptInfo = ii;
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)
{
if (!scheduledInterrupts)
{
scheduledInterrupts = new ScheduledFunctions(32);
}
InterruptInfo* ii = new InterruptInfo;
FunctionInfo* fi = new FunctionInfo;
@ -67,5 +61,5 @@ void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> sc
as->interruptInfo = ii;
as->functionInfo = fi;
__attachInterruptArg (pin, (voidFuncPtr)interruptFunctional, as, mode);
__attachInterruptFunctionalArg(pin, (voidFuncPtr)interruptFunctional, as, mode, true);
}

View File

@ -4,7 +4,6 @@
#include <stddef.h>
#include <stdint.h>
#include <functional>
#include <ScheduledFunctions.h>
extern "C" {
#include "c_types.h"
@ -29,7 +28,6 @@ struct ArgStructure {
FunctionInfo* functionInfo = nullptr;
};
static ScheduledFunctions* scheduledInterrupts;
void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode);
void attachScheduledInterrupt(uint8_t pin, std::function<void(InterruptInfo)> scheduledIntRoutine, int mode);

View File

@ -1,157 +1,184 @@
/*
HardwareSerial.cpp - esp8266 UART support
Copyright (c) 2014 Ivan Grokhotkov. 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
Modified 31 March 2015 by Markus Sattler (rewrite the code for UART0 + UART1 support in ESP8266)
Modified 25 April 2015 by Thomas Flayols (add configuration different from 8N1 in ESP8266)
Modified 3 May 2015 by Hristo Gochkov (change register access methods)
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <PolledTimeout.h>
#include "Arduino.h"
#include "HardwareSerial.h"
#include "Esp.h"
HardwareSerial::HardwareSerial(int uart_nr)
: _uart_nr(uart_nr), _rx_size(256)
{}
void HardwareSerial::begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin)
{
end();
_uart = uart_init(_uart_nr, baud, (int) config, (int) mode, tx_pin, _rx_size);
#if defined(DEBUG_ESP_PORT) && !defined(NDEBUG)
if (static_cast<void*>(this) == static_cast<void*>(&DEBUG_ESP_PORT))
{
setDebugOutput(true);
println();
println(ESP.getFullVersion());
}
#endif
}
void HardwareSerial::end()
{
if(uart_get_debug() == _uart_nr) {
uart_set_debug(UART_NO);
}
uart_uninit(_uart);
_uart = NULL;
}
size_t HardwareSerial::setRxBufferSize(size_t size){
if(_uart) {
_rx_size = uart_resize_rx_buffer(_uart, size);
} else {
_rx_size = size;
}
return _rx_size;
}
void HardwareSerial::setDebugOutput(bool en)
{
if(!_uart) {
return;
}
if(en) {
if(uart_tx_enabled(_uart)) {
uart_set_debug(_uart_nr);
} else {
uart_set_debug(UART_NO);
}
} else {
// disable debug for this interface
if(uart_get_debug() == _uart_nr) {
uart_set_debug(UART_NO);
}
}
}
int HardwareSerial::available(void)
{
int result = static_cast<int>(uart_rx_available(_uart));
if (!result) {
optimistic_yield(10000);
}
return result;
}
void HardwareSerial::flush()
{
if(!_uart || !uart_tx_enabled(_uart)) {
return;
}
uart_wait_tx_empty(_uart);
//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
delayMicroseconds(11000000 / uart_get_baudrate(_uart) + 1);
}
void HardwareSerial::startDetectBaudrate()
{
uart_start_detect_baudrate(_uart_nr);
}
unsigned long HardwareSerial::testBaudrate()
{
return uart_detect_baudrate(_uart_nr);
}
unsigned long HardwareSerial::detectBaudrate(time_t timeoutMillis)
{
time_t startMillis = millis();
unsigned long detectedBaudrate;
while ((time_t) millis() - startMillis < timeoutMillis) {
if ((detectedBaudrate = testBaudrate())) {
break;
}
yield();
delay(100);
}
return detectedBaudrate;
}
/*
HardwareSerial.cpp - esp8266 UART support
Copyright (c) 2014 Ivan Grokhotkov. 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
Modified 31 March 2015 by Markus Sattler (rewrite the code for UART0 + UART1 support in ESP8266)
Modified 25 April 2015 by Thomas Flayols (add configuration different from 8N1 in ESP8266)
Modified 3 May 2015 by Hristo Gochkov (change register access methods)
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <PolledTimeout.h>
#include "Arduino.h"
#include "HardwareSerial.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)
: _uart_nr(uart_nr), _rx_size(256)
{}
void HardwareSerial::begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin, bool invert)
{
end();
_uart = uart_init(_uart_nr, baud, (int) config, (int) mode, tx_pin, _rx_size, invert);
#if defined(DEBUG_ESP_PORT) && !defined(NDEBUG)
if (static_cast<void*>(this) == static_cast<void*>(&DEBUG_ESP_PORT))
{
setDebugOutput(true);
println();
println(ESP.getFullVersion());
}
#endif
}
void HardwareSerial::end()
{
if(uart_get_debug() == _uart_nr) {
uart_set_debug(UART_NO);
}
uart_uninit(_uart);
_uart = NULL;
}
void HardwareSerial::updateBaudRate(unsigned long baud)
{
if(!_uart) {
return;
}
uart_set_baudrate(_uart, baud);
}
size_t HardwareSerial::setRxBufferSize(size_t size){
if(_uart) {
_rx_size = uart_resize_rx_buffer(_uart, size);
} else {
_rx_size = size;
}
return _rx_size;
}
void HardwareSerial::setDebugOutput(bool en)
{
if(!_uart) {
return;
}
if(en) {
if(uart_tx_enabled(_uart)) {
uart_set_debug(_uart_nr);
} else {
uart_set_debug(UART_NO);
}
} else {
// disable debug for this interface
if(uart_get_debug() == _uart_nr) {
uart_set_debug(UART_NO);
}
}
}
int HardwareSerial::available(void)
{
int result = static_cast<int>(uart_rx_available(_uart));
if (!result) {
optimistic_yield(10000);
}
return result;
}
void HardwareSerial::flush()
{
uint8_t bit_length = 0;
if(!_uart || !uart_tx_enabled(_uart)) {
return;
}
bit_length = uart_get_bit_length(_uart_nr); // data width, parity and stop
uart_wait_tx_empty(_uart);
//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
delayMicroseconds(bit_length * 1000000 / uart_get_baudrate(_uart) + 1);
}
void HardwareSerial::startDetectBaudrate()
{
uart_start_detect_baudrate(_uart_nr);
}
unsigned long HardwareSerial::testBaudrate()
{
return uart_detect_baudrate(_uart_nr);
}
unsigned long HardwareSerial::detectBaudrate(time_t timeoutMillis)
{
esp8266::polledTimeout::oneShotFastMs timeOut(timeoutMillis);
unsigned long detectedBaudrate = 0;
while (!timeOut) {
if ((detectedBaudrate = testBaudrate())) {
break;
}
yield();
delay(100);
}
return detectedBaudrate;
}
size_t HardwareSerial::readBytes(char* buffer, size_t size)
{
size_t got = 0;
while (got < size)
{
esp8266::polledTimeout::oneShotFastMs timeOut(_timeout);
size_t avail;
while ((avail = available()) == 0 && !timeOut);
if (avail == 0)
break;
got += read(buffer + got, std::min(size - got, avail));
}
return got;
size_t got = 0;
while (got < size)
{
esp8266::polledTimeout::oneShotFastMs timeOut(_timeout);
size_t avail;
while ((avail = available()) == 0 && !timeOut);
if (avail == 0)
break;
got += read(buffer + got, std::min(size - got, avail));
}
return got;
}
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
HardwareSerial Serial(UART0);
#endif
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL1)
HardwareSerial Serial1(UART1);
#endif
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
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
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL1)
HardwareSerial Serial1(UART1);
#endif

View File

@ -28,7 +28,7 @@
#define HardwareSerial_h
#include <inttypes.h>
#include <time.h>
#include <../include/time.h> // See issue #6714
#include "Stream.h"
#include "uart.h"
@ -73,52 +73,59 @@ public:
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)
{
begin(baud, config, SERIAL_FULL, 1);
begin(baud, config, SERIAL_FULL, 1, false);
}
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 updateBaudRate(unsigned long baud);
size_t setRxBufferSize(size_t size);
size_t getRxBufferSize()
{
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.
* 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 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;
@ -128,22 +135,51 @@ public:
// return -1 when data is unvailable (arduino api)
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
{
// return -1 when data is unvailable (arduino api)
return uart_read_char(_uart);
}
// ::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);
}
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(uint8_t* buffer, size_t size) override
{
return readBytes((char*)buffer, size);
}
int availableForWrite(void)
int availableForWrite(void) override
{
return static_cast<int>(uart_tx_free(_uart));
}
@ -200,4 +236,6 @@ protected:
extern HardwareSerial Serial;
extern HardwareSerial Serial1;
extern void serialEventRun(void) __attribute__((weak));
#endif

View File

@ -32,7 +32,7 @@ IPAddress::IPAddress() {
}
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) {
@ -180,9 +180,13 @@ bool IPAddress::isValid(const char* 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);
void IPAddress::clear() {
(*this) = INADDR_ANY;
}
/**************************************/
#if LWIP_IPV6

View File

@ -26,27 +26,18 @@
#include <lwip/init.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
struct ip_addr: ipv4_addr { };
#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
// IPv6 update:
@ -136,10 +127,13 @@ class IPAddress: public Printable {
// Overloaded copy operators to allow initialisation of IPAddress objects from other types
IPAddress& operator=(const uint8_t *address);
IPAddress& operator=(uint32_t address);
IPAddress& operator=(const IPAddress&) = default;
virtual size_t printTo(Print& p) const;
String toString() const;
void clear();
/*
check if input string(arg) is a valid IPV4 address or not.
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;
#endif

View 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"

File diff suppressed because it is too large Load Diff

View 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
View 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
View 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

View 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
View 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

View File

@ -1,10 +1,11 @@
#include <Arduino.h>
#include <MD5Builder.h>
#include <memory>
uint8_t hex_char_to_byte(uint8_t c){
return (c >= 'a' && c <= 'f') ? (c - ((uint8_t)'a' - 0xa)) :
(c >= 'A' && c <= 'F') ? (c - ((uint8_t)'A' - 0xA)) :
(c >= '0' && c<= '9') ? (c - (uint8_t)'0') : 0;
uint8_t hex_char_to_byte(uint8_t c) {
return (c >= 'a' && c <= 'f') ? (c - ((uint8_t)'a' - 0xa)) :
(c >= 'A' && c <= 'F') ? (c - ((uint8_t)'A' - 0xA)) :
(c >= '0' && c <= '9') ? (c - (uint8_t)'0') : 0;
}
void MD5Builder::begin(void){
@ -18,25 +19,27 @@ void MD5Builder::add(const uint8_t * data, const uint16_t len){
void MD5Builder::addHexString(const char * data){
uint16_t i, len = strlen(data);
uint8_t * tmp = (uint8_t*)malloc(len/2);
if(tmp == NULL) {
auto tmp = std::unique_ptr<uint8_t[]>{new(std::nothrow) uint8_t[len / 2]};
if (!tmp) {
return;
}
for(i=0; i<len; i+=2) {
uint8_t high = hex_char_to_byte(data[i]);
uint8_t low = hex_char_to_byte(data[i+1]);
tmp[i/2] = (high & 0x0F) << 4 | (low & 0x0F);
}
add(tmp, len/2);
free(tmp);
add(tmp.get(), len/2);
}
bool MD5Builder::addStream(Stream & stream, const size_t maxLen){
bool MD5Builder::addStream(Stream &stream, const size_t maxLen) {
const int buf_size = 512;
int maxLengthLeft = maxLen;
uint8_t * buf = (uint8_t*) malloc(buf_size);
if(!buf) {
auto buf = std::unique_ptr<uint8_t[]>{new(std::nothrow) uint8_t[buf_size]};
if (!buf) {
return false;
}
@ -45,21 +48,21 @@ bool MD5Builder::addStream(Stream & stream, const size_t maxLen){
// determine number of bytes to read
int readBytes = bytesAvailable;
if(readBytes > maxLengthLeft) {
readBytes = maxLengthLeft ; // read only until max_len
if (readBytes > maxLengthLeft){
readBytes = maxLengthLeft; // read only until max_len
}
if(readBytes > buf_size) {
if (readBytes > buf_size){
readBytes = buf_size; // not read more the buffer can handle
}
// read data and check if we got something
int numBytesRead = stream.readBytes(buf, readBytes);
if(numBytesRead< 1) {
int numBytesRead = stream.readBytes(buf.get(), readBytes);
if (numBytesRead < 1) {
return false;
}
// Update MD5 with buffer payload
MD5Update(&_ctx, buf, numBytesRead);
MD5Update(&_ctx, buf.get(), numBytesRead);
yield(); // time for network streams
@ -67,7 +70,7 @@ bool MD5Builder::addStream(Stream & stream, const size_t maxLen){
maxLengthLeft -= numBytesRead;
bytesAvailable = stream.available();
}
free(buf);
return true;
}
@ -75,17 +78,17 @@ void MD5Builder::calculate(void){
MD5Final(_buf, &_ctx);
}
void MD5Builder::getBytes(uint8_t * output){
void MD5Builder::getBytes(uint8_t * output) const {
memcpy(output, _buf, 16);
}
void MD5Builder::getChars(char * output){
for(uint8_t i = 0; i < 16; i++) {
void MD5Builder::getChars(char * output) const {
for (uint8_t i=0; i<16; i++){
sprintf(output + (i * 2), "%02x", _buf[i]);
}
}
String MD5Builder::toString(void){
String MD5Builder::toString(void) const {
char out[33];
getChars(out);
return String(out);

View File

@ -40,9 +40,9 @@ class MD5Builder {
void addHexString(const String& data){ addHexString(data.c_str()); }
bool addStream(Stream & stream, const size_t maxLen);
void calculate(void);
void getBytes(uint8_t * output);
void getChars(char * output);
String toString(void);
void getBytes(uint8_t * output) const;
void getChars(char * output) const;
String toString(void) const;
};

View File

@ -4,7 +4,7 @@
/*
PolledTimeout.h - Encapsulation of a polled Timeout
Copyright (c) 2018 Daniel Salazar. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
@ -23,9 +23,10 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <limits>
#include <Arduino.h>
#include <c_types.h> // IRAM_ATTR
#include <limits> // std::numeric_limits
#include <type_traits> // std::is_unsigned
#include <core_esp8266_features.h>
namespace esp8266
{
@ -70,13 +71,13 @@ struct TimeSourceMillis
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
// (every loop, every yield)
using timeType = decltype(ESP.getCycleCount());
static timeType time() {return ESP.getCycleCount();}
static constexpr timeType ticksPerSecond = F_CPU; // 80'000'000 or 160'000'000 Hz
using timeType = decltype(esp_get_cycle_count());
static timeType time() {return esp_get_cycle_count();}
static constexpr timeType ticksPerSecond = esp_get_cpu_freq_mhz() * 1000000UL; // 80'000'000 or 160'000'000 Hz
static constexpr timeType ticksPerSecondMax = 160000000; // 160MHz
};
@ -153,7 +154,7 @@ public:
reset(userTimeout);
}
ICACHE_RAM_ATTR
IRAM_ATTR // fast
bool expired()
{
YieldPolicyT::execute(); //in case of DoNothing: gets optimized away
@ -161,13 +162,13 @@ public:
return expiredRetrigger();
return expiredOneShot();
}
ICACHE_RAM_ATTR
IRAM_ATTR // fast
operator bool()
{
return expired();
return expired();
}
bool canExpire () const
{
return !_neverExpires;
@ -178,6 +179,8 @@ public:
return _timeout != alwaysExpired;
}
// Resets, will trigger after this new timeout.
IRAM_ATTR // called from ISR
void reset(const timeType newUserTimeout)
{
reset();
@ -185,11 +188,30 @@ public:
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
}
// Resets, will trigger after the timeout previously set.
IRAM_ATTR // called from ISR
void reset()
{
_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 ()
{
_timeout = alwaysExpired + 1; // because canWait() has precedence
@ -200,7 +222,7 @@ public:
{
return TimePolicyT::toUserUnit(_timeout);
}
static constexpr timeType timeMax()
{
return TimePolicyT::timeMax;
@ -208,7 +230,7 @@ public:
private:
ICACHE_RAM_ATTR
IRAM_ATTR // fast
bool checkExpired(const timeType internalUnit) const
{
// canWait() is not checked here
@ -218,7 +240,7 @@ private:
protected:
ICACHE_RAM_ATTR
IRAM_ATTR // fast
bool expiredRetrigger()
{
if (!canWait())
@ -233,14 +255,14 @@ protected:
}
return false;
}
ICACHE_RAM_ATTR
IRAM_ATTR // fast
bool expiredOneShot() const
{
// returns "always expired" or "has expired"
return !canWait() || checkExpired(TimePolicyT::time());
}
timeType _timeout;
timeType _start;
bool _neverExpires;
@ -257,14 +279,14 @@ using periodic = polledTimeout::timeoutTemplate<true> /*__attribute__((deprecate
using oneShotMs = polledTimeout::timeoutTemplate<false>;
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%)
// (cpu cycles for ::expired(): 372 (millis()) vs 52 (ESP.getCycleCount()))
// (cpu cycles for ::expired(): 372 (millis()) vs 52 (esp_get_cycle_count()))
// timeMax() values:
// Ms: max is 26843 ms (26.8 s)
// Us: max is 26843545 us (26.8 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 periodicFastMs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;

View File

@ -33,19 +33,11 @@
/* default implementation: may be overridden */
size_t Print::write(const uint8_t *buffer, size_t size) {
#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
IAMSLOW();
size_t n = 0;
while (size--) {
size_t ret = write(*buffer++);
size_t ret = write(pgm_read_byte(buffer++));
if (ret == 0) {
// Write of last byte didn't complete, abort additional processing
break;
@ -63,7 +55,7 @@ size_t Print::printf(const char *format, ...) {
size_t len = vsnprintf(temp, sizeof(temp), format, arg);
va_end(arg);
if (len > sizeof(temp) - 1) {
buffer = new char[len + 1];
buffer = new (std::nothrow) char[len + 1];
if (!buffer) {
return 0;
}
@ -86,7 +78,7 @@ size_t Print::printf_P(PGM_P format, ...) {
size_t len = vsnprintf_P(temp, sizeof(temp), format, arg);
va_end(arg);
if (len > sizeof(temp) - 1) {
buffer = new char[len + 1];
buffer = new (std::nothrow) char[len + 1];
if (!buffer) {
return 0;
}
@ -104,11 +96,19 @@ size_t Print::printf_P(PGM_P format, ...) {
size_t Print::print(const __FlashStringHelper *ifsh) {
PGM_P p = reinterpret_cast<PGM_P>(ifsh);
char buff[128] __attribute__ ((aligned(4)));
auto len = strlen_P(p);
size_t n = 0;
while (1) {
uint8_t c = pgm_read_byte(p++);
if (c == 0) break;
n += write(c);
while (n < len) {
int to_write = std::min(sizeof(buff), len - n);
memcpy_P(buff, p, to_write);
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;
}
@ -138,35 +138,39 @@ size_t Print::print(unsigned int n, int base) {
}
size_t Print::print(long n, int base) {
if(base == 0) {
return write(n);
} else if(base == 10) {
if(n < 0) {
int t = print('-');
n = -n;
return printNumber(n, 10) + t;
}
return printNumber(n, 10);
} else {
return printNumber(n, base);
int t = 0;
if (base == 10 && n < 0) {
t = print('-');
n = -n;
}
return printNumber(static_cast<unsigned long>(n), base) + t;
}
size_t Print::print(unsigned long n, int base) {
if(base == 0)
if (base == 0) {
return write(n);
else
return printNumber(n, base);
}
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);
}
size_t Print::print(double n, int digits) {
return printFloat(n, digits);
}
size_t Print::println(const __FlashStringHelper *ifsh) {
size_t n = print(ifsh);
n += println();
return n;
return printNumber(n, digits);
}
size_t Print::print(const Printable& x) {
@ -177,130 +181,90 @@ size_t Print::println(void) {
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 n = print(s);
n += println();
return n;
return _println(s);
}
size_t Print::println(const char c[]) {
size_t n = print(c);
n += println();
return n;
return _println(c);
}
size_t Print::println(char c) {
size_t n = print(c);
n += println();
return n;
return _println(c);
}
size_t Print::println(unsigned char b, int base) {
size_t n = print(b, base);
n += println();
return n;
return _println(b, base);
}
size_t Print::println(int num, int base) {
size_t n = print(num, base);
n += println();
return n;
return _println(num, base);
}
size_t Print::println(unsigned int num, int base) {
size_t n = print(num, base);
n += println();
return n;
return _println(num, base);
}
size_t Print::println(long num, int base) {
size_t n = print(num, base);
n += println();
return n;
return _println(num, base);
}
size_t Print::println(unsigned long num, int base) {
size_t n = print(num, base);
n += println();
return n;
return _println(num, base);
}
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 n = print(num, digits);
n += println();
return n;
return _println(num, digits);
}
size_t Print::println(const Printable& x) {
size_t n = print(x);
n += println();
return n;
return _println<const Printable&>(x);
}
// Private Methods /////////////////////////////////////////////////////////////
size_t Print::printNumber(unsigned long n, uint8_t base) {
char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte.
char *str = &buf[sizeof(buf) - 1];
template<typename T, typename... P> inline size_t Print::_println(T v, P... args)
{
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];
*str = '\0';
// prevent crash if called with base == 1
if(base < 2)
if (base < 2) {
base = 10;
}
do {
unsigned long m = n;
auto m = n;
n /= base;
char c = m - base * n;
*--str = c < 10 ? c + '0' : c + 'A' - 10;
} while(n);
} while (n);
return write(str);
}
size_t Print::printFloat(double number, uint8_t digits) {
size_t n = 0;
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;
template<> size_t Print::printNumber(double number, uint8_t digits) {
char buf[40];
return write(dtostrf(number, 0, digits, buf));
}

View File

@ -26,6 +26,8 @@
#include "WString.h"
#include "Printable.h"
#include "stdlib_noniso.h"
#define DEC 10
#define HEX 16
#define OCT 8
@ -33,17 +35,15 @@
class Print {
private:
int write_error;
size_t printNumber(unsigned long, uint8_t);
size_t printFloat(double, uint8_t);
int write_error = 0;
template<typename T> size_t printNumber(T n, uint8_t base);
template<typename T, typename... P> inline size_t _println(T v, P... args);
protected:
void setWriteError(int err = 1) {
write_error = err;
}
public:
Print() :
write_error(0) {
}
Print() {}
int getWriteError() {
return write_error;
@ -56,7 +56,7 @@ class Print {
size_t write(const char *str) {
if(str == NULL)
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);
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(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)
inline size_t write(char 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_P(PGM_P format, ...) __attribute__((format(printf, 2, 3)));
size_t print(const __FlashStringHelper *);
@ -84,6 +90,8 @@ class Print {
size_t print(unsigned int, int = DEC);
size_t print(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(const Printable&);
@ -96,11 +104,19 @@ class Print {
size_t println(unsigned int, int = DEC);
size_t println(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(const Printable&);
size_t println(void);
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

View File

@ -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
{
scheduled_fn_t* mNext;
std::function<void(void)> mFunc;
scheduled_fn_t* mNext = nullptr;
mSchedFuncT mFunc;
};
static scheduled_fn_t* sFirst = 0;
static scheduled_fn_t* sLast = 0;
static scheduled_fn_t* sFirstUnused = 0;
static scheduled_fn_t* sLastUnused = 0;
static scheduled_fn_t* sFirst = nullptr;
static scheduled_fn_t* sLast = nullptr;
static scheduled_fn_t* sUnused = nullptr;
static int sCount = 0;
static scheduled_fn_t* get_fn() {
scheduled_fn_t* result = NULL;
typedef std::function<bool(void)> mRecFuncT;
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
if (sFirstUnused) {
result = sFirstUnused;
sFirstUnused = result->mNext;
if (sFirstUnused == NULL) {
sLastUnused = NULL;
}
if (sUnused)
{
result = sUnused;
sUnused = sUnused->mNext;
}
// if no unused items, and count not too high, allocate a new one
else if (sCount != SCHEDULED_FN_MAX_COUNT) {
result = new scheduled_fn_t;
result->mNext = NULL;
++sCount;
else if (sCount < SCHEDULED_FN_MAX_COUNT)
{
result = new (std::nothrow) scheduled_fn_t;
if (result)
++sCount;
}
return result;
}
static void recycle_fn(scheduled_fn_t* fn)
static void recycle_fn_unsafe(scheduled_fn_t* fn)
{
if (!sLastUnused) {
sFirstUnused = fn;
}
else {
sLastUnused->mNext = fn;
}
fn->mNext = NULL;
sLastUnused = fn;
fn->mFunc = nullptr; // special overload in c++ std lib
fn->mNext = sUnused;
sUnused = 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 (!item) {
if (!fn)
return false;
}
esp8266::InterruptLock lockAllInterruptsInThisScope;
scheduled_fn_t* item = get_fn_unsafe();
if (!item)
return false;
item->mFunc = fn;
item->mNext = NULL;
if (!sFirst) {
sFirst = item;
}
else {
item->mNext = nullptr;
if (sFirst)
sLast->mNext = item;
}
else
sFirst = 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;
}
void run_scheduled_functions()
{
scheduled_fn_t* rFirst = sFirst;
sFirst = NULL;
sLast = NULL;
while (rFirst) {
scheduled_fn_t* item = rFirst;
rFirst = item->mNext;
item->mFunc();
item->mFunc = std::function<void(void)>();
recycle_fn(item);
esp8266::polledTimeout::periodicFastMs yieldNow(100); // yield every 100ms
// prevent scheduling of new functions during this run
auto stop = sLast;
bool done = false;
while (sFirst && !done)
{
done = sFirst == stop;
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;
}

View File

@ -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
#define ESP_SCHEDULE_H
#include <functional>
#include <stdint.h>
#define SCHEDULED_FN_MAX_COUNT 32
#define SCHEDULED_FN_INITIAL_COUNT 4
// Warning
// This API is not considered stable.
// Function signatures will change.
// You have been warned.
// The purpose of scheduled functions is to trigger, from SYS stack (like in
// an interrupt or a system event), registration of user code to be executed
// in user stack (called CONT stack) without the common restrictions from
// system context. Details are below.
// Run given function next time `loop` function returns,
// or `run_scheduled_functions` is called.
// Use std::bind to pass arguments to a function, or call a class member function.
// Note: there is no mechanism for cancelling scheduled functions.
// Keep that in mind when binding functions to objects which may have short lifetime.
// Returns false if the number of scheduled functions exceeds SCHEDULED_FN_MAX_COUNT.
bool schedule_function(std::function<void(void)> fn);
// The purpose of recurrent scheduled function is to independently execute
// user code in CONT stack on a regular basis.
// It has been introduced with ethernet service in mind, it can also be used
// for all libraries in the need of a regular `libdaemon_handlestuff()`.
// It allows these services to be run even from a user loop not going back
// to `loop()`.
// 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.
// Use this function if your are not using `loop`,
// or `loop` does not return on a regular basis.
// Run all scheduled functions.
// Use this function if your are not using `loop`, or `loop` does not return
// on a regular basis.
void run_scheduled_functions();
#endif //ESP_SCHEDULE_H
// 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

View File

@ -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++;
}
}
}

View File

@ -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_ */

View File

@ -26,8 +26,13 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include "pgmspace.h"
#include "debug.h"
#include "StackThunk.h"
#include <ets_sys.h>
#include <umm_malloc/umm_malloc.h>
#include <umm_malloc/umm_heap_select.h>
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_refcnt = 0;
#define _stackSize (5600/4)
/* Largest stack usage seen in the wild at 6120 */
#define _stackSize (6200/4)
#define _stackPaint 0xdeadbeef
/* Add a reference, and allocate the stack if necessary */
@ -44,7 +50,19 @@ void stack_thunk_add_ref()
{
stack_thunk_refcnt++;
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));
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_save = NULL;
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 */
void stack_thunk_dump_stack()
{
uint32_t *pos = stack_thunk_top;
while (pos < stack_thunk_ptr) {
uint32_t *pos = stack_thunk_ptr;
while (pos < stack_thunk_top) {
if ((pos[0] != _stackPaint) || (pos[1] != _stackPaint) || (pos[2] != _stackPaint) || (pos[3] != _stackPaint))
break;
pos += 4;
}
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]);
pos += 4;
}
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();
}
};

View File

@ -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_max_usage();
extern void stack_thunk_dump_stack();
extern void stack_thunk_fatal_overflow();
// Globals required for thunking operation
extern uint32_t *stack_thunk_ptr;
@ -50,9 +51,10 @@ extern uint32_t stack_thunk_refcnt;
// Thunking macro
#define make_stack_thunk(fcnToThunk) \
__asm("\n\
__asm__ ("\n\
.text\n\
.literal_position\n\
.literal .LC_STACK_VALUE"#fcnToThunk", 0xdeadbeef\n\
\n\
.text\n\
.global thunk_"#fcnToThunk"\n\
@ -67,6 +69,14 @@ thunk_"#fcnToThunk":\n\
movi a15, stack_thunk_top /* Load A1(SP) with thunk stack */\n\
l32i.n a1, a15, 0\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\
l32i.n a1, a15, 0\n\
l32i.n a15, a1, 8 /* Restore the saved registers */\n\

View File

@ -22,6 +22,7 @@
#include <Arduino.h>
#include <Stream.h>
#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
@ -33,6 +34,8 @@ int Stream::timedRead() {
c = read();
if(c >= 0)
return c;
if(_timeout == 0)
return -1;
yield();
} while(millis() - _startMillis < _timeout);
return -1; // -1 indicates timeout
@ -46,6 +49,8 @@ int Stream::timedPeek() {
c = peek();
if(c >= 0)
return c;
if(_timeout == 0)
return -1;
yield();
} while(millis() - _startMillis < _timeout);
return -1; // -1 indicates timeout
@ -169,7 +174,7 @@ float Stream::parseFloat(char skipChar) {
boolean isFraction = false;
long value = 0;
int c;
float fraction = 1.0;
float fraction = 1.0f;
c = peekNextDigit();
// ignore non numeric leading characters
@ -186,7 +191,7 @@ float Stream::parseFloat(char skipChar) {
else if(c >= '0' && c <= '9') { // is c a digit?
value = value * 10 + c - '0';
if(isFraction)
fraction *= 0.1;
fraction *= 0.1f;
}
read(); // consume the character we got with peek
c = timedPeek();
@ -206,6 +211,8 @@ float Stream::parseFloat(char skipChar) {
// the buffer is NOT null terminated.
//
size_t Stream::readBytes(char *buffer, size_t length) {
IAMSLOW();
size_t count = 0;
while(count < length) {
int c = timedRead();
@ -255,3 +262,19 @@ String Stream::readStringUntil(char terminator) {
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;
}

View File

@ -22,10 +22,13 @@
#ifndef Stream_h
#define Stream_h
#include <debug.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(skipChar) parseInt(skipchar)
@ -35,9 +38,18 @@
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 {
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
int timedRead(); // private method to read 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 peek() = 0;
Stream() {
_timeout = 1000;
}
Stream() {}
// parsing methods
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(uint8_t *target) {
@ -104,12 +115,114 @@ class Stream: public Print {
virtual String readString();
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:
long parseInt(char skipChar); // as above but the given skipChar is ignored
// as above but the given skipChar is ignored
size_t sendGeneric (Print* to,
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
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

250
cores/esp8266/StreamDev.h Normal file
View 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

View 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

View File

@ -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() {
}

View File

@ -1,39 +1,285 @@
/**
StreamString.h
Copyright (c) 2015 Markus Sattler. All rights reserved.
This file is part of the esp8266 core for Arduino environment.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef STREAMSTRING_H_
#define STREAMSTRING_H_
class StreamString: public Stream, public String {
public:
size_t write(const uint8_t *buffer, size_t size) override;
size_t write(uint8_t data) override;
int available() override;
int read() override;
int peek() override;
void flush() override;
};
#endif /* STREAMSTRING_H_ */
/**
StreamString.h
Copyright (c) 2020 D. Gauchard. 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 __STREAMSTRING_H
#define __STREAMSTRING_H
#include <limits>
#include "WString.h"
///////////////////////////////////////////////////////////////
// S2Stream points to a String and makes it a Stream
// (it is also the helper for StreamString)
class S2Stream: public Stream
{
public:
S2Stream(String& string, int peekPointer = -1):
string(&string), peekPointer(peekPointer)
{
}
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
};
// 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
View 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

View File

@ -23,24 +23,27 @@
#include "Arduino.h"
#include "core_esp8266_waveform.h"
#include "user_interface.h"
// Which pins have a tone running on them?
static uint32_t _toneMap = 0;
static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, unsigned long duration) {
static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) {
if (_pin > 16) {
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);
high = std::max(high, (uint32_t)100);
low = std::max(low, (uint32_t)100);
high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency,
low = std::max(low, (uint32_t)microsecondsToClockCycles(25)); // (25us high + 25us low period = 20KHz)
if (startWaveform(_pin, high, low, (uint32_t) duration * 1000)) {
_toneMap |= 1 << _pin;
}
duration = microsecondsToClockCycles(duration * 1000UL);
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) {
noTone(_pin);
} else {
uint32_t period = 1000000L / frequency;
uint32_t period = microsecondsToClockCycles(1000000UL) / frequency;
uint32_t high = period / 2;
uint32_t low = period - high;
_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
noTone(_pin);
} else {
double period = 1000000.0 / frequency;
double period = (double)microsecondsToClockCycles(1000000UL) / frequency;
uint32_t high = (uint32_t)((period / 2.0) + 0.5);
uint32_t low = (uint32_t)(period + 0.5) - high;
_startTone(_pin, high, low, duration);
@ -82,6 +85,5 @@ void noTone(uint8_t _pin) {
return;
}
stopWaveform(_pin);
_toneMap &= ~(1 << _pin);
digitalWrite(_pin, 0);
}

View 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;
}
}
}

View 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

View File

@ -83,14 +83,14 @@ class UDP: public Stream {
// Return the port of the host who sent the current incoming packet
virtual uint16_t remotePort() =0;
protected:
uint8_t* rawIPAddress(IPAddress& addr) {
return addr.raw_address();
}
#if LWIP_VERSION_MAJOR != 1
const uint8_t* rawIPAddress(const IPAddress& addr) {
return addr.raw_address();
}
#endif
};
#endif

View File

@ -1,9 +1,8 @@
#include "Updater.h"
#include "Arduino.h"
#include "eboot_command.h"
#include <interrupts.h>
#include <esp8266_peri.h>
#include <PolledTimeout.h>
#include "StackThunk.h"
//#define DEBUG_UPDATER Serial
@ -13,10 +12,10 @@
#endif
#if ARDUINO_SIGNING
#include "../../libraries/ESP8266WiFi/src/BearSSLHelpers.h"
static BearSSL::PublicKey signPubKey(signing_pubkey);
static BearSSL::HashSHA256 hash;
static BearSSL::SigningVerifier sign(&signPubKey);
namespace esp8266 {
extern UpdaterHashClass& updaterSigningHash;
extern UpdaterVerifyClass& updaterSigningVerifier;
}
#endif
extern "C" {
@ -25,23 +24,21 @@ extern "C" {
#include "user_interface.h"
}
extern "C" uint32_t _SPIFFS_start;
extern "C" uint32_t _FS_start;
extern "C" uint32_t _FS_end;
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
installSignature(&hash, &sign);
installSignature(&esp8266::updaterSigningHash, &esp8266::updaterSigningVerifier);
stack_thunk_add_ref();
#endif
}
UpdaterClass::~UpdaterClass()
{
#if ARDUINO_SIGNING
stack_thunk_del_ref();
#endif
}
@ -88,8 +85,8 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
}
#ifdef DEBUG_UPDATER
if (command == U_SPIFFS) {
DEBUG_UPDATER.println(F("[begin] Update SPIFFS."));
if (command == U_FS) {
DEBUG_UPDATER.println(F("[begin] Update Filesystem."));
}
#endif
@ -105,18 +102,24 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
_reset();
clearError(); // _error = 0
_target_md5 = emptyString;
_md5 = MD5Builder();
#ifndef HOST_MOCK
wifi_set_sleep_type(NONE_SLEEP_T);
#endif
//address where we will start writing the update
uintptr_t updateStartAddress = 0;
//size of current sketch rounded to a sector
size_t currentSketchSize = (ESP.getSketchSize() + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
//size of the update rounded to a sector
size_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
if (command == U_FLASH) {
//size of current sketch rounded to a sector
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_t roundedSize = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
//address where we will start writing the update
uintptr_t updateEndAddress = (uintptr_t)&_FS_start - 0x40200000;
updateStartAddress = (updateEndAddress > roundedSize)? (updateEndAddress - roundedSize) : 0;
#ifdef DEBUG_UPDATER
@ -131,8 +134,25 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
return false;
}
}
else if (command == U_SPIFFS) {
updateStartAddress = (uintptr_t)&_SPIFFS_start - 0x40200000;
else if (command == U_FS) {
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 {
// unknown command
@ -180,14 +200,19 @@ bool UpdaterClass::end(bool evenIfRemaining){
#ifdef DEBUG_UPDATER
DEBUG_UPDATER.println(F("no update"));
#endif
_reset();
return false;
}
// Updating w/o any data is an error we detect here
if (!progress()) {
_setError(UPDATE_ERROR_NO_DATA);
}
if(hasError() || (!isFinished() && !evenIfRemaining)){
#ifdef DEBUG_UPDATER
DEBUG_UPDATER.printf_P(PSTR("premature end: res:%u, pos:%zu/%zu\n"), getError(), progress(), _size);
#endif
_reset();
return false;
}
@ -217,7 +242,7 @@ bool UpdaterClass::end(bool evenIfRemaining){
DEBUG_UPDATER.printf_P(PSTR("[Updater] Adjusted binsize: %d\n"), binSize);
#endif
// 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)) {
ESP.flashRead(_startAddress + i, (uint32_t *)buff, sizeof(buff));
size_t read = std::min((int)sizeof(buff), binSize - i);
@ -236,7 +261,7 @@ bool UpdaterClass::end(bool evenIfRemaining){
_reset();
return false;
}
ESP.flashRead(_startAddress + binSize, (uint32_t *)sig, sigLen);
ESP.flashRead(_startAddress + binSize, sig, sigLen);
#ifdef DEBUG_UPDATER
DEBUG_UPDATER.printf_P(PSTR("[Updater] Received Signature:"));
for (size_t i=0; i<sigLen; i++) {
@ -245,10 +270,14 @@ bool UpdaterClass::end(bool evenIfRemaining){
DEBUG_UPDATER.printf("\n");
#endif
if (!_verify->verify(_hash, (void *)sig, sigLen)) {
free(sig);
_setError(UPDATE_ERROR_SIGN);
_reset();
return false;
}
free(sig);
_size = binSize; // Adjust size to remove signature, not part of bin payload
#ifdef DEBUG_UPDATER
DEBUG_UPDATER.printf_P(PSTR("[Updater] Signature matches\n"));
#endif
@ -256,7 +285,6 @@ bool UpdaterClass::end(bool evenIfRemaining){
_md5.calculate();
if (strcasecmp(_target_md5.c_str(), _md5.toString().c_str())) {
_setError(UPDATE_ERROR_MD5);
_reset();
return false;
}
#ifdef DEBUG_UPDATER
@ -279,9 +307,20 @@ bool UpdaterClass::end(bool evenIfRemaining){
#ifdef DEBUG_UPDATER
DEBUG_UPDATER.printf_P(PSTR("Staged: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
#endif
}
else if (_command == U_SPIFFS) {
DEBUG_UPDATER.printf_P(PSTR("SPIFFS: address:0x%08X, size:0x%08zX\n"), _startAddress, _size);
else if (_command == U_FS) {
#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
}
@ -305,7 +344,8 @@ bool UpdaterClass::_writeBuffer(){
bool modifyFlashMode = false;
FlashMode_t flashMode = 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();
#ifdef DEBUG_UPDATER
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(!_async) yield();
writeResult = ESP.flashWrite(_currentAddress, (uint32_t*) _buffer, _bufferLen);
writeResult = ESP.flashWrite(_currentAddress, _buffer, _bufferLen);
} else { // if erase was unsuccessful
_currentAddress = (_startAddress + _size);
_setError(UPDATE_ERROR_ERASE);
@ -353,9 +393,7 @@ size_t UpdaterClass::write(uint8_t *data, size_t len) {
if(hasError() || !isRunning())
return 0;
if(len > remaining()){
//len = remaining();
//fail instead
if(progress() + _bufferLen + len > _size) {
_setError(UPDATE_ERROR_SPACE);
return 0;
}
@ -387,14 +425,14 @@ size_t UpdaterClass::write(uint8_t *data, size_t len) {
bool UpdaterClass::_verifyHeader(uint8_t data) {
if(_command == U_FLASH) {
// check for valid first magic byte (is always 0xE9)
if(data != 0xE9) {
if ((data != 0xE9) && (data != 0x1f)) {
_currentAddress = (_startAddress + _size);
_setError(UPDATE_ERROR_MAGIC_BYTE);
return false;
}
return true;
} else if(_command == U_SPIFFS) {
// no check of SPIFFS possible with first byte.
} else if(_command == U_FS) {
// no check of FS possible with first byte.
return true;
}
return false;
@ -403,7 +441,7 @@ bool UpdaterClass::_verifyHeader(uint8_t data) {
bool UpdaterClass::_verifyEnd() {
if(_command == U_FLASH) {
uint8_t buf[4];
uint8_t buf[4] __attribute__((aligned(4)));
if(!ESP.flashRead(_startAddress, (uint32_t *) &buf[0], 4)) {
_currentAddress = (_startAddress);
_setError(UPDATE_ERROR_READ);
@ -411,7 +449,12 @@ bool UpdaterClass::_verifyEnd() {
}
// 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);
_setError(UPDATE_ERROR_MAGIC_BYTE);
return false;
@ -427,8 +470,8 @@ bool UpdaterClass::_verifyEnd() {
}
return true;
} else if(_command == U_SPIFFS) {
// SPIFFS is already over written checks make no sense any more.
} else if(_command == U_FS) {
// FS is already over written checks make no sense any more.
return true;
}
return false;
@ -498,6 +541,7 @@ void UpdaterClass::_setError(int error){
#ifdef DEBUG_UPDATER
printError(DEBUG_UPDATER);
#endif
_reset(); // Any error condition invalidates the entire update, so clear partial status
}
void UpdaterClass::printError(Print &out){
@ -516,6 +560,8 @@ void UpdaterClass::printError(Print &out){
out.println(F("Bad Size Given"));
} else if(_error == UPDATE_ERROR_STREAM){
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){
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){

View File

@ -19,9 +19,10 @@
#define UPDATE_ERROR_MAGIC_BYTE (10)
#define UPDATE_ERROR_BOOTSTRAP (11)
#define UPDATE_ERROR_SIGN (12)
#define UPDATE_ERROR_NO_DATA (13)
#define U_FLASH 0
#define U_SPIFFS 100
#define U_FS 100
#define U_AUTH 200
#ifdef DEBUG_ESP_UPDATER
@ -38,6 +39,7 @@ class UpdaterHashClass {
virtual void end() = 0;
virtual int len() = 0;
virtual const void *hash() = 0;
virtual const unsigned char *oid() = 0;
};
// Abstract class to implement a signature verifier
@ -52,6 +54,7 @@ class UpdaterClass {
typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
UpdaterClass();
~UpdaterClass();
/* Optionally add a cryptographic signature verification hash and method */
void installSignature(UpdaterHashClass *hash, UpdaterVerifyClass *verify) { _hash = hash; _verify = verify; }
@ -180,27 +183,27 @@ class UpdaterClass {
void _setError(int error);
bool _async;
uint8_t _error;
uint8_t *_buffer;
size_t _bufferLen; // amount of data written into _buffer
size_t _bufferSize; // total size of _buffer
size_t _size;
uint32_t _startAddress;
uint32_t _currentAddress;
uint32_t _command;
bool _async = false;
uint8_t _error = 0;
uint8_t *_buffer = nullptr;
size_t _bufferLen = 0; // amount of data written into _buffer
size_t _bufferSize = 0; // total size of _buffer
size_t _size = 0;
uint32_t _startAddress = 0;
uint32_t _currentAddress = 0;
uint32_t _command = U_FLASH;
String _target_md5;
MD5Builder _md5;
int _ledPin;
int _ledPin = -1;
uint8_t _ledOn;
// Optional signed binary verification
UpdaterHashClass *_hash;
UpdaterVerifyClass *_verify;
UpdaterHashClass *_hash = nullptr;
UpdaterVerifyClass *_verify = nullptr;
// Optional progress callback function
THandlerFunction_Progress _progress_callback;
THandlerFunction_Progress _progress_callback = nullptr;
};
extern UpdaterClass Update;

View File

@ -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 divisor = (in_max - in_min);
if(divisor == 0){
return -1; //AVR returns -1, SAM returns 0
}
return (x - in_min) * (out_max - out_min) / divisor + out_min;
const long dividend = out_max - out_min;
const long divisor = in_max - in_min;
const long delta = x - in_min;
return (delta * dividend + (divisor / 2)) / divisor + out_min;
}
unsigned int makeWord(unsigned int w) {

File diff suppressed because it is too large Load Diff

View File

@ -23,14 +23,15 @@
#define String_class_h
#ifdef __cplusplus
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pgmspace.h>
// An inherited class for holding the result of a concatenation. These
// result objects are assumed to be writable by subsequent concatenations.
class StringSumHelper;
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <cctype>
#include <utility>
#include <type_traits>
// an abstract class used as a means to proide a unique pointer type
// but really has no body
@ -38,6 +39,10 @@ class __FlashStringHelper;
#define FPSTR(pstr_pointer) (reinterpret_cast<const __FlashStringHelper *>(pstr_pointer))
#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
class String {
// use a function pointer to allow for "if (s)" without the
@ -53,46 +58,61 @@ class String {
// 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
// be false).
String(const char *cstr = "");
String() __attribute__((always_inline)) { // See init()
init();
}
String(const char *cstr);
String(const String &str);
String(const __FlashStringHelper *str);
#ifdef __GXX_EXPERIMENTAL_CXX0X__
String(String &&rval);
String(StringSumHelper &&rval);
#endif
explicit String(char c);
String(String &&rval) noexcept;
explicit String(char c) {
sso.buff[0] = c;
sso.buff[1] = 0;
sso.len = 1;
sso.isHeap = 0;
}
explicit String(unsigned char, unsigned char base = 10);
explicit String(int, unsigned char base = 10);
explicit String(unsigned int, unsigned char base = 10);
explicit String(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(double, unsigned char decimalPlaces = 2);
~String(void);
~String() {
invalidate();
}
// memory management
// return true on success, false on failure (in which case, the string
// is left unchanged). reserve(0), if successful, will validate an
// invalid string (i.e., "if (s)" will be true afterwards)
unsigned char reserve(unsigned int size);
inline unsigned int length(void) const {
if(buffer()) {
return len();
} else {
return 0;
}
unsigned int length(void) const {
return buffer() ? len() : 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
// invalid, or if the memory allocation fails, the string will be
// marked as invalid ("if (s)" will be false).
String & operator =(const String &rhs);
String & operator =(const char *cstr);
String & operator = (const __FlashStringHelper *str);
#ifdef __GXX_EXPERIMENTAL_CXX0X__
String & operator =(String &&rval);
String & operator =(StringSumHelper &&rval);
#endif
String &operator =(const String &rhs);
String &operator =(const char *cstr);
String &operator =(const __FlashStringHelper *str);
String &operator =(String &&rval) noexcept;
String &operator =(char c) {
char buffer[2] { c, '\0' };
*this = buffer;
return *this;
}
// concatenate (works w/ built-in types)
@ -107,68 +127,20 @@ class String {
unsigned char concat(unsigned int num);
unsigned char concat(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(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
// 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);
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")
operator StringIfHelperType() const {
@ -196,29 +168,45 @@ class String {
unsigned char equalsIgnoreCase(const String &s) const;
unsigned char equalsConstantTime(const String &s) 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 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
char charAt(unsigned int index) const;
char charAt(unsigned int index) const {
return operator [](index);
}
void setCharAt(unsigned int index, char c);
char operator [](unsigned int index) const;
char& operator [](unsigned int index);
char &operator [](unsigned int index);
void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index = 0) const;
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const {
getBytes((unsigned char *) buf, bufsize, index);
}
const char* c_str() const { return buffer(); }
char* begin() { return wbuffer(); }
char* end() { return wbuffer() + length(); }
const char* begin() const { return c_str(); }
const char* end() const { return c_str() + length(); }
const char *c_str() const { return buffer(); }
char *begin() { return wbuffer(); }
char *end() { return wbuffer() + length(); }
const char *begin() const { return c_str(); }
const char *end() const { return c_str() + length(); }
// search
int indexOf(char ch) const;
int indexOf(char ch, unsigned int fromIndex) const;
int indexOf(const String &str) const;
int indexOf(const String &str, unsigned int fromIndex) const;
int indexOf(char ch, unsigned int fromIndex = 0) const;
int indexOf(const char *str, unsigned int fromIndex = 0) const;
int indexOf(const __FlashStringHelper *str, unsigned int fromIndex = 0) 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, unsigned int fromIndex) const;
int lastIndexOf(const String &str) const;
@ -226,14 +214,29 @@ class String {
String substring(unsigned int beginIndex) const {
return substring(beginIndex, len());
}
;
String substring(unsigned int beginIndex, unsigned int endIndex) const;
// modification
void replace(char find, char replace);
void replace(const String& find, const String& replace);
void remove(unsigned int index);
void remove(unsigned int index, unsigned int count);
void replace(const String &find, const String &replace);
void replace(const char *find, const String &replace) {
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 toUpperCase(void);
void trim(void);
@ -241,84 +244,149 @@ class String {
// parsing/conversion
long toInt(void) const;
float toFloat(void) const;
double toDouble(void) const;
double toDouble(void) const;
protected:
// Contains the string info when we're not in SSO mode
struct _ptr {
struct _ptr {
char * buff;
uint16_t cap;
uint16_t len;
};
// SSO is handled by checking the last byte of sso_buff.
// 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)
// This allows strings up up to 12 (11 + \0 termination) without any extra space.
enum { SSOSIZE = sizeof(struct _ptr) + 4 }; // Characters to allocate space for SSO, must be 12 or more
enum { CAPACITY_MAX = 65535 }; // If size of capacity changed, be sure to update this enum
// This allows strings up up to 11 (10 + \0 termination) without any extra space.
enum { SSOSIZE = sizeof(struct _ptr) + 4 - 1 }; // Characters to allocate space for SSO, must be 12 or more
struct _sso {
char buff[SSOSIZE];
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
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 {
struct _ptr ptr;
char sso_buf[SSOSIZE];
struct _sso sso;
};
// Accessor functions
inline bool sso() const { return sso_buf[SSOSIZE - 1] == 0; }
inline unsigned int len() const { return sso() ? strlen(sso_buf) : ptr.len; }
inline unsigned int capacity() const { return sso() ? SSOSIZE - 1 : ptr.cap; }
inline void setSSO(bool sso) { sso_buf[SSOSIZE - 1] = sso ? 0x00 : 0xff; }
inline void setLen(int len) { if (!sso()) ptr.len = len; }
inline void setCapacity(int cap) { if (!sso()) ptr.cap = cap; }
inline void setBuffer(char *buff) { if (!sso()) ptr.buff = buff; }
bool isSSO() const { return !sso.isHeap; }
unsigned int len() const { return isSSO() ? sso.len : ptr.len; }
unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL
void setSSO(bool set) { sso.isHeap = !set; }
void setLen(int len) {
if (isSSO()) {
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
inline const char *buffer() const { return (const char *)(sso() ? sso_buf : ptr.buff); }
inline char *wbuffer() const { return sso() ? const_cast<char *>(sso_buf) : ptr.buff; } // Writable version of buffer
const char *buffer() const { return wbuffer(); }
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:
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);
unsigned char changeBuffer(unsigned int maxStrLen);
unsigned char concat(const char *cstr, unsigned int length);
// copy and move
String & copy(const char *cstr, unsigned int length);
String & copy(const __FlashStringHelper *pstr, unsigned int length);
#ifdef __GXX_EXPERIMENTAL_CXX0X__
void move(String &rhs);
#endif
// copy or insert at a specific position
String &copy(const char *cstr, unsigned int length);
String &copy(const __FlashStringHelper *pstr, unsigned int length);
String &insert(size_t position, char);
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 {
public:
StringSumHelper(const String &s) :
String(s) {
}
StringSumHelper(const char *p) :
String(p) {
}
StringSumHelper(char c) :
String(c) {
}
StringSumHelper(unsigned char num) :
String(num) {
}
StringSumHelper(int num) :
String(num) {
}
StringSumHelper(unsigned int num) :
String(num) {
}
StringSumHelper(long num) :
String(num) {
}
StringSumHelper(unsigned long num) :
String(num) {
}
StringSumHelper(float num) :
String(num) {
}
StringSumHelper(double num) :
String(num) {
}
};
// concatenation (note that it's done using non-method operators to handle both possible type refs)
inline String operator +(const String &lhs, const String &rhs) {
String res;
res.reserve(lhs.length() + rhs.length());
res += lhs;
res += rhs;
return res;
}
inline String operator +(String &&lhs, const String &rhs) {
lhs += rhs;
return std::move(lhs);
}
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;
}
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);
}
// `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));
}
// 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));
}
inline String operator +(const __FlashStringHelper *lhs, const String &rhs) {
return reinterpret_cast<const char*>(lhs) + rhs;
}
inline String operator +(const __FlashStringHelper *lhs, String &&rhs) {
return std::move(rhs.insert(0, lhs));
}
extern const String emptyString;

View File

@ -18,7 +18,6 @@
#include <stdlib.h>
#include <assert.h>
#include <debug.h>
#include <Arduino.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_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)
{
panic();

View 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

View File

@ -0,0 +1 @@
#include <stdlib_noniso.h>

View File

@ -1,71 +1,76 @@
/**
* base64.cpp
*
* Created on: 09.12.2015
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the ESP8266 core for Arduino.
*
* 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"
extern "C" {
#include "libb64/cdecode.h"
#include "libb64/cencode.h"
}
#include "base64.h"
/**
* convert input data to base64
* @param data uint8_t *
* @param length size_t
* @return String
*/
String base64::encode(uint8_t * data, size_t length, bool doNewLines) {
// base64 needs more size then the source data, use cencode.h macros
size_t size = ((doNewLines ? base64_encode_expected_len(length)
: base64_encode_expected_len_nonewlines(length)) + 1);
char * buffer = (char *) malloc(size);
if(buffer) {
base64_encodestate _state;
if(doNewLines)
{
base64_init_encodestate(&_state);
}
else
{
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);
free(buffer);
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);
}
/**
* base64.cpp
*
* Created on: 09.12.2015
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the ESP8266 core for Arduino.
*
* 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"
extern "C" {
#include "libb64/cencode.h"
}
#include "base64.h"
/**
* convert input data to base64
* @param data const uint8_t *
* @param length size_t
* @return String
*/
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
size_t size = ((doNewLines ? base64_encode_expected_len( length )
: base64_encode_expected_len_nonewlines( length )) + 1);
if (base64.reserve(size))
{
base64_encodestate _state;
if (doNewLines)
{
base64_init_encodestate(&_state);
}
else
{
base64_init_encodestate_nonewlines(&_state);
}
constexpr size_t BUFSIZE = 48;
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;
}

View File

@ -1,39 +1,57 @@
/**
* base64.h
*
* Created on: 09.12.2015
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the ESP8266 core for Arduino.
*
* 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 CORE_BASE64_H_
#define CORE_BASE64_H_
class base64 {
public:
// NOTE: The default behaviour of backend (lib64)
// is to add a newline every 72 (encoded) characters output.
// This may 'break' longer uris and json variables
static String encode(uint8_t * data, size_t length, bool doNewLines = true);
static String encode(String text, bool doNewLines = true);
private:
};
#endif /* CORE_BASE64_H_ */
/**
* base64.h
*
* Created on: 09.12.2015
*
* Copyright (c) 2015 Markus Sattler. All rights reserved.
* This file is part of the ESP8266 core for Arduino.
*
* 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 CORE_BASE64_H_
#define CORE_BASE64_H_
#include <WString.h>
class base64
{
public:
// NOTE: The default behaviour of backend (lib64)
// is to add a newline every 72 (encoded) characters output.
// This may 'break' longer uris and json variables
static String encode(const uint8_t * data, size_t length, bool doNewLines);
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:
};
#endif /* CORE_BASE64_H_ */

View File

@ -18,6 +18,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <new> // std::nothrow
#include "cbuf.h"
#include "c_types.h"
@ -43,7 +44,7 @@ size_t cbuf::resize(size_t newSize) {
return _size;
}
char *newbuf = new char[newSize];
char *newbuf = new (std::nothrow) char[newSize];
char *oldbuf = _buf;
if(!newbuf) {
@ -66,7 +67,7 @@ size_t cbuf::resize(size_t newSize) {
return _size;
}
size_t ICACHE_RAM_ATTR cbuf::available() const {
size_t IRAM_ATTR cbuf::available() const {
if(_end >= _begin) {
return _end - _begin;
}
@ -107,7 +108,7 @@ size_t cbuf::peek(char *dst, size_t size) {
return size_read;
}
int ICACHE_RAM_ATTR cbuf::read() {
int IRAM_ATTR cbuf::read() {
if(empty())
return -1;
@ -132,7 +133,7 @@ size_t cbuf::read(char* dst, size_t size) {
return size_read;
}
size_t ICACHE_RAM_ATTR cbuf::write(char c) {
size_t IRAM_ATTR cbuf::write(char c) {
if(full())
return 0;

View File

@ -18,7 +18,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
.text
.section .irom0.text
.align 4
.literal_position
.global cont_yield
@ -84,7 +84,7 @@ cont_wrapper:
////////////////////////////////////////////////////
.text
.section .irom0.text
.align 4
.literal_position
.global cont_run

View File

@ -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;
return 0;
@ -62,7 +62,7 @@ int cont_get_free_stack(cont_t* cont) {
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() &&
cont->pc_ret != 0 && cont->pc_yield == 0;
}

View File

@ -27,7 +27,7 @@ extern "C" void call_user_start();
/* this is the default NONOS-SDK user's heap location */
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;

View File

@ -21,36 +21,15 @@
#include <stddef.h>
#include <stdbool.h>
#include "coredecls.h"
#include "eboot_command.h"
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)
{
return crc_update(0xffffffff, (const uint8_t*) cmd,
offsetof(struct eboot_command, crc32));
return crc32((const uint8_t*) cmd, offsetof(struct eboot_command, crc32));
}
int eboot_command_read(struct eboot_command* cmd)

View 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

View File

@ -1,39 +1,129 @@
/*
core_esp8266_features.h - list of features integrated in to ESP8266 core
Copyright (c) 2014 Ivan Grokhotkov. 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 CORE_ESP8266_FEATURES_H
#define CORE_ESP8266_FEATURES_H
#define CORE_HAS_LIBB64
#define CORE_HAS_BASE64_CLASS
#define CORE_HAS_CXA_GUARD
#define CORE_HAS_UMM
#define WIFI_HAS_EVENT_CALLBACK
#endif
/*
core_esp8266_features.h - list of features integrated in to ESP8266 core
Copyright (c) 2014 Ivan Grokhotkov. 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 CORE_ESP8266_FEATURES_H
#define CORE_ESP8266_FEATURES_H
#define CORE_HAS_LIBB64
#define CORE_HAS_BASE64_CLASS
#define CORE_HAS_CXA_GUARD
#define CORE_HAS_UMM
#define WIFI_HAS_EVENT_CALLBACK
#include <stdlib.h> // malloc()
#include <stddef.h> // size_t
#include <stdint.h>
#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,
//
#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

View 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

View File

@ -24,7 +24,7 @@
#include "osapi.h"
#include "ets_sys.h"
#include "i2s_reg.h"
#include "i2s.h"
#include "core_esp8266_i2s.h"
extern "C" {
@ -61,9 +61,10 @@ typedef struct i2s_state {
uint32_t * curr_slc_buf; // Current buffer for writing
uint32_t curr_slc_buf_pos; // Position in the current buffer
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
// function, use it to set flags and process later.
bool driveClocks;
} i2s_state_t;
// 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
static uint32_t _i2s_sample_rate;
static int _i2s_bits = 16;
// IOs used for I2S. Not defined in i2s.h, unfortunately.
// 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_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) {
if (!ch) {
return false;
@ -129,7 +139,7 @@ uint16_t i2s_rx_available(){
}
// 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;
uint32_t *item = ch->slc_queue[0];
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
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
for (int i=0, dest=0; i < ch->slc_queue_len; i++) {
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();
uint32_t slc_intr_status = SLCIS;
SLCIC = 0xFFFFFFFF;
@ -184,11 +194,11 @@ static void ICACHE_RAM_ATTR i2s_slc_isr(void) {
}
void i2s_set_callback(void (*callback) (void)) {
tx->callback = callback;
if (tx) tx->callback = callback;
}
void i2s_rx_set_callback(void (*callback) (void)) {
rx->callback = callback;
if (rx) rx->callback = callback;
}
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
// 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;
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;
}
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) {
if (!rx) {
@ -440,7 +450,7 @@ void i2s_set_rate(uint32_t rate) { //Rate in HZ
}
_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;
uint8_t sbd_div_best=1;
@ -464,21 +474,42 @@ void i2s_set_dividers(uint8_t div1, uint8_t div2) {
div1 &= I2SBDM;
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
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)
// I2SMR = MSB recv/xmit first
// 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
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(){
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) {
return i2s_rxtxdrive_begin(enableRx, enableTx, true, true);
}
bool i2s_rxtxdrive_begin(bool enableRx, bool enableTx, bool driveRxClocks, bool driveTxClocks) {
if (tx || rx) {
i2s_end(); // Stop and free any ongoing stuff
}
@ -489,9 +520,12 @@ bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
// Nothing to clean up yet
return false; // OOM Error!
}
pinMode(I2SO_WS, FUNCTION_1);
tx->driveClocks = driveTxClocks;
pinMode(I2SO_DATA, FUNCTION_1);
pinMode(I2SO_BCK, FUNCTION_1);
if (driveTxClocks) {
pinMode(I2SO_WS, FUNCTION_1);
pinMode(I2SO_BCK, FUNCTION_1);
}
}
if (enableRx) {
rx = (i2s_state_t*)calloc(1, sizeof(*rx));
@ -499,15 +533,17 @@ bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
i2s_end(); // Clean up any TX or pin changes
return false; // OOM error!
}
pinMode(I2SI_WS, OUTPUT);
pinMode(I2SI_BCK, OUTPUT);
rx->driveClocks = driveRxClocks;
pinMode(I2SI_DATA, INPUT);
if (driveRxClocks) {
pinMode(I2SI_WS, OUTPUT);
pinMode(I2SI_BCK, OUTPUT);
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_MTDI_U, FUNC_I2SI_DATA);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTCK_U, FUNC_I2SI_BCK);
PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTMS_U, FUNC_I2SI_WS);
}
_i2s_sample_rate = 0;
if (!i2s_slc_begin()) {
// OOM in SLC memory allocations, tear it all down and abort!
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
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
// I2STXCMM, I2SRXCMM=0 => Dual channel mode
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) {
// Need to prime the # of samples to receive in the engine
@ -560,15 +605,19 @@ void i2s_end() {
if (tx) {
pinMode(I2SO_DATA, INPUT);
pinMode(I2SO_BCK, INPUT);
pinMode(I2SO_WS, INPUT);
if (tx->driveClocks) {
pinMode(I2SO_BCK, INPUT);
pinMode(I2SO_WS, INPUT);
}
free(tx);
tx = NULL;
}
if (rx) {
pinMode(I2SI_DATA, INPUT);
pinMode(I2SI_BCK, INPUT);
pinMode(I2SI_WS, INPUT);
if (rx->driveClocks) {
pinMode(I2SI_BCK, INPUT);
pinMode(I2SI_WS, INPUT);
}
free(rx);
rx = NULL;
}

View 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

View File

@ -34,10 +34,14 @@ extern "C" {
}
#include <core_version.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_QUEUE_SIZE 1
#define OPTIMISTIC_YIELD_TIME_US 16000
extern "C" void call_user_start();
extern void loop();
@ -58,7 +62,14 @@ cont_t* g_pcont __attribute__((section(".noinit")));
static os_event_t s_loop_queue[LOOP_QUEUE_SIZE];
/* 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" {
@ -75,29 +86,52 @@ void initVariant() __attribute__((weak));
void initVariant() {
}
void preloop_update_frequency() __attribute__((weak));
void preloop_update_frequency() {
extern "C" void __preloop_update_frequency() {
#if defined(F_CPU) && (F_CPU == 160000000L)
REG_SET_BIT(0x3ff00014, BIT(0));
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
}
extern "C" void preloop_update_frequency() __attribute__((weak, alias("__preloop_update_frequency")));
extern "C" void esp_yield() {
if (cont_can_yield(g_pcont)) {
extern "C" bool can_yield() {
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);
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);
}
extern "C" void __yield() {
if (cont_can_yield(g_pcont)) {
if (can_yield()) {
esp_schedule();
esp_yield();
esp_yield_within_cont();
}
else {
panic();
@ -107,13 +141,55 @@ extern "C" void __yield() {
extern "C" void yield(void) __attribute__ ((weak, alias("__yield")));
extern "C" void optimistic_yield(uint32_t interval_us) {
if (cont_can_yield(g_pcont) &&
(system_get_time() - s_micros_at_task_start) > interval_us)
const uint32_t intvl_cycles = interval_us *
#if defined(F_CPU)
clockCyclesPerMicrosecond();
#else
ESP.getCpuFreqMHz();
#endif
if ((ESP.getCycleCount() - s_cycles_at_yield_start) > intvl_cycles &&
can_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 bool setup_done = false;
preloop_update_frequency();
@ -122,14 +198,19 @@ static void loop_wrapper() {
setup_done = true;
}
loop();
run_scheduled_functions();
loop_end();
if (serialEventRun) {
serialEventRun();
}
esp_schedule();
}
static void loop_task(os_event_t *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);
ESP.setDramHeap();
if (cont_check(g_pcont) != 0) {
panic();
}
@ -156,7 +237,7 @@ extern void __unhandled_exception(const char *str);
static void __unhandled_exception_cpp()
{
#ifndef __EXCEPTIONS
abort();
abort();
#else
static bool terminating;
if (terminating)
@ -181,6 +262,7 @@ void init_done() {
std::set_terminate(__unhandled_exception_cpp);
do_global_ctors();
esp_schedule();
ESP.setDramHeap();
}
/* 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 ICACHE_RAM_ATTR app_entry_redefinable(void)
extern "C" void app_entry_redefinable(void) __attribute__((weak));
extern "C" void app_entry_redefinable(void)
{
/* Allocate continuation context on this SYS stack,
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_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 ICACHE_RAM_ATTR app_entry (void)
extern "C" void app_entry (void)
{
umm_init();
return app_entry_custom();
}
@ -263,8 +345,21 @@ extern "C" void user_init(void) {
initVariant();
experimental::initFlashQuirks(); // Chip specific flash init.
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.
ets_task(loop_task,

View 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);
}
}
};

View 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

View File

@ -27,6 +27,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <math.h>
#include <limits>
#include "stdlib_noniso.h"
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
double tenpow = 1.0;
int digitcount = 1;
while (number >= 10.0 * tenpow) {
tenpow *= 10.0;
double nextpow;
while (number >= (nextpow = (10.0 * tenpow))) {
tenpow = nextpow;
digitcount++;
}
// minimal compensation for possible lack of precision (#7087 addition)
number *= 1 + std::numeric_limits<decltype(number)>::epsilon();
number /= tenpow;
fillme -= digitcount;
@ -112,4 +117,36 @@ char * dtostrf(double number, signed char width, unsigned char prec, char *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;
}
};

View File

@ -300,10 +300,10 @@ static const uint8_t ICACHE_FLASH_ATTR phy_init_data[128] =
static bool spoof_init_data = false;
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 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) {
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() {}
};

View File

@ -32,11 +32,10 @@
#include "pgmspace.h"
#include "gdb_hooks.h"
#include "StackThunk.h"
#include "coredecls.h"
extern "C" {
extern void __real_system_restart_local();
// These will be pointers to PROGMEM const strings
static const char* s_panic_file = 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 const char* s_unhandled_exception = NULL;
static uint32_t s_stacksmash_addr = 0;
void abort() __attribute__((noreturn));
static void uart_write_char_d(char c);
static void uart0_write_char_d(char c);
static void uart1_write_char_d(char c);
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:
extern void *umm_last_fail_alloc_addr;
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));
@ -78,35 +91,44 @@ static void ets_printf_P(const char *str, ...) {
vsnprintf(destStr, sizeof(destStr), str, argPtr);
va_end(argPtr);
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() {
register uint32_t sp asm("a1");
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;
memset(&rst_info, 0, sizeof(rst_info));
system_rtc_mem_read(0, &rst_info, sizeof(rst_info));
if (rst_info.reason != REASON_SOFT_WDT_RST &&
rst_info.reason != REASON_EXCEPTION_RST &&
rst_info.reason != REASON_WDT_RST)
if (s_user_reset_reason == REASON_DEFAULT_RST)
{
return;
system_rtc_mem_read(0, &rst_info, sizeof(rst_info));
if (rst_info.reason != REASON_SOFT_WDT_RST &&
rst_info.reason != REASON_EXCEPTION_RST &&
rst_info.reason != REASON_WDT_RST)
{
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((void *)&uart_write_char_d);
ets_install_putc1(&uart_write_char_d);
cut_here();
if (s_panic_line) {
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"));
}
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"),
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) {
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_end = (uint32_t) g_pcont->stack_end;
@ -138,10 +171,10 @@ void __wrap_system_restart_local() {
// (determined empirically, might break)
uint32_t offset = 0;
if (rst_info.reason == REASON_SOFT_WDT_RST) {
offset = 0x1b0;
offset = 0x1a0;
}
else if (rst_info.reason == REASON_EXCEPTION_RST) {
offset = 0x1a0;
offset = 0x190;
}
else if (rst_info.reason == REASON_WDT_RST) {
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
if (umm_last_fail_alloc_addr) {
ets_printf_P(PSTR("\nlast failed alloc call: %08X(%d)\n"), (uint32_t)umm_last_fail_alloc_addr, umm_last_fail_alloc_size);
#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);
#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 );
@ -222,7 +269,13 @@ static void uart1_write_char_d(char c) {
}
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
}
@ -254,4 +307,20 @@ void __panic_func(const char* file, int line, const char* func) {
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

View 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

View File

@ -31,8 +31,9 @@ extern "C" {
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) frame;
if ((T1C & ((1 << TCAR) | (1 << TCIT))) == 0) TEIE &= ~TEIE1;//edge int disable
T1I = 0;
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);
}
void timer1_attachInterrupt(timercallback userFunc) {
void IRAM_ATTR timer1_attachInterrupt(timercallback userFunc) {
timer1_user_cb = userFunc;
ETS_FRC1_INTR_ENABLE();
}
void ICACHE_RAM_ATTR timer1_detachInterrupt() {
void IRAM_ATTR timer1_detachInterrupt() {
timer1_user_cb = 0;
TEIE &= ~TEIE1;//edge int 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);
T1I = 0;
}
void ICACHE_RAM_ATTR timer1_write(uint32_t ticks){
void IRAM_ATTR timer1_write(uint32_t ticks){
T1L = ((ticks)& 0x7FFFFF);
if ((T1C & (1 << TCIT)) == 0) TEIE |= TEIE1;//edge int enable
}
void ICACHE_RAM_ATTR timer1_disable(){
void IRAM_ATTR timer1_disable(){
T1C = 0;
T1I = 0;
}
@ -79,8 +80,9 @@ void ICACHE_RAM_ATTR timer1_disable(){
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) frame;
if (timer0_user_cb) {
// to make ISR compatible to Arduino AVR model where interrupts are disabled
// 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);
}
void timer0_attachInterrupt(timercallback userFunc) {
void IRAM_ATTR timer0_attachInterrupt(timercallback userFunc) {
timer0_user_cb = userFunc;
ETS_CCOMPARE0_ENABLE();
}
void ICACHE_RAM_ATTR timer0_detachInterrupt() {
void IRAM_ATTR timer0_detachInterrupt() {
timer0_user_cb = NULL;
ETS_CCOMPARE0_DISABLE();
}

View 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

View File

@ -1,8 +1,11 @@
#ifdef __cplusplus
extern "C" {
#endif
#include <sys/utmp.h>
extern void install_vm_exception_handler();
#ifdef __cplusplus
}
};
#endif

View File

@ -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
}
};

View File

@ -2,16 +2,17 @@
esp8266_waveform - General purpose waveform generation and control,
supporting outputs on all pins in parallel.
-- Default, PWM locked version --
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.
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 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
always increments once per 80MHz clock. Changes to a waveform are
contiguous and only take effect on the next waveform transition,
@ -19,8 +20,33 @@
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).
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).
----------
-- 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
modify it under the terms of the GNU Lesser General Public
@ -46,24 +72,55 @@
extern "C" {
#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.
// 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.
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.
// Returns true or false on success or failure.
int stopWaveform(uint8_t pin);
// 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
// again before this period. It should therefore use the ESP Cycle Counter
// to determine whether or not to perform an operation.
// Pass in NULL to disable the callback and, if no other waveforms being
// 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)());
// 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
}
#endif

View 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;
}

View 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);
}
};

View File

@ -43,7 +43,7 @@ void delay_end(void* arg) {
esp_schedule();
}
void delay(unsigned long ms) {
void __delay(unsigned long ms) {
if(ms) {
os_timer_setfn(&delay_timer, (os_timer_func_t*) &delay_end, 0);
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) arg;
uint32_t m = system_get_time();
@ -147,7 +149,7 @@ void micros_overflow_tick(void* arg) {
//
// Reference function: corrected millis(), 64-bit arithmetic,
// 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
// ......
@ -161,7 +163,7 @@ void micros_overflow_tick(void* arg) {
#define MAGIC_1E3_wLO 0x4bc6a7f0 // LS part
#define MAGIC_1E3_wHI 0x00418937 // MS part, magic multiplier
unsigned long ICACHE_RAM_ATTR millis()
unsigned long IRAM_ATTR millis()
{
union {
uint64_t q; // Accumulator, 64-bit, little endian
@ -192,18 +194,18 @@ unsigned long ICACHE_RAM_ATTR millis()
} //millis
unsigned long ICACHE_RAM_ATTR micros() {
unsigned long IRAM_ATTR micros() {
return system_get_time();
}
uint64_t ICACHE_RAM_ATTR micros64() {
uint64_t IRAM_ATTR micros64() {
uint32_t low32_us = system_get_time();
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;
return duration64_us;
}
void ICACHE_RAM_ATTR delayMicroseconds(unsigned int us) {
void IRAM_ATTR delayMicroseconds(unsigned int us) {
os_delay_us(us);
}

View File

@ -39,4 +39,16 @@ extern int __analogRead(uint8_t pin)
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