mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-19 23:22:16 +03:00
Merge branch 'master' into wifi_mesh_update_2.2
This commit is contained in:
commit
7fbf620ab6
271
.github/workflows/pull-request.yml
vendored
Normal file
271
.github/workflows/pull-request.yml
vendored
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
# Run whenever a PR is generated or updated.
|
||||||
|
|
||||||
|
# Most jobs check out the code, ensure Python3 is installed, and for build
|
||||||
|
# tests the ESP8266 toolchain is cached when possible to speed up execution.
|
||||||
|
|
||||||
|
name: ESP8266 Arduino CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
# Run 8 parallel jobs for the default build of all examples.
|
||||||
|
build-linux:
|
||||||
|
name: Build ${{ matrix.chunk }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
chunk: [0, 1, 2, 3, 4, 5, 6, 7]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Cache Linux toolchain
|
||||||
|
id: cache-linux
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ./tools/dist
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||||
|
- name: Build Sketches
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
BUILD_PARITY: custom
|
||||||
|
mod: 8
|
||||||
|
rem: ${{ matrix.chunk }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/build.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Cover the debug and IPv6 cases by enabling both and running 8 parallel jobs
|
||||||
|
# over all example code.
|
||||||
|
build-debug-ipv6:
|
||||||
|
name: Debug IPv6 ${{ matrix.chunk }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
chunk: [0, 1, 2, 3, 4, 5, 6, 7]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Cache Linux toolchain
|
||||||
|
id: cache-linux
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ./tools/dist
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||||
|
- name: Build Sketches
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
BUILD_PARITY: custom
|
||||||
|
mod: 8
|
||||||
|
rem: ${{ matrix.chunk }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/debug6.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Single build under Windows to ensure the Win toolchain is good.
|
||||||
|
build-windows:
|
||||||
|
name: Windows
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Cache Windows toolchain
|
||||||
|
id: cache-windows
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ./tools/dist
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||||
|
- name: Build Sketch
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
WINDOWS: 1
|
||||||
|
BUILD_PARITY: custom
|
||||||
|
mod: 500
|
||||||
|
rem: 1
|
||||||
|
run: |
|
||||||
|
# Windows has python3 already installed, but it's called "python".
|
||||||
|
# Copy python.exe to the proper name so scripts "just work".
|
||||||
|
try { Get-Command python3 } catch { copy (get-command python).source (get-command python).source.Replace("python.exe", "python3.exe") }
|
||||||
|
bash ./tests/build.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Single build under macOS to ensure the Mac toolchain is good.
|
||||||
|
build-mac:
|
||||||
|
name: Mac
|
||||||
|
runs-on: macOS-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Cache Mac toolchain
|
||||||
|
id: cache-mac
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ./tools/dist
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||||
|
- name: Build Sketch
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
MACOSX: 1
|
||||||
|
BUILD_PARITY: custom
|
||||||
|
mod: 500
|
||||||
|
rem: 1
|
||||||
|
run: |
|
||||||
|
bash ./tests/build.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Run a few Platform.IO jobs (not full suite) to check PIO integration.
|
||||||
|
build-pio:
|
||||||
|
name: Build Platform.IO
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Build subset on Platform.IO
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
BUILD_PARITY: custom
|
||||||
|
mod: 42 # Picked at random to give 4-5 builds and exit.
|
||||||
|
rem: 13
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install python3-pip python3-setuptools
|
||||||
|
PATH=/home/runner/.local/bin:$PATH bash ./tests/platformio.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Run host test suite under valgrind for runtime checking of code.
|
||||||
|
host-tests:
|
||||||
|
name: Host tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Run host tests
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install valgrind lcov
|
||||||
|
bash ./tests/ci/host_test.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Ensure Sphinx can build the documentation properly.
|
||||||
|
documentation:
|
||||||
|
name: Documentation
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Build documentation
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install python3-pip python3-setuptools
|
||||||
|
# GitHub CI installs pip3 and setuptools outside the path.
|
||||||
|
# Update the path to include them and run.
|
||||||
|
PATH=/home/runner/.local/bin:$PATH pip3 install --user -r doc/requirements.txt
|
||||||
|
PATH=/home/runner/.local/bin:$PATH bash ./tests/ci/build_docs.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Standard Arduino formatting in all the examples
|
||||||
|
style-check:
|
||||||
|
name: Style and formatting
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Style check
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install astyle
|
||||||
|
bash ./tests/ci/style_check.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Quick test that the mocking builds succeed
|
||||||
|
mock-check:
|
||||||
|
name: Mock trivial test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Mock build
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/buildm.sh
|
||||||
|
|
||||||
|
|
||||||
|
# Ensure no manual edits to boards.txt
|
||||||
|
boards-check:
|
||||||
|
name: Boards.txt check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Cache Linux toolchain
|
||||||
|
id: cache-linux
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ./tools/dist
|
||||||
|
key: ${{ runner.os }}-${{ hashFiles('package/package_esp8266com_index.template.json', 'tests/common.sh') }}
|
||||||
|
- name: Boards.txt diff
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
TRAVIS_TAG: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/ci/build_boards.sh
|
||||||
|
bash ./tests/ci/eboot_test.sh
|
||||||
|
bash ./tests/ci/pkgrefs_test.sh
|
56
.github/workflows/release-to-publish.yml
vendored
Normal file
56
.github/workflows/release-to-publish.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Whenever a release is published from a draft, this will update the
|
||||||
|
# master Arduino JSON file to add its new entry.
|
||||||
|
|
||||||
|
# We keep the master JSON file in another repo, so we need to use a pre-set
|
||||||
|
# Deployment SSH key to be able to push a change to the repo.
|
||||||
|
|
||||||
|
#### Steps to follow when you need to make a new SSH key for upload (not
|
||||||
|
#### normally needed!)
|
||||||
|
|
||||||
|
# Generate a new SSH key private/public pair
|
||||||
|
|
||||||
|
# ssh-keygen -t rsa -b 4096 -C "your@email.com" -f ./deploy_rsa
|
||||||
|
|
||||||
|
# Upload deploy_rsa.pub to the *ESP8266.GITHUB.IO* repo as a deployment key
|
||||||
|
|
||||||
|
# Convert the private key to base64 (to remove line breaks and allow easier
|
||||||
|
# usage in the script as an environment variable)
|
||||||
|
|
||||||
|
# base64.exe -w 0 < deploy_rsa > deploy_rsa.b64
|
||||||
|
|
||||||
|
# Copy the contents of the .b64 file to the clipboard, make a new GitHub
|
||||||
|
# secret in the ESP8266/Arduino repo called "GHCI_DEPLOY_KEY" and paste
|
||||||
|
# the B64 code into the variable.
|
||||||
|
|
||||||
|
name: ESP8266 Arduino Release Publisher
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
package:
|
||||||
|
name: Update master JSON file
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Set GIT tag name
|
||||||
|
run: |
|
||||||
|
echo "::set-env name=TRAVIS_TAG::$(git describe --exact-match --tags)"
|
||||||
|
- name: Deploy updated JSON
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
BUILD_TYPE: package
|
||||||
|
CI_GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GHCI_DEPLOY_KEY: ${{ secrets.GHCI_DEPLOY_KEY }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/ci/build_package.sh
|
||||||
|
# Only the regenerated JSON file will be used, but it's simpler
|
||||||
|
# than looking for it in a GH release.
|
||||||
|
bash ./package/deploy_package_index.sh
|
||||||
|
|
40
.github/workflows/tag-to-draft-release.yml
vendored
Normal file
40
.github/workflows/tag-to-draft-release.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# Whenever a tag of the form #.xxxx is pushed against master, generate a
|
||||||
|
# draft release and upload the ZIP and JSON file to it. Maintainers then
|
||||||
|
# will manually add the changelist and publish it.
|
||||||
|
|
||||||
|
name: ESP8266 Arduino Draft Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
# Run for tags of the x.x.x* form (i.e. 3.0.0, 3.0.0-beta, etc.).
|
||||||
|
- '[0-9]+.[0-9]+.[0-9]+*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
package:
|
||||||
|
name: Package
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
- name: Set GIT tag name
|
||||||
|
run: |
|
||||||
|
# Sets an environment variable used in the next steps
|
||||||
|
echo "::set-env name=TRAVIS_TAG::$(git describe --exact-match --tags)"
|
||||||
|
- name: Build package JSON
|
||||||
|
env:
|
||||||
|
TRAVIS_BUILD_DIR: ${{ github.workspace }}
|
||||||
|
BUILD_TYPE: package
|
||||||
|
CI_GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
bash ./tests/ci/build_package.sh
|
||||||
|
pip3 install PyGithub
|
||||||
|
# Create a draft release and upload the ZIP and JSON files.
|
||||||
|
# This draft is not visible to normal users and needs to be
|
||||||
|
# updated manually with release notes and published from the
|
||||||
|
# GitHub web interface.
|
||||||
|
python3 ./package/upload_release.py --user "$GITHUB_ACTOR" --repo "$GITHUB_REPOSITORY" --token "$CI_GITHUB_API_KEY" --tag "$TRAVIS_TAG" --name "Release $TRAVIS_TAG" --msg "Update the draft with release notes before publishing." package/versions/*/*.zip package/versions/*/package_esp8266com_index.json
|
5
.gitmodules
vendored
5
.gitmodules
vendored
@ -19,6 +19,9 @@
|
|||||||
[submodule "tools/esptool"]
|
[submodule "tools/esptool"]
|
||||||
path = tools/esptool
|
path = tools/esptool
|
||||||
url = https://github.com/espressif/esptool.git
|
url = https://github.com/espressif/esptool.git
|
||||||
|
[submodule "libraries/Ethernet"]
|
||||||
|
path = libraries/Ethernet
|
||||||
|
url = https://github.com/arduino-libraries/Ethernet.git
|
||||||
[submodule "tools/sdk/uzlib"]
|
[submodule "tools/sdk/uzlib"]
|
||||||
path = tools/sdk/uzlib
|
path = tools/sdk/uzlib
|
||||||
url = https://github.com/earlephilhower/uzlib.git
|
url = https://github.com/pfalcon/uzlib.git
|
||||||
|
162
.travis.yml
162
.travis.yml
@ -1,162 +0,0 @@
|
|||||||
language: bash
|
|
||||||
os: linux
|
|
||||||
dist: bionic
|
|
||||||
|
|
||||||
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
|
|
||||||
install:
|
|
||||||
- sudo apt-get install python3-pip python3-setuptools
|
|
||||||
env:
|
|
||||||
- BUILD_PARITY=even
|
|
||||||
- name: "Platformio (2)"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/platformio.sh
|
|
||||||
install:
|
|
||||||
- sudo apt-get install python3-pip python3-setuptools
|
|
||||||
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: "Mac OSX can build sketches"
|
|
||||||
os: osx
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/build.sh
|
|
||||||
env: MACOSX=1 BUILD_PARITY=custom mod=500 rem=1
|
|
||||||
|
|
||||||
- name: "Windows can build sketches"
|
|
||||||
os: windows
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/build.sh
|
|
||||||
env: WINDOWS=1 BUILD_PARITY=custom mod=500 rem=1
|
|
||||||
|
|
||||||
- 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 python3-setuptools
|
|
||||||
- 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: "Mock trivial test"
|
|
||||||
stage: build
|
|
||||||
script: $TRAVIS_BUILD_DIR/tests/buildm.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
|
|
10
README.md
10
README.md
@ -3,7 +3,7 @@ Arduino core for ESP8266 WiFi chip
|
|||||||
|
|
||||||
# Quick links
|
# Quick links
|
||||||
|
|
||||||
- [Latest release documentation](https://arduino-esp8266.readthedocs.io/en/2.7.1/)
|
- [Latest release documentation](https://arduino-esp8266.readthedocs.io/en/2.7.4_a/)
|
||||||
- [Current "git version" documentation](https://arduino-esp8266.readthedocs.io/en/latest/)
|
- [Current "git version" documentation](https://arduino-esp8266.readthedocs.io/en/latest/)
|
||||||
- [Install git version](https://arduino-esp8266.readthedocs.io/en/latest/installing.html#using-git-version) ([sources](doc/installing.rst#using-git-version))
|
- [Install git version](https://arduino-esp8266.readthedocs.io/en/latest/installing.html#using-git-version) ([sources](doc/installing.rst#using-git-version))
|
||||||
|
|
||||||
@ -30,13 +30,13 @@ Starting with 1.6.4, Arduino allows installation of third-party platform package
|
|||||||
|
|
||||||
- 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).
|
- 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.
|
- Start Arduino and open the Preferences window.
|
||||||
- Enter ```https://arduino.esp8266.com/stable/package_esp8266com_index.json``` into the *Additional Board Manager URLs* field. You can add multiple URLs, separating them with commas.
|
- Enter ```https://arduino.esp8266.com/stable/package_esp8266com_index.json``` into the *File>Preferences>Additional Boards Manager URLs* field of the Arduino IDE. You can add multiple URLs, separating them with commas.
|
||||||
- Open Boards Manager from Tools > Board menu and install *esp8266* platform (and don't forget to select your ESP8266 board from Tools > Board menu after installation).
|
- Open Boards Manager from Tools > Board menu and install *esp8266* platform (and don't forget to select your ESP8266 board from Tools > Board menu after installation).
|
||||||
|
|
||||||
#### Latest release [](https://github.com/esp8266/Arduino/releases/latest/)
|
#### Latest release [](https://github.com/esp8266/Arduino/releases/latest/)
|
||||||
Boards manager link: `https://arduino.esp8266.com/stable/package_esp8266com_index.json`
|
Boards manager link: `https://arduino.esp8266.com/stable/package_esp8266com_index.json`
|
||||||
|
|
||||||
Documentation: [https://arduino-esp8266.readthedocs.io/en/2.7.1/](https://arduino-esp8266.readthedocs.io/en/2.7.1/)
|
Documentation: [https://arduino-esp8266.readthedocs.io/en/2.7.4_a/](https://arduino-esp8266.readthedocs.io/en/2.7.4_a/)
|
||||||
|
|
||||||
### Using git version
|
### Using git version
|
||||||
[](https://travis-ci.org/esp8266/Arduino)
|
[](https://travis-ci.org/esp8266/Arduino)
|
||||||
@ -108,7 +108,7 @@ ESP8266 core includes an xtensa gcc toolchain, which is also under GPL.
|
|||||||
|
|
||||||
Esptool.py was initially created by Fredrik Ahlberg (@themadinventor, @kongo), and is currently maintained by Angus Gratton (@projectgus) under GPL 2.0 license.
|
Esptool.py was initially created by Fredrik Ahlberg (@themadinventor, @kongo), and is currently maintained by Angus Gratton (@projectgus) under GPL 2.0 license.
|
||||||
|
|
||||||
Espressif SDK included in this build is under Espressif MIT License.
|
[Espressif's NONOS SDK](https://github.com/espressif/ESP8266_NONOS_SDK) included in this build is under Espressif MIT License.
|
||||||
|
|
||||||
ESP8266 core files are licensed under LGPL.
|
ESP8266 core files are licensed under LGPL.
|
||||||
|
|
||||||
@ -118,8 +118,6 @@ ESP8266 core files are licensed under LGPL.
|
|||||||
|
|
||||||
[SoftwareSerial](https://github.com/plerup/espsoftwareserial) library and examples written by Peter Lerup. Distributed under LGPL 2.1.
|
[SoftwareSerial](https://github.com/plerup/espsoftwareserial) library and examples written by Peter Lerup. Distributed under LGPL 2.1.
|
||||||
|
|
||||||
[axTLS](http://axtls.sourceforge.net/) library written by Cameron Rich, built from https://github.com/igrr/axtls-8266, is used in this project. It is distributed under [BSD license](https://github.com/igrr/axtls-8266/blob/master/LICENSE).
|
|
||||||
|
|
||||||
[BearSSL](https://bearssl.org) library written by Thomas Pornin, built from https://github.com/earlephilhower/bearssl-esp8266, is used in this project. It is distributed under the [MIT License](https://bearssl.org/#legal-details).
|
[BearSSL](https://bearssl.org) library written by Thomas Pornin, built from https://github.com/earlephilhower/bearssl-esp8266, is used in this project. It is distributed under the [MIT License](https://bearssl.org/#legal-details).
|
||||||
|
|
||||||
[LittleFS](https://github.com/ARMmbed/littlefs) library written by ARM Limited and released under the [BSD 3-clause license](https://github.com/ARMmbed/littlefs/blob/master/LICENSE.md).
|
[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).
|
||||||
|
12215
boards.txt
12215
boards.txt
File diff suppressed because it is too large
Load Diff
@ -21,9 +21,9 @@ OBJDUMP := $(XTENSA_TOOLCHAIN)xtensa-lx106-elf-objdump
|
|||||||
|
|
||||||
INC += -I../../tools/sdk/include -I../../tools/sdk/uzlib/src
|
INC += -I../../tools/sdk/include -I../../tools/sdk/uzlib/src
|
||||||
|
|
||||||
CFLAGS += -std=gnu99
|
CFLAGS += -std=gnu17
|
||||||
|
|
||||||
CFLAGS += -Os -g -Wall -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mno-text-section-literals -ffunction-sections -fdata-sections
|
CFLAGS += -Os -fcommon -g -Wall -Wpointer-arith -Wno-implicit-function-declaration -Wl,-EL -fno-inline-functions -nostdlib -mlongcalls -mno-text-section-literals -ffunction-sections -fdata-sections -free -fipa-pta
|
||||||
|
|
||||||
CFLAGS += $(INC)
|
CFLAGS += $(INC)
|
||||||
|
|
||||||
@ -40,17 +40,17 @@ APP_FW := eboot.bin
|
|||||||
|
|
||||||
all: $(APP_OUT)
|
all: $(APP_OUT)
|
||||||
|
|
||||||
tinflate.o: $(UZLIB_PATH)/tinflate.c $(UZLIB_PATH)/uzlib.h $(UZLIB_PATH)/uzlib_conf.h
|
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
|
$(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
|
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
|
$(CC) $(CFLAGS) -c -o tinfgzip.o $(UZLIB_PATH)/tinfgzip.c
|
||||||
|
|
||||||
$(APP_AR): $(TARGET_OBJ_PATHS) tinflate.o tinfgzip.o
|
$(APP_AR): $(TARGET_OBJ_PATHS) tinflate.o tinfgzip.o Makefile
|
||||||
$(AR) cru $@ $^
|
$(AR) cru $@ $^
|
||||||
|
|
||||||
$(APP_OUT): $(APP_AR) eboot.ld | Makefile
|
$(APP_OUT): $(APP_AR) eboot.ld | Makefile
|
||||||
$(LD) $(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group -Wl,--whole-archive $(APP_AR) -Wl,--end-group -o $@
|
$(LD) $(LD_SCRIPT) $(LDFLAGS) -Wl,--start-group -Wl,--sort-common $(APP_AR) -Wl,--end-group -o $@
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *.o
|
rm -f *.o
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
#include "eboot_command.h"
|
#include "eboot_command.h"
|
||||||
#include <uzlib.h>
|
#include <uzlib.h>
|
||||||
|
|
||||||
extern unsigned char _gzip_dict;
|
|
||||||
|
|
||||||
#define SWRST do { (*((volatile uint32_t*) 0x60000700)) |= 0x80000000; } while(0);
|
#define SWRST do { (*((volatile uint32_t*) 0x60000700)) |= 0x80000000; } while(0);
|
||||||
|
|
||||||
@ -27,15 +26,7 @@ int print_version(const uint32_t flash_addr)
|
|||||||
if (SPIRead(flash_addr + APP_START_OFFSET + sizeof(image_header_t) + sizeof(section_header_t), &ver, sizeof(ver))) {
|
if (SPIRead(flash_addr + APP_START_OFFSET + sizeof(image_header_t) + sizeof(section_header_t), &ver, sizeof(ver))) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
char fmt[7];
|
ets_printf("v%08x\n", ver);
|
||||||
fmt[0] = 'v';
|
|
||||||
fmt[1] = '%';
|
|
||||||
fmt[2] = '0';
|
|
||||||
fmt[3] = '8';
|
|
||||||
fmt[4] = 'x';
|
|
||||||
fmt[5] = '\n';
|
|
||||||
fmt[6] = 0;
|
|
||||||
ets_printf((const char*) fmt, ver);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +59,9 @@ int load_app_from_flash_raw(const uint32_t flash_addr)
|
|||||||
load = true;
|
load = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (address >= 0x40100000 && address < 0x40108000) {
|
// The final IRAM size, once boot has completed, can be either 32K or 48K.
|
||||||
|
// Allow for the higher in range testing.
|
||||||
|
if (address >= 0x40100000 && address < 0x4010C000) {
|
||||||
load = true;
|
load = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,11 +152,6 @@ int copy_raw(const uint32_t src_addr,
|
|||||||
gzip = true;
|
gzip = true;
|
||||||
}
|
}
|
||||||
while (left > 0) {
|
while (left > 0) {
|
||||||
if (!verify) {
|
|
||||||
if (SPIEraseSector(daddr/buffer_size)) {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!gzip) {
|
if (!gzip) {
|
||||||
if (SPIRead(saddr, buffer, buffer_size)) {
|
if (SPIRead(saddr, buffer, buffer_size)) {
|
||||||
return 3;
|
return 3;
|
||||||
@ -190,8 +178,25 @@ int copy_raw(const uint32_t src_addr,
|
|||||||
return 9;
|
return 9;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (SPIWrite(daddr, buffer, buffer_size)) {
|
// Special treatment for address 0 (bootloader). Only erase and
|
||||||
return 4;
|
// 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;
|
saddr += buffer_size;
|
||||||
@ -208,6 +213,16 @@ int main()
|
|||||||
bool clear_cmd = false;
|
bool clear_cmd = false;
|
||||||
struct eboot_command cmd;
|
struct eboot_command cmd;
|
||||||
|
|
||||||
|
// BSS init commented out for now to save space. If any static variables set
|
||||||
|
// to 0 are used, need to uncomment it or else the BSS will not be cleared and
|
||||||
|
// the static vars will power on with random values.
|
||||||
|
#if 0
|
||||||
|
// Clear BSS ourselves, we don't have handy C runtime
|
||||||
|
extern char _bss_start;
|
||||||
|
extern char _bss_end;
|
||||||
|
ets_bzero(&_bss_start, &_bss_end - &_bss_start);
|
||||||
|
#endif
|
||||||
|
|
||||||
print_version(0);
|
print_version(0);
|
||||||
|
|
||||||
if (eboot_command_read(&cmd) == 0) {
|
if (eboot_command_read(&cmd) == 0) {
|
||||||
@ -222,23 +237,27 @@ int main()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cmd.action == ACTION_COPY_RAW) {
|
if (cmd.action == ACTION_COPY_RAW) {
|
||||||
ets_putc('c'); ets_putc('p'); ets_putc(':');
|
ets_printf("cp:");
|
||||||
|
|
||||||
ets_wdt_disable();
|
ets_wdt_disable();
|
||||||
res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2], false);
|
res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2], false);
|
||||||
ets_wdt_enable();
|
ets_wdt_enable();
|
||||||
|
|
||||||
ets_putc('0'+res); ets_putc('\n');
|
ets_printf("%d\n", res);
|
||||||
|
#if 0
|
||||||
|
//devyte: this verify step below (cmp:) only works when the end of copy operation above does not overwrite the
|
||||||
|
//beginning of the image in the empty area, see #7458. Disabling for now.
|
||||||
|
//TODO: replace the below verify with hash type, crc, or similar.
|
||||||
// Verify the copy
|
// Verify the copy
|
||||||
ets_putc('c'); ets_putc('m'); ets_putc('p'); ets_putc(':');
|
ets_printf("cmp:");
|
||||||
if (res == 0) {
|
if (res == 0) {
|
||||||
ets_wdt_disable();
|
ets_wdt_disable();
|
||||||
res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2], true);
|
res = copy_raw(cmd.args[0], cmd.args[1], cmd.args[2], true);
|
||||||
ets_wdt_enable();
|
ets_wdt_enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
ets_putc('0'+res); ets_putc('\n');
|
ets_printf("%d\n", res);
|
||||||
|
#endif
|
||||||
if (res == 0) {
|
if (res == 0) {
|
||||||
cmd.action = ACTION_LOAD_APP;
|
cmd.action = ACTION_LOAD_APP;
|
||||||
cmd.args[0] = cmd.args[1];
|
cmd.args[0] = cmd.args[1];
|
||||||
@ -250,10 +269,10 @@ int main()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cmd.action == ACTION_LOAD_APP) {
|
if (cmd.action == ACTION_LOAD_APP) {
|
||||||
ets_putc('l'); ets_putc('d'); ets_putc('\n');
|
ets_printf("ld\n");
|
||||||
res = load_app_from_flash_raw(cmd.args[0]);
|
res = load_app_from_flash_raw(cmd.args[0]);
|
||||||
//we will get to this only on load fail
|
// We will get to this only on load fail
|
||||||
ets_putc('e'); ets_putc(':'); ets_putc('0'+res); ets_putc('\n');
|
ets_printf("e:%d\n", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
|
Binary file not shown.
@ -42,53 +42,13 @@ PROVIDE(_memmap_cacheattr_reset = _memmap_cacheattr_wb_trapnull);
|
|||||||
SECTIONS
|
SECTIONS
|
||||||
{
|
{
|
||||||
|
|
||||||
.dport0.rodata : ALIGN(4)
|
.globals : ALIGN(4)
|
||||||
{
|
{
|
||||||
_dport0_rodata_start = ABSOLUTE(.);
|
*(COMMON) /* Global vars */
|
||||||
*(.dport0.rodata)
|
} >dram0_0_seg :dram0_0_bss_phdr
|
||||||
*(.dport.rodata)
|
|
||||||
_dport0_rodata_end = ABSOLUTE(.);
|
|
||||||
} >dport0_0_seg :dport0_0_phdr
|
|
||||||
|
|
||||||
.dport0.literal : ALIGN(4)
|
|
||||||
{
|
|
||||||
_dport0_literal_start = ABSOLUTE(.);
|
|
||||||
*(.dport0.literal)
|
|
||||||
*(.dport.literal)
|
|
||||||
_dport0_literal_end = ABSOLUTE(.);
|
|
||||||
} >dport0_0_seg :dport0_0_phdr
|
|
||||||
|
|
||||||
.dport0.data : ALIGN(4)
|
|
||||||
{
|
|
||||||
_dport0_data_start = ABSOLUTE(.);
|
|
||||||
*(.dport0.data)
|
|
||||||
*(.dport.data)
|
|
||||||
_dport0_data_end = ABSOLUTE(.);
|
|
||||||
} >dport0_0_seg :dport0_0_phdr
|
|
||||||
|
|
||||||
.data : ALIGN(4)
|
.data : ALIGN(4)
|
||||||
{
|
{
|
||||||
*(COMMON) /* Global vars */
|
|
||||||
. = ALIGN(4);
|
|
||||||
_heap_start = ABSOLUTE(.);
|
|
||||||
/* _stack_sentry = ALIGN(0x8); */
|
|
||||||
} >dram0_0_seg :dram0_0_bss_phdr
|
|
||||||
/* __stack = 0x3ffc8000; */
|
|
||||||
|
|
||||||
.text : ALIGN(4)
|
|
||||||
{
|
|
||||||
_stext = .;
|
|
||||||
_text_start = ABSOLUTE(.);
|
|
||||||
*(.entry.text)
|
|
||||||
*(.init.literal)
|
|
||||||
*(.init)
|
|
||||||
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
|
|
||||||
*(.fini.literal)
|
|
||||||
*(.fini)
|
|
||||||
*(.gnu.version)
|
|
||||||
_text_end = ABSOLUTE(.);
|
|
||||||
_etext = .;
|
|
||||||
. = ALIGN (8);
|
|
||||||
_data_start = ABSOLUTE(.);
|
_data_start = ABSOLUTE(.);
|
||||||
*(.data)
|
*(.data)
|
||||||
*(.data.*)
|
*(.data.*)
|
||||||
@ -102,7 +62,10 @@ SECTIONS
|
|||||||
*(.gnu.linkonce.s2.*)
|
*(.gnu.linkonce.s2.*)
|
||||||
*(.jcr)
|
*(.jcr)
|
||||||
_data_end = ABSOLUTE(.);
|
_data_end = ABSOLUTE(.);
|
||||||
. = ALIGN (8);
|
} >dram0_0_seg :dram0_0_bss_phdr
|
||||||
|
|
||||||
|
.rodata : ALIGN(4)
|
||||||
|
{
|
||||||
_rodata_start = ABSOLUTE(.);
|
_rodata_start = ABSOLUTE(.);
|
||||||
*(.rodata)
|
*(.rodata)
|
||||||
*(.rodata.*)
|
*(.rodata.*)
|
||||||
@ -131,14 +94,11 @@ SECTIONS
|
|||||||
*(.xt_except_desc_end)
|
*(.xt_except_desc_end)
|
||||||
*(.dynamic)
|
*(.dynamic)
|
||||||
*(.gnu.version_d)
|
*(.gnu.version_d)
|
||||||
. = ALIGN(4); /* this table MUST be 4-byte aligned */
|
|
||||||
_bss_table_start = ABSOLUTE(.);
|
|
||||||
LONG(_bss_start)
|
|
||||||
LONG(_bss_end)
|
|
||||||
_bss_table_end = ABSOLUTE(.);
|
|
||||||
_rodata_end = ABSOLUTE(.);
|
_rodata_end = ABSOLUTE(.);
|
||||||
|
} >dram0_0_seg :dram0_0_bss_phdr
|
||||||
|
|
||||||
. = ALIGN (8);
|
.bss : ALIGN(4)
|
||||||
|
{
|
||||||
_bss_start = ABSOLUTE(.);
|
_bss_start = ABSOLUTE(.);
|
||||||
*(.dynsbss)
|
*(.dynsbss)
|
||||||
*(.sbss)
|
*(.sbss)
|
||||||
@ -152,26 +112,24 @@ SECTIONS
|
|||||||
*(.bss)
|
*(.bss)
|
||||||
*(.bss.*)
|
*(.bss.*)
|
||||||
*(.gnu.linkonce.b.*)
|
*(.gnu.linkonce.b.*)
|
||||||
. = ALIGN (8);
|
|
||||||
_bss_end = ABSOLUTE(.);
|
_bss_end = ABSOLUTE(.);
|
||||||
_free_space = 4096 - 17 - (. - _stext);
|
} >dram0_0_seg :dram0_0_bss_phdr
|
||||||
/*
|
|
||||||
The boot loader checksum must be before the CRC, which is written by elf2bin.py.
|
|
||||||
This leaves 16 bytes after the checksum for the CRC placed at the end of the
|
|
||||||
4096-byte sector. */
|
|
||||||
_cs_here = (ALIGN((. + 1), 16) == ALIGN(16)) ? (ALIGN(16) - 1) : (. + 0x0F);
|
|
||||||
|
|
||||||
/*
|
|
||||||
The filling (padding) and values for _crc_size and _crc_val are handled by
|
|
||||||
elf2bin.py. With this, we give values to the symbols without explicitly
|
|
||||||
assigning space. This avoids the linkers back *fill* operation that causes
|
|
||||||
trouble.
|
|
||||||
|
|
||||||
The CRC info is stored in last 8 bytes. */
|
.text : ALIGN(4)
|
||||||
_crc_size = _stext + 4096 - 8;
|
{
|
||||||
_crc_val = _stext + 4096 - 4;
|
_stext = .;
|
||||||
ASSERT((4096 > (17 + (. - _stext))), "Error: No space for CS and CRC in bootloader sector.");
|
_text_start = ABSOLUTE(.);
|
||||||
ASSERT((_crc_size > _cs_here), "Error: CRC must be located after CS.");
|
*(.entry.text)
|
||||||
|
*(.init.literal)
|
||||||
|
*(.init)
|
||||||
|
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
|
||||||
|
*(.fini.literal)
|
||||||
|
*(.fini)
|
||||||
|
*(.gnu.version)
|
||||||
|
_text_end = ABSOLUTE(.);
|
||||||
|
_etext = .;
|
||||||
|
. = ALIGN (4); /* Ensure 32b alignment since this is written to IRAM */
|
||||||
} >iram1_0_seg :iram1_0_phdr
|
} >iram1_0_seg :iram1_0_phdr
|
||||||
|
|
||||||
.lit4 : ALIGN(4)
|
.lit4 : ALIGN(4)
|
||||||
|
@ -128,7 +128,7 @@ struct netifWrapper
|
|||||||
const char* ifmac () const { return (const char*)_netif->hwaddr; }
|
const char* ifmac () const { return (const char*)_netif->hwaddr; }
|
||||||
int ifnumber () const { return _netif->num; }
|
int ifnumber () const { return _netif->num; }
|
||||||
bool ifUp () const { return !!(_netif->flags & NETIF_FLAG_UP); }
|
bool ifUp () const { return !!(_netif->flags & NETIF_FLAG_UP); }
|
||||||
CONST netif* interface () const { return _netif; }
|
const netif* interface () const { return _netif; }
|
||||||
|
|
||||||
const ip_addr_t* ipFromNetifNum () const
|
const ip_addr_t* ipFromNetifNum () const
|
||||||
{
|
{
|
||||||
|
@ -37,14 +37,13 @@ extern "C" {
|
|||||||
#include "binary.h"
|
#include "binary.h"
|
||||||
#include "esp8266_peri.h"
|
#include "esp8266_peri.h"
|
||||||
#include "twi.h"
|
#include "twi.h"
|
||||||
|
|
||||||
#include "core_esp8266_features.h"
|
#include "core_esp8266_features.h"
|
||||||
#include "core_esp8266_version.h"
|
#include "core_esp8266_version.h"
|
||||||
|
|
||||||
#define HIGH 0x1
|
#define HIGH 0x1
|
||||||
#define LOW 0x0
|
#define LOW 0x0
|
||||||
|
|
||||||
#define PWMRANGE 1023
|
|
||||||
|
|
||||||
//GPIO FUNCTIONS
|
//GPIO FUNCTIONS
|
||||||
#define INPUT 0x00
|
#define INPUT 0x00
|
||||||
#define INPUT_PULLUP 0x02
|
#define INPUT_PULLUP 0x02
|
||||||
@ -127,21 +126,11 @@ void timer0_isr_init(void);
|
|||||||
void timer0_attachInterrupt(timercallback userFunc);
|
void timer0_attachInterrupt(timercallback userFunc);
|
||||||
void timer0_detachInterrupt(void);
|
void timer0_detachInterrupt(void);
|
||||||
|
|
||||||
// undefine stdlib's abs if encountered
|
|
||||||
#ifdef abs
|
|
||||||
#undef abs
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define abs(x) ((x)>0?(x):-(x))
|
|
||||||
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
|
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
|
||||||
#define round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5))
|
|
||||||
#define radians(deg) ((deg)*DEG_TO_RAD)
|
#define radians(deg) ((deg)*DEG_TO_RAD)
|
||||||
#define degrees(rad) ((rad)*RAD_TO_DEG)
|
#define degrees(rad) ((rad)*RAD_TO_DEG)
|
||||||
#define sq(x) ((x)*(x))
|
#define sq(x) ((x)*(x))
|
||||||
|
|
||||||
void ets_intr_lock();
|
|
||||||
void ets_intr_unlock();
|
|
||||||
|
|
||||||
#define interrupts() xt_rsil(0)
|
#define interrupts() xt_rsil(0)
|
||||||
#define noInterrupts() xt_rsil(15)
|
#define noInterrupts() xt_rsil(15)
|
||||||
|
|
||||||
@ -170,25 +159,23 @@ typedef uint16_t word;
|
|||||||
typedef bool boolean;
|
typedef bool boolean;
|
||||||
typedef uint8_t byte;
|
typedef uint8_t byte;
|
||||||
|
|
||||||
|
void ets_intr_lock();
|
||||||
|
void ets_intr_unlock();
|
||||||
|
|
||||||
void init(void);
|
void init(void);
|
||||||
void initVariant(void);
|
void initVariant(void);
|
||||||
|
|
||||||
int atexit(void (*func)()) __attribute__((weak));
|
|
||||||
|
|
||||||
void pinMode(uint8_t pin, uint8_t mode);
|
void pinMode(uint8_t pin, uint8_t mode);
|
||||||
void digitalWrite(uint8_t pin, uint8_t val);
|
void digitalWrite(uint8_t pin, uint8_t val);
|
||||||
int digitalRead(uint8_t pin);
|
int digitalRead(uint8_t pin);
|
||||||
int analogRead(uint8_t pin);
|
int analogRead(uint8_t pin);
|
||||||
void analogReference(uint8_t mode);
|
void analogReference(uint8_t mode);
|
||||||
void analogWrite(uint8_t pin, int val);
|
void analogWrite(uint8_t pin, int val);
|
||||||
|
void analogWriteMode(uint8_t pin, int val, bool openDrain);
|
||||||
void analogWriteFreq(uint32_t freq);
|
void analogWriteFreq(uint32_t freq);
|
||||||
|
void analogWriteResolution(int res);
|
||||||
void analogWriteRange(uint32_t range);
|
void analogWriteRange(uint32_t range);
|
||||||
|
|
||||||
unsigned long millis(void);
|
|
||||||
unsigned long micros(void);
|
|
||||||
uint64_t micros64(void);
|
|
||||||
void delay(unsigned long);
|
|
||||||
void delayMicroseconds(unsigned int us);
|
|
||||||
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout);
|
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout);
|
||||||
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout);
|
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout);
|
||||||
|
|
||||||
@ -224,34 +211,35 @@ void optimistic_yield(uint32_t interval_us);
|
|||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// undefine stdlib's definitions when encountered, provide abs that supports floating point for C code
|
||||||
|
#ifndef __cplusplus
|
||||||
|
#undef abs
|
||||||
|
#define abs(x) ({ __typeof__(x) _x = (x); _x > 0 ? _x : -_x; })
|
||||||
|
#undef round
|
||||||
|
#define round(x) ({ __typeof__(x) _x = (x); _x >= 0 ? (long)(_x + 0.5) : (long)(_x - 0.5); })
|
||||||
|
#endif // ifndef __cplusplus
|
||||||
|
|
||||||
//for compatibility, below 4 lines to be removed in release 3.0.0
|
// from this point onward, we need to configure the c++ environment
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C"
|
|
||||||
#endif
|
|
||||||
const int TIM_DIV265 __attribute__((deprecated, weak)) = TIM_DIV256;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstdlib>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <pgmspace.h>
|
|
||||||
|
|
||||||
#include "WCharacter.h"
|
|
||||||
#include "WString.h"
|
|
||||||
|
|
||||||
#include "HardwareSerial.h"
|
#include "mmu_iram.h"
|
||||||
#include "Esp.h"
|
|
||||||
#include "Updater.h"
|
|
||||||
#include "debug.h"
|
|
||||||
|
|
||||||
using std::min;
|
using std::min;
|
||||||
using std::max;
|
using std::max;
|
||||||
|
using std::round;
|
||||||
using std::isinf;
|
using std::isinf;
|
||||||
using std::isnan;
|
using std::isnan;
|
||||||
|
|
||||||
|
// 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 _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; })
|
#define _max(a,b) ({ decltype(a) _a = (a); decltype(b) _b = (b); _a > _b? _a : _b; })
|
||||||
|
|
||||||
@ -291,8 +279,19 @@ inline void configTzTime(const char* tz, const char* server1,
|
|||||||
configTime(tz, server1, server2, server3);
|
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
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
#include "pins_arduino.h"
|
#include "pins_arduino.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -42,11 +42,9 @@ class Client: public Stream {
|
|||||||
uint8_t* rawIPAddress(IPAddress& addr) {
|
uint8_t* rawIPAddress(IPAddress& addr) {
|
||||||
return addr.raw_address();
|
return addr.raw_address();
|
||||||
}
|
}
|
||||||
#if LWIP_VERSION_MAJOR != 1
|
|
||||||
const uint8_t* rawIPAddress(const IPAddress& addr) {
|
const uint8_t* rawIPAddress(const IPAddress& addr) {
|
||||||
return addr.raw_address();
|
return addr.raw_address();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -100,6 +100,7 @@ void *createBearsslHmac(const br_hash_class *hashType, const void *data, const s
|
|||||||
|
|
||||||
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)
|
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);
|
assert(1 <= hmacLength && hmacLength <= hashTypeNaturalLength);
|
||||||
|
|
||||||
uint8_t hmac[hmacLength];
|
uint8_t hmac[hmacLength];
|
||||||
@ -152,6 +153,7 @@ void *createBearsslHmacCT(const br_hash_class *hashType, const void *data, const
|
|||||||
|
|
||||||
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)
|
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);
|
assert(1 <= hmacLength && hmacLength <= hashTypeNaturalLength);
|
||||||
|
|
||||||
uint8_t hmac[hmacLength];
|
uint8_t hmac[hmacLength];
|
||||||
|
@ -30,14 +30,15 @@ void EspClass::getHeapStats(uint32_t* hfree, uint16_t* hmax, uint8_t* hfrag)
|
|||||||
// 100 * (1 - sqrt(sum(hole-size²)) / sum(hole-size))
|
// 100 * (1 - sqrt(sum(hole-size²)) / sum(hole-size))
|
||||||
|
|
||||||
umm_info(NULL, false);
|
umm_info(NULL, false);
|
||||||
uint8_t block_size = umm_block_size();
|
|
||||||
|
uint32_t free_size = umm_free_heap_size_core(umm_get_current_heap());
|
||||||
if (hfree)
|
if (hfree)
|
||||||
*hfree = ummHeapInfo.freeBlocks * block_size;
|
*hfree = free_size;
|
||||||
if (hmax)
|
if (hmax)
|
||||||
*hmax = (uint16_t)ummHeapInfo.maxFreeContiguousBlocks * block_size;
|
*hmax = (uint16_t)umm_max_block_size_core(umm_get_current_heap());
|
||||||
if (hfrag) {
|
if (hfrag) {
|
||||||
if (ummHeapInfo.freeBlocks) {
|
if (free_size) {
|
||||||
*hfrag = 100 - (sqrt32(ummHeapInfo.freeBlocksSquared) * 100) / ummHeapInfo.freeBlocks;
|
*hfrag = umm_fragmentation_metric_core(umm_get_current_heap());
|
||||||
} else {
|
} else {
|
||||||
*hfrag = 0;
|
*hfrag = 0;
|
||||||
}
|
}
|
||||||
@ -46,11 +47,5 @@ void EspClass::getHeapStats(uint32_t* hfree, uint16_t* hmax, uint8_t* hfrag)
|
|||||||
|
|
||||||
uint8_t EspClass::getHeapFragmentation()
|
uint8_t EspClass::getHeapFragmentation()
|
||||||
{
|
{
|
||||||
#ifdef UMM_INLINE_METRICS
|
return (uint8_t)umm_fragmentation_metric();
|
||||||
return (uint8_t)umm_fragmentation_metric();
|
|
||||||
#else
|
|
||||||
uint8_t hfrag;
|
|
||||||
getHeapStats(nullptr, nullptr, &hfrag);
|
|
||||||
return hfrag;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <user_interface.h>
|
#include <user_interface.h>
|
||||||
#include <core_version.h>
|
#include <core_version.h>
|
||||||
#include <lwip/init.h> // LWIP_VERSION_*
|
|
||||||
#include <lwipopts.h> // LWIP_HASH_STR (lwip2)
|
#include <lwipopts.h> // LWIP_HASH_STR (lwip2)
|
||||||
#include <bearssl/bearssl_git.h> // BEARSSL_GIT short hash
|
#include <bearssl/bearssl_git.h> // BEARSSL_GIT short hash
|
||||||
|
|
||||||
@ -29,13 +28,11 @@
|
|||||||
#define STR(x) STRHELPER(x) // stringifier
|
#define STR(x) STRHELPER(x) // stringifier
|
||||||
|
|
||||||
static const char arduino_esp8266_git_ver [] PROGMEM = "/Core:" STR(ARDUINO_ESP8266_GIT_DESC) "=";
|
static const char arduino_esp8266_git_ver [] PROGMEM = "/Core:" STR(ARDUINO_ESP8266_GIT_DESC) "=";
|
||||||
#if LWIP_VERSION_MAJOR > 1
|
|
||||||
#if LWIP_IPV6
|
#if LWIP_IPV6
|
||||||
static const char lwip_version [] PROGMEM = "/lwIP:IPv6+" LWIP_HASH_STR;
|
static const char lwip_version [] PROGMEM = "/lwIP:IPv6+" LWIP_HASH_STR;
|
||||||
#else
|
#else
|
||||||
static const char lwip_version [] PROGMEM = "/lwIP:" LWIP_HASH_STR;
|
static const char lwip_version [] PROGMEM = "/lwIP:" LWIP_HASH_STR;
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
static const char bearssl_version [] PROGMEM = "/BearSSL:" STR(BEARSSL_GIT);
|
static const char bearssl_version [] PROGMEM = "/BearSSL:" STR(BEARSSL_GIT);
|
||||||
|
|
||||||
String EspClass::getFullVersion() {
|
String EspClass::getFullVersion() {
|
||||||
@ -45,23 +42,7 @@ String EspClass::getFullVersion() {
|
|||||||
s += system_get_sdk_version();
|
s += system_get_sdk_version();
|
||||||
s += FPSTR(arduino_esp8266_git_ver);
|
s += FPSTR(arduino_esp8266_git_ver);
|
||||||
s += String(esp8266::coreVersionNumeric());
|
s += String(esp8266::coreVersionNumeric());
|
||||||
#if LWIP_VERSION_MAJOR == 1
|
|
||||||
s += F("/lwIP:");
|
|
||||||
s += LWIP_VERSION_MAJOR;
|
|
||||||
s += '.';
|
|
||||||
s += LWIP_VERSION_MINOR;
|
|
||||||
s += '.';
|
|
||||||
s += LWIP_VERSION_REVISION;
|
|
||||||
#if LWIP_VERSION_IS_DEVELOPMENT
|
|
||||||
s += F("-dev");
|
|
||||||
#endif
|
|
||||||
#if LWIP_VERSION_IS_RC
|
|
||||||
s += F("rc");
|
|
||||||
s += String(LWIP_VERSION_RC);
|
|
||||||
#endif
|
|
||||||
#else // LWIP_VERSION_MAJOR != 1
|
|
||||||
s += FPSTR(lwip_version);
|
s += FPSTR(lwip_version);
|
||||||
#endif // LWIP_VERSION_MAJOR != 1
|
|
||||||
s += FPSTR(bearssl_version);
|
s += FPSTR(bearssl_version);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
|
@ -26,7 +26,12 @@
|
|||||||
#include "MD5Builder.h"
|
#include "MD5Builder.h"
|
||||||
#include "umm_malloc/umm_malloc.h"
|
#include "umm_malloc/umm_malloc.h"
|
||||||
#include "cont.h"
|
#include "cont.h"
|
||||||
|
|
||||||
#include "coredecls.h"
|
#include "coredecls.h"
|
||||||
|
#include "umm_malloc/umm_malloc.h"
|
||||||
|
// #include "core_esp8266_vm.h"
|
||||||
|
#include <pgmspace.h>
|
||||||
|
#include "reboot_uart_dwnld.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "user_interface.h"
|
#include "user_interface.h"
|
||||||
@ -40,11 +45,6 @@ extern struct rst_info resetInfo;
|
|||||||
#ifndef PUYA_SUPPORT
|
#ifndef PUYA_SUPPORT
|
||||||
#define PUYA_SUPPORT 1
|
#define PUYA_SUPPORT 1
|
||||||
#endif
|
#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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User-defined Literals
|
* User-defined Literals
|
||||||
@ -204,6 +204,15 @@ void EspClass::restart(void)
|
|||||||
esp_yield();
|
esp_yield();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void EspClass::rebootIntoUartDownloadMode()
|
||||||
|
{
|
||||||
|
wdtDisable();
|
||||||
|
/* disable hardware watchdog */
|
||||||
|
CLEAR_PERI_REG_MASK(PERIPHS_HW_WDT, 0x1);
|
||||||
|
|
||||||
|
esp8266RebootIntoUartDownloadMode();
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t EspClass::getVcc(void)
|
uint16_t EspClass::getVcc(void)
|
||||||
{
|
{
|
||||||
esp8266::InterruptLock lock;
|
esp8266::InterruptLock lock;
|
||||||
@ -264,13 +273,6 @@ uint8_t EspClass::getBootMode(void)
|
|||||||
return system_get_boot_mode();
|
return system_get_boot_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef F_CPU
|
|
||||||
uint8_t EspClass::getCpuFreqMHz(void)
|
|
||||||
{
|
|
||||||
return system_get_cpu_freq();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint32_t EspClass::getFlashChipId(void)
|
uint32_t EspClass::getFlashChipId(void)
|
||||||
{
|
{
|
||||||
static uint32_t flash_chip_id = 0;
|
static uint32_t flash_chip_id = 0;
|
||||||
@ -452,22 +454,24 @@ bool EspClass::checkFlashConfig(bool needsEquals) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These are defined in the linker script, and filled in by the elf2bin.py util
|
||||||
|
extern "C" uint32_t __crc_len;
|
||||||
|
extern "C" uint32_t __crc_val;
|
||||||
|
|
||||||
bool EspClass::checkFlashCRC() {
|
bool EspClass::checkFlashCRC() {
|
||||||
// The CRC and total length are placed in extra space at the end of the 4K chunk
|
// Dummy CRC fill
|
||||||
// of flash occupied by the bootloader. If the bootloader grows to >4K-8 bytes,
|
|
||||||
// we'll need to adjust this.
|
|
||||||
uint32_t flashsize = *((uint32_t*)(0x40200000 + 4088)); // Start of PROGMEM plus 4K-8
|
|
||||||
uint32_t flashcrc = *((uint32_t*)(0x40200000 + 4092)); // Start of PROGMEM plus 4K-4
|
|
||||||
uint32_t z[2];
|
uint32_t z[2];
|
||||||
z[0] = z[1] = 0;
|
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
|
// Start the checksum
|
||||||
uint32_t crc = crc32((const void*)0x40200000, 4096-8, 0xffffffff);
|
uint32_t crc = crc32((const void*)0x40200000, firstPart, 0xffffffff);
|
||||||
// Pretend the 2 words of crc/len are zero to be idempotent
|
// Pretend the 2 words of crc/len are zero to be idempotent
|
||||||
crc = crc32(z, 8, crc);
|
crc = crc32(z, 8, crc);
|
||||||
// Finish the CRC calculation over the rest of flash
|
// Finish the CRC calculation over the rest of flash
|
||||||
crc = crc32((const void*)0x40201000, flashsize-4096, crc);
|
crc = crc32((const void*)(0x40200000 + firstPart + 8), __crc_len - (firstPart + 8), crc);
|
||||||
return crc == flashcrc;
|
return crc == __crc_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -526,45 +530,45 @@ uint8_t *EspClass::random(uint8_t *resultArray, const size_t outputSizeBytes) co
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The ESP32 Technical Reference Manual v4.1 chapter 24 has the following to say about random number generation (no information found for ESP8266):
|
* 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.
|
* "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.
|
* 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 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).
|
* 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.
|
* 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,
|
* 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).
|
* has been tested using the Dieharder Random Number Testsuite (version 3.31.1).
|
||||||
* The sample passed all tests."
|
* 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.
|
* 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.
|
* 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 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.
|
* 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.
|
* 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.
|
* Thus only delayMicroseconds() is used below.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
constexpr uint8_t cooldownMicros = 2;
|
constexpr uint8_t cooldownMicros = 2;
|
||||||
static uint32_t lastCalledMicros = micros() - cooldownMicros;
|
static uint32_t lastCalledMicros = micros() - cooldownMicros;
|
||||||
|
|
||||||
uint32_t randomNumber = 0;
|
uint32_t randomNumber = 0;
|
||||||
|
|
||||||
for(size_t byteIndex = 0; byteIndex < outputSizeBytes; ++byteIndex)
|
for(size_t byteIndex = 0; byteIndex < outputSizeBytes; ++byteIndex)
|
||||||
{
|
{
|
||||||
if(byteIndex % 4 == 0)
|
if(byteIndex % 4 == 0)
|
||||||
{
|
{
|
||||||
// Old random number has been used up (random number could be exactly 0, so we can't check for that)
|
// 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;
|
uint32_t timeSinceLastCall = micros() - lastCalledMicros;
|
||||||
if(timeSinceLastCall < cooldownMicros)
|
if(timeSinceLastCall < cooldownMicros)
|
||||||
delayMicroseconds(cooldownMicros - timeSinceLastCall);
|
delayMicroseconds(cooldownMicros - timeSinceLastCall);
|
||||||
|
|
||||||
randomNumber = RANDOM_REG32;
|
randomNumber = RANDOM_REG32;
|
||||||
lastCalledMicros = micros();
|
lastCalledMicros = micros();
|
||||||
}
|
}
|
||||||
|
|
||||||
resultArray[byteIndex] = randomNumber;
|
resultArray[byteIndex] = randomNumber;
|
||||||
randomNumber >>= 8;
|
randomNumber >>= 8;
|
||||||
}
|
}
|
||||||
@ -673,11 +677,14 @@ static SpiFlashOpResult spi_flash_write_puya(uint32_t offset, uint32_t *data, si
|
|||||||
if (data == nullptr) {
|
if (data == nullptr) {
|
||||||
return SPI_FLASH_RESULT_ERR;
|
return SPI_FLASH_RESULT_ERR;
|
||||||
}
|
}
|
||||||
|
if (size % 4 != 0) {
|
||||||
|
return SPI_FLASH_RESULT_ERR;
|
||||||
|
}
|
||||||
// PUYA flash chips need to read existing data, update in memory and write modified data again.
|
// PUYA flash chips need to read existing data, update in memory and write modified data again.
|
||||||
static uint32_t *flash_write_puya_buf = nullptr;
|
static uint32_t *flash_write_puya_buf = nullptr;
|
||||||
|
|
||||||
if (flash_write_puya_buf == nullptr) {
|
if (flash_write_puya_buf == nullptr) {
|
||||||
flash_write_puya_buf = (uint32_t*) malloc(PUYA_BUFFER_SIZE);
|
flash_write_puya_buf = (uint32_t*) malloc(FLASH_PAGE_SIZE);
|
||||||
// No need to ever free this, since the flash chip will never change at runtime.
|
// No need to ever free this, since the flash chip will never change at runtime.
|
||||||
if (flash_write_puya_buf == nullptr) {
|
if (flash_write_puya_buf == nullptr) {
|
||||||
// Memory could not be allocated.
|
// Memory could not be allocated.
|
||||||
@ -691,9 +698,9 @@ static SpiFlashOpResult spi_flash_write_puya(uint32_t offset, uint32_t *data, si
|
|||||||
uint32_t pos = offset;
|
uint32_t pos = offset;
|
||||||
while (bytesLeft > 0 && rc == SPI_FLASH_RESULT_OK) {
|
while (bytesLeft > 0 && rc == SPI_FLASH_RESULT_OK) {
|
||||||
size_t bytesNow = bytesLeft;
|
size_t bytesNow = bytesLeft;
|
||||||
if (bytesNow > PUYA_BUFFER_SIZE) {
|
if (bytesNow > FLASH_PAGE_SIZE) {
|
||||||
bytesNow = PUYA_BUFFER_SIZE;
|
bytesNow = FLASH_PAGE_SIZE;
|
||||||
bytesLeft -= PUYA_BUFFER_SIZE;
|
bytesLeft -= FLASH_PAGE_SIZE;
|
||||||
} else {
|
} else {
|
||||||
bytesLeft = 0;
|
bytesLeft = 0;
|
||||||
}
|
}
|
||||||
@ -712,23 +719,240 @@ static SpiFlashOpResult spi_flash_write_puya(uint32_t offset, uint32_t *data, si
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool EspClass::flashWrite(uint32_t offset, uint32_t *data, size_t size) {
|
bool EspClass::flashReplaceBlock(uint32_t address, const uint8_t *value, uint32_t byteCount) {
|
||||||
SpiFlashOpResult rc = SPI_FLASH_RESULT_OK;
|
uint32_t alignedAddress = (address & ~3);
|
||||||
|
uint32_t alignmentOffset = address - alignedAddress;
|
||||||
|
|
||||||
|
if (alignedAddress != ((address + byteCount - 1) & ~3)) {
|
||||||
|
// Only one 4 byte block is supported
|
||||||
|
return false;
|
||||||
|
}
|
||||||
#if PUYA_SUPPORT
|
#if PUYA_SUPPORT
|
||||||
if (getFlashChipVendorId() == SPI_FLASH_VENDOR_PUYA) {
|
if (getFlashChipVendorId() == SPI_FLASH_VENDOR_PUYA) {
|
||||||
rc = spi_flash_write_puya(offset, data, size);
|
uint8_t tempData[4] __attribute__((aligned(4)));
|
||||||
|
if (spi_flash_read(alignedAddress, (uint32_t *)tempData, 4) != SPI_FLASH_RESULT_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < byteCount; i++) {
|
||||||
|
tempData[i + alignmentOffset] &= value[i];
|
||||||
|
}
|
||||||
|
if (spi_flash_write(alignedAddress, (uint32_t *)tempData, 4) != SPI_FLASH_RESULT_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#endif
|
#endif // PUYA_SUPPORT
|
||||||
{
|
{
|
||||||
rc = spi_flash_write(offset, data, size);
|
uint32_t tempData;
|
||||||
|
if (spi_flash_read(alignedAddress, &tempData, 4) != SPI_FLASH_RESULT_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memcpy((uint8_t *)&tempData + alignmentOffset, value, byteCount);
|
||||||
|
if (spi_flash_write(alignedAddress, &tempData, 4) != SPI_FLASH_RESULT_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return rc == SPI_FLASH_RESULT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EspClass::flashRead(uint32_t offset, uint32_t *data, size_t size) {
|
bool EspClass::flashWrite(uint32_t address, const uint8_t *data, size_t size) {
|
||||||
auto rc = spi_flash_read(offset, (uint32_t*) data, size);
|
if (size == 0) {
|
||||||
return rc == SPI_FLASH_RESULT_OK;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t sizeLeft = size & ~3;
|
||||||
|
size_t currentOffset = 0;
|
||||||
|
|
||||||
|
if (sizeLeft) {
|
||||||
|
if ((uintptr_t)data % 4 != 0) {
|
||||||
|
size_t written = flashWriteUnalignedMemory(address, data, size);
|
||||||
|
if (!written) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset += written;
|
||||||
|
sizeLeft -= written;
|
||||||
|
} else {
|
||||||
|
bool pageBreak = ((address % 4) != 0 && (address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1) / FLASH_PAGE_SIZE));
|
||||||
|
|
||||||
|
if (pageBreak) {
|
||||||
|
while (sizeLeft) {
|
||||||
|
// We cannot cross page boundary, but the write must be 4 byte aligned,
|
||||||
|
// so this is the maximum amount we can write
|
||||||
|
size_t pageBoundary = (FLASH_PAGE_SIZE - ((address + currentOffset) % FLASH_PAGE_SIZE)) & ~3;
|
||||||
|
|
||||||
|
if (sizeLeft > pageBoundary) {
|
||||||
|
// Aligned write up to page boundary
|
||||||
|
if (!flashWrite(address + currentOffset, (uint32_t *)(data + currentOffset), pageBoundary)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset += pageBoundary;
|
||||||
|
sizeLeft -= pageBoundary;
|
||||||
|
// Cross the page boundary
|
||||||
|
if (!flashWritePageBreak(address + currentOffset, data + currentOffset, 4)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset += 4;
|
||||||
|
sizeLeft -= 4;
|
||||||
|
} else {
|
||||||
|
// We do not cross page boundary
|
||||||
|
if (!flashWrite(address + currentOffset, (uint32_t *)(data + currentOffset), sizeLeft)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset += sizeLeft;
|
||||||
|
sizeLeft = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pointer is properly aligned and write does not cross page boundary,
|
||||||
|
// so use aligned write
|
||||||
|
if (!flashWrite(address, (uint32_t *)data, sizeLeft)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset = sizeLeft;
|
||||||
|
sizeLeft = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sizeLeft = size - currentOffset;
|
||||||
|
if (sizeLeft > 0) {
|
||||||
|
// Size was not aligned, so we have some bytes left to write, we also need to recheck for
|
||||||
|
// page boundary crossing
|
||||||
|
bool pageBreak = ((address % 4) != 0 && (address / FLASH_PAGE_SIZE) != ((address + sizeLeft - 1) / FLASH_PAGE_SIZE));
|
||||||
|
|
||||||
|
if (pageBreak) {
|
||||||
|
// Cross the page boundary
|
||||||
|
if (!flashWritePageBreak(address + currentOffset, data + currentOffset, sizeLeft)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Just write partial block
|
||||||
|
flashReplaceBlock(address + currentOffset, data + currentOffset, sizeLeft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::flashRead(uint32_t address, uint8_t *data, size_t size) {
|
||||||
|
size_t sizeAligned = size & ~3;
|
||||||
|
size_t currentOffset = 0;
|
||||||
|
|
||||||
|
if ((uintptr_t)data % 4 != 0) {
|
||||||
|
uint32_t alignedData[FLASH_PAGE_SIZE / sizeof(uint32_t)] __attribute__((aligned(4)));
|
||||||
|
size_t sizeLeft = sizeAligned;
|
||||||
|
|
||||||
|
while (sizeLeft) {
|
||||||
|
size_t willCopy = std::min(sizeLeft, sizeof(alignedData));
|
||||||
|
// We read to our aligned buffer and then copy to data
|
||||||
|
if (!flashRead(address + currentOffset, alignedData, willCopy))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memcpy(data + currentOffset, alignedData, willCopy);
|
||||||
|
sizeLeft -= willCopy;
|
||||||
|
currentOffset += willCopy;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pointer is properly aligned, so use aligned read
|
||||||
|
if (!flashRead(address, (uint32_t *)data, sizeAligned)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
currentOffset = sizeAligned;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentOffset < size) {
|
||||||
|
uint32_t tempData;
|
||||||
|
if (spi_flash_read(address + currentOffset, &tempData, 4) != SPI_FLASH_RESULT_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memcpy((uint8_t *)data + currentOffset, &tempData, size - currentOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::flashRead(uint32_t address, uint32_t *data, size_t size) {
|
||||||
|
if ((uintptr_t)data % 4 != 0 || size % 4 != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return (spi_flash_read(address, data, size) == SPI_FLASH_RESULT_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
String EspClass::getSketchMD5()
|
String EspClass::getSketchMD5()
|
||||||
@ -739,17 +963,17 @@ String EspClass::getSketchMD5()
|
|||||||
}
|
}
|
||||||
uint32_t lengthLeft = getSketchSize();
|
uint32_t lengthLeft = getSketchSize();
|
||||||
const size_t bufSize = 512;
|
const size_t bufSize = 512;
|
||||||
std::unique_ptr<uint8_t[]> buf(new uint8_t[bufSize]);
|
std::unique_ptr<uint8_t[]> buf(new (std::nothrow) uint8_t[bufSize]);
|
||||||
uint32_t offset = 0;
|
uint32_t offset = 0;
|
||||||
if(!buf.get()) {
|
if(!buf.get()) {
|
||||||
return String();
|
return emptyString;
|
||||||
}
|
}
|
||||||
MD5Builder md5;
|
MD5Builder md5;
|
||||||
md5.begin();
|
md5.begin();
|
||||||
while( lengthLeft > 0) {
|
while( lengthLeft > 0) {
|
||||||
size_t readBytes = (lengthLeft < bufSize) ? lengthLeft : bufSize;
|
size_t readBytes = (lengthLeft < bufSize) ? lengthLeft : bufSize;
|
||||||
if (!flashRead(offset, reinterpret_cast<uint32_t*>(buf.get()), (readBytes + 3) & ~3)) {
|
if (!flashRead(offset, reinterpret_cast<uint32_t*>(buf.get()), (readBytes + 3) & ~3)) {
|
||||||
return String();
|
return emptyString;
|
||||||
}
|
}
|
||||||
md5.add(buf.get(), readBytes);
|
md5.add(buf.get(), readBytes);
|
||||||
lengthLeft -= readBytes;
|
lengthLeft -= readBytes;
|
||||||
@ -759,3 +983,62 @@ String EspClass::getSketchMD5()
|
|||||||
result = md5.toString();
|
result = md5.toString();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EspClass::enableVM()
|
||||||
|
{
|
||||||
|
#ifdef UMM_HEAP_EXTERNAL
|
||||||
|
if (!vmEnabled)
|
||||||
|
install_vm_exception_handler();
|
||||||
|
vmEnabled = true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspClass::setExternalHeap()
|
||||||
|
{
|
||||||
|
#ifdef UMM_HEAP_EXTERNAL
|
||||||
|
if (vmEnabled) {
|
||||||
|
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 (vmEnabled) {
|
||||||
|
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 (vmEnabled) {
|
||||||
|
if (!umm_pop_heap()) {
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#elif defined(UMM_HEAP_IRAM)
|
||||||
|
if (!umm_pop_heap()) {
|
||||||
|
panic();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#define ESP_H
|
#define ESP_H
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include "core_esp8266_features.h"
|
||||||
#include "spi_vendors.h"
|
#include "spi_vendors.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,6 +103,12 @@ class EspClass {
|
|||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
void restart();
|
void restart();
|
||||||
|
/**
|
||||||
|
* @brief When calling this method the ESP8266 reboots into the UART download mode without
|
||||||
|
* the need of any external wiring. This is the same mode which can also be entered by
|
||||||
|
* pulling GPIO0=low, GPIO2=high, GPIO15=low and resetting the ESP8266.
|
||||||
|
*/
|
||||||
|
[[noreturn]] void rebootIntoUartDownloadMode();
|
||||||
|
|
||||||
uint16_t getVcc();
|
uint16_t getVcc();
|
||||||
uint32_t getChipId();
|
uint32_t getChipId();
|
||||||
@ -122,13 +129,12 @@ class EspClass {
|
|||||||
uint8_t getBootMode();
|
uint8_t getBootMode();
|
||||||
|
|
||||||
#if defined(F_CPU) || defined(CORE_MOCK)
|
#if defined(F_CPU) || defined(CORE_MOCK)
|
||||||
constexpr uint8_t getCpuFreqMHz() const
|
constexpr
|
||||||
{
|
|
||||||
return clockCyclesPerMicrosecond();
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
uint8_t getCpuFreqMHz();
|
|
||||||
#endif
|
#endif
|
||||||
|
inline uint8_t getCpuFreqMHz() const __attribute__((always_inline))
|
||||||
|
{
|
||||||
|
return esp_get_cpu_freq_mhz();
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t getFlashChipId();
|
uint32_t getFlashChipId();
|
||||||
uint8_t getFlashChipVendorId();
|
uint8_t getFlashChipVendorId();
|
||||||
@ -150,8 +156,48 @@ class EspClass {
|
|||||||
bool checkFlashCRC();
|
bool checkFlashCRC();
|
||||||
|
|
||||||
bool flashEraseSector(uint32_t sector);
|
bool flashEraseSector(uint32_t sector);
|
||||||
bool flashWrite(uint32_t offset, uint32_t *data, size_t size);
|
/**
|
||||||
bool flashRead(uint32_t offset, uint32_t *data, size_t size);
|
* @brief Write @a size bytes from @a data to flash at @a address
|
||||||
|
* This overload requires @a data and @a size to be always 4 byte aligned and
|
||||||
|
* @a address to be 4 byte aligned if the write crossess page boundary,
|
||||||
|
* but guarantees no overhead (except on PUYA flashes)
|
||||||
|
* @param address address on flash where write should start, 4 byte alignment is conditional
|
||||||
|
* @param data input buffer, must be 4-byte aligned
|
||||||
|
* @param size amount of data, must be a multiple of 4
|
||||||
|
* @return bool result of operation
|
||||||
|
* @retval true success
|
||||||
|
* @retval false failure to write to flash or incorrect alignment of params
|
||||||
|
*/
|
||||||
|
bool flashWrite(uint32_t address, const uint32_t *data, size_t size);
|
||||||
|
/**
|
||||||
|
* @brief Write @a size bytes from @a data to flash at @a address
|
||||||
|
* This overload handles all misalignment cases
|
||||||
|
* @param address address on flash where write should start
|
||||||
|
* @param data input buffer, passing unaligned memory will cause significant stack usage
|
||||||
|
* @param size amount of data, passing not multiple of 4 will cause additional reads and writes
|
||||||
|
* @return bool result of operation
|
||||||
|
*/
|
||||||
|
bool flashWrite(uint32_t address, const uint8_t *data, size_t size);
|
||||||
|
/**
|
||||||
|
* @brief Read @a size bytes to @a data to flash at @a address
|
||||||
|
* This overload requires @a data and @a size to be 4 byte aligned
|
||||||
|
* @param address address on flash where read should start
|
||||||
|
* @param data input buffer, must be 4-byte aligned
|
||||||
|
* @param size amount of data, must be a multiple of 4
|
||||||
|
* @return bool result of operation
|
||||||
|
* @retval true success
|
||||||
|
* @retval false failure to read from flash or incorrect alignment of params
|
||||||
|
*/
|
||||||
|
bool flashRead(uint32_t address, uint32_t *data, size_t size);
|
||||||
|
/**
|
||||||
|
* @brief Read @a size bytes to @a data to flash at @a address
|
||||||
|
* This overload handles all misalignment cases
|
||||||
|
* @param address address on flash where read should start
|
||||||
|
* @param data input buffer, passing unaligned memory will cause significant stack usage
|
||||||
|
* @param size amount of data, passing not multiple of 4 will cause additional read
|
||||||
|
* @return bool result of operation
|
||||||
|
*/
|
||||||
|
bool flashRead(uint32_t address, uint8_t *data, size_t size);
|
||||||
|
|
||||||
uint32_t getSketchSize();
|
uint32_t getSketchSize();
|
||||||
String getSketchMD5();
|
String getSketchMD5();
|
||||||
@ -167,21 +213,88 @@ class EspClass {
|
|||||||
uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes) const;
|
uint8_t *random(uint8_t *resultArray, const size_t outputSizeBytes) const;
|
||||||
uint32_t random() const;
|
uint32_t random() const;
|
||||||
|
|
||||||
#ifndef CORE_MOCK
|
#if !defined(CORE_MOCK)
|
||||||
inline uint32_t getCycleCount() __attribute__((always_inline));
|
inline uint32_t getCycleCount() __attribute__((always_inline))
|
||||||
|
{
|
||||||
|
return esp_get_cycle_count();
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
uint32_t getCycleCount();
|
uint32_t getCycleCount();
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifndef CORE_MOCK
|
|
||||||
|
|
||||||
uint32_t EspClass::getCycleCount()
|
|
||||||
{
|
|
||||||
return esp_get_cycle_count();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // !defined(CORE_MOCK)
|
#endif // !defined(CORE_MOCK)
|
||||||
|
/**
|
||||||
|
* @brief Installs VM exception handler to support External memory (Experimental)
|
||||||
|
*
|
||||||
|
* @param none
|
||||||
|
* @return none
|
||||||
|
*/
|
||||||
|
void enableVM();
|
||||||
|
/**
|
||||||
|
* @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:
|
||||||
|
#ifdef UMM_HEAP_EXTERNAL
|
||||||
|
bool vmEnabled = false;
|
||||||
|
#endif
|
||||||
|
/**
|
||||||
|
* @brief Replaces @a byteCount bytes of a 4 byte block on flash
|
||||||
|
*
|
||||||
|
* @param address flash address
|
||||||
|
* @param value buffer with data
|
||||||
|
* @param byteCount number of bytes to replace
|
||||||
|
* @return bool result of operation
|
||||||
|
* @retval true success
|
||||||
|
* @retval false failed to read/write or invalid args
|
||||||
|
*/
|
||||||
|
bool flashReplaceBlock(uint32_t address, const uint8_t *value, uint32_t byteCount);
|
||||||
|
/**
|
||||||
|
* @brief Write up to @a size bytes from @a data to flash at @a address
|
||||||
|
* This function takes case of unaligned memory acces by copying @a data to a temporary buffer,
|
||||||
|
* it also takes care of page boundary crossing see @a flashWritePageBreak as to why it's done.
|
||||||
|
* Less than @a size bytes may be written, due to 4 byte alignment requirement of spi_flash_write
|
||||||
|
* @param address address on flash where write should start
|
||||||
|
* @param data input buffer
|
||||||
|
* @param size amount of data
|
||||||
|
* @return size_t amount of data written, 0 on failure
|
||||||
|
*/
|
||||||
|
size_t flashWriteUnalignedMemory(uint32_t address, const uint8_t *data, size_t size);
|
||||||
|
/**
|
||||||
|
* @brief Splits up to 4 bytes into 4 byte blocks and writes them to flash
|
||||||
|
* We need this since spi_flash_write cannot handle writing over a page boundary with unaligned offset
|
||||||
|
* i.e. spi_flash_write(254, data, 4) will fail, also we cannot write less bytes as in
|
||||||
|
* spi_flash_write(254, data, 2) since it will be extended internally to 4 bytes and fail
|
||||||
|
* @param address start of write
|
||||||
|
* @param data data to be written
|
||||||
|
* @param size amount of data, must be < 4
|
||||||
|
* @return bool result of operation
|
||||||
|
*/
|
||||||
|
bool flashWritePageBreak(uint32_t address, const uint8_t *data, size_t size);
|
||||||
|
};
|
||||||
|
|
||||||
extern EspClass ESP;
|
extern EspClass ESP;
|
||||||
|
|
||||||
|
@ -46,6 +46,14 @@ int File::available() {
|
|||||||
return _p->size() - _p->position();
|
return _p->size() - _p->position();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int File::availableForWrite() {
|
||||||
|
if (!_p)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return _p->availableForWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int File::read() {
|
int File::read() {
|
||||||
if (!_p)
|
if (!_p)
|
||||||
return -1;
|
return -1;
|
||||||
@ -60,7 +68,7 @@ int File::read() {
|
|||||||
|
|
||||||
size_t File::read(uint8_t* buf, size_t size) {
|
size_t File::read(uint8_t* buf, size_t size) {
|
||||||
if (!_p)
|
if (!_p)
|
||||||
return -1;
|
return 0;
|
||||||
|
|
||||||
return _p->read(buf, size);
|
return _p->read(buf, size);
|
||||||
}
|
}
|
||||||
@ -198,6 +206,7 @@ void File::setTimeCallback(time_t (*cb)(void)) {
|
|||||||
if (!_p)
|
if (!_p)
|
||||||
return;
|
return;
|
||||||
_p->setTimeCallback(cb);
|
_p->setTimeCallback(cb);
|
||||||
|
_timeCallback = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
File Dir::openFile(const char* mode) {
|
File Dir::openFile(const char* mode) {
|
||||||
@ -213,7 +222,7 @@ File Dir::openFile(const char* mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
File f(_impl->openFile(om, am), _baseFS);
|
File f(_impl->openFile(om, am), _baseFS);
|
||||||
f.setTimeCallback(timeCallback);
|
f.setTimeCallback(_timeCallback);
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,7 +288,7 @@ void Dir::setTimeCallback(time_t (*cb)(void)) {
|
|||||||
if (!_impl)
|
if (!_impl)
|
||||||
return;
|
return;
|
||||||
_impl->setTimeCallback(cb);
|
_impl->setTimeCallback(cb);
|
||||||
timeCallback = cb;
|
_timeCallback = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -296,7 +305,7 @@ bool FS::begin() {
|
|||||||
DEBUGV("#error: FS: no implementation");
|
DEBUGV("#error: FS: no implementation");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
_impl->setTimeCallback(timeCallback);
|
_impl->setTimeCallback(_timeCallback);
|
||||||
bool ret = _impl->begin();
|
bool ret = _impl->begin();
|
||||||
DEBUGV("%s\n", ret? "": "#error: FS could not start");
|
DEBUGV("%s\n", ret? "": "#error: FS could not start");
|
||||||
return ret;
|
return ret;
|
||||||
@ -359,7 +368,7 @@ File FS::open(const char* path, const char* mode) {
|
|||||||
return File();
|
return File();
|
||||||
}
|
}
|
||||||
File f(_impl->open(path, om, am), this);
|
File f(_impl->open(path, om, am), this);
|
||||||
f.setTimeCallback(timeCallback);
|
f.setTimeCallback(_timeCallback);
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,7 +389,7 @@ Dir FS::openDir(const char* path) {
|
|||||||
}
|
}
|
||||||
DirImplPtr p = _impl->openDir(path);
|
DirImplPtr p = _impl->openDir(path);
|
||||||
Dir d(p, this);
|
Dir d(p, this);
|
||||||
d.setTimeCallback(timeCallback);
|
d.setTimeCallback(_timeCallback);
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,10 +441,18 @@ bool FS::rename(const String& pathFrom, const String& pathTo) {
|
|||||||
return rename(pathFrom.c_str(), pathTo.c_str());
|
return rename(pathFrom.c_str(), pathTo.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
time_t FS::getCreationTime() {
|
||||||
|
if (!_impl) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return _impl->getCreationTime();
|
||||||
|
}
|
||||||
|
|
||||||
void FS::setTimeCallback(time_t (*cb)(void)) {
|
void FS::setTimeCallback(time_t (*cb)(void)) {
|
||||||
if (!_impl)
|
if (!_impl)
|
||||||
return;
|
return;
|
||||||
_impl->setTimeCallback(cb);
|
_impl->setTimeCallback(cb);
|
||||||
|
_timeCallback = cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ public:
|
|||||||
// Print methods:
|
// Print methods:
|
||||||
size_t write(uint8_t) override;
|
size_t write(uint8_t) override;
|
||||||
size_t write(const uint8_t *buf, size_t size) override;
|
size_t write(const uint8_t *buf, size_t size) override;
|
||||||
|
int availableForWrite() override;
|
||||||
|
|
||||||
// Stream methods:
|
// Stream methods:
|
||||||
int available() override;
|
int available() override;
|
||||||
@ -117,6 +118,7 @@ public:
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
FileImplPtr _p;
|
FileImplPtr _p;
|
||||||
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
|
|
||||||
// Arduino SD class emulation
|
// Arduino SD class emulation
|
||||||
std::shared_ptr<Dir> _fakeDir;
|
std::shared_ptr<Dir> _fakeDir;
|
||||||
@ -144,7 +146,7 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
DirImplPtr _impl;
|
DirImplPtr _impl;
|
||||||
FS *_baseFS;
|
FS *_baseFS;
|
||||||
time_t (*timeCallback)(void) = nullptr;
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Backwards compatible, <4GB filesystem usage
|
// Backwards compatible, <4GB filesystem usage
|
||||||
@ -197,7 +199,7 @@ public:
|
|||||||
class FS
|
class FS
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FS(FSImplPtr impl) : _impl(impl) { timeCallback = _defaultTimeCB; }
|
FS(FSImplPtr impl) : _impl(impl) { _timeCallback = _defaultTimeCB; }
|
||||||
|
|
||||||
bool setConfig(const FSConfig &cfg);
|
bool setConfig(const FSConfig &cfg);
|
||||||
|
|
||||||
@ -233,13 +235,15 @@ public:
|
|||||||
bool gc();
|
bool gc();
|
||||||
bool check();
|
bool check();
|
||||||
|
|
||||||
|
time_t getCreationTime();
|
||||||
|
|
||||||
void setTimeCallback(time_t (*cb)(void));
|
void setTimeCallback(time_t (*cb)(void));
|
||||||
|
|
||||||
friend class ::SDClass; // More of a frenemy, but SD needs internal implementation to get private FAT bits
|
friend class ::SDClass; // More of a frenemy, but SD needs internal implementation to get private FAT bits
|
||||||
protected:
|
protected:
|
||||||
FSImplPtr _impl;
|
FSImplPtr _impl;
|
||||||
FSImplPtr getImpl() { return _impl; }
|
FSImplPtr getImpl() { return _impl; }
|
||||||
time_t (*timeCallback)(void);
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
static time_t _defaultTimeCB(void) { return time(NULL); }
|
static time_t _defaultTimeCB(void) { return time(NULL); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ public:
|
|||||||
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
|
virtual bool seek(uint32_t pos, SeekMode mode) = 0;
|
||||||
virtual size_t position() const = 0;
|
virtual size_t position() const = 0;
|
||||||
virtual size_t size() const = 0;
|
virtual size_t size() const = 0;
|
||||||
|
virtual int availableForWrite() { return 0; }
|
||||||
virtual bool truncate(uint32_t size) = 0;
|
virtual bool truncate(uint32_t size) = 0;
|
||||||
virtual void close() = 0;
|
virtual void close() = 0;
|
||||||
virtual const char* name() const = 0;
|
virtual const char* name() const = 0;
|
||||||
@ -44,8 +45,8 @@ public:
|
|||||||
|
|
||||||
// Filesystems *may* support a timestamp per-file, so allow the user to override with
|
// 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
|
// their own callback for *this specific* file (as opposed to the FSImpl call of the
|
||||||
// same name. The default implementation simply returns time(&null)
|
// same name. The default implementation simply returns time(null)
|
||||||
virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; }
|
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
|
||||||
|
|
||||||
// Return the last written time for a file. Undefined when called on a writable file
|
// 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
|
// as the FS is allowed to return either the time of the last write() operation or the
|
||||||
@ -55,7 +56,7 @@ public:
|
|||||||
virtual time_t getCreationTime() { return 0; } // Default is to not support timestamps
|
virtual time_t getCreationTime() { return 0; } // Default is to not support timestamps
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
time_t (*timeCallback)(void) = nullptr;
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum OpenMode {
|
enum OpenMode {
|
||||||
@ -89,11 +90,11 @@ public:
|
|||||||
|
|
||||||
// Filesystems *may* support a timestamp per-file, so allow the user to override with
|
// 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
|
// their own callback for *this specific* file (as opposed to the FSImpl call of the
|
||||||
// same name. The default implementation simply returns time(&null)
|
// same name. The default implementation simply returns time(null)
|
||||||
virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; }
|
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
time_t (*timeCallback)(void) = nullptr;
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
class FSImpl {
|
class FSImpl {
|
||||||
@ -114,14 +115,15 @@ public:
|
|||||||
virtual bool rmdir(const char* path) = 0;
|
virtual bool rmdir(const char* path) = 0;
|
||||||
virtual bool gc() { return true; } // May not be implemented in all file systems.
|
virtual bool gc() { return true; } // May not be implemented in all file systems.
|
||||||
virtual bool check() { return true; } // May not be implemented in all file systems.
|
virtual 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
|
// 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
|
// their own callback for all files on this FS. The default implementation simply
|
||||||
// returns the present time as reported by time(&null)
|
// returns the present time as reported by time(null)
|
||||||
virtual void setTimeCallback(time_t (*cb)(void)) { timeCallback = cb; }
|
virtual void setTimeCallback(time_t (*cb)(void)) { _timeCallback = cb; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
time_t (*timeCallback)(void) = nullptr;
|
time_t (*_timeCallback)(void) = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace fs
|
} // namespace fs
|
||||||
|
@ -32,6 +32,14 @@
|
|||||||
#include "HardwareSerial.h"
|
#include "HardwareSerial.h"
|
||||||
#include "Esp.h"
|
#include "Esp.h"
|
||||||
|
|
||||||
|
|
||||||
|
// SerialEvent functions are weak, so when the user doesn't define them,
|
||||||
|
// the linker just sets their address to 0 (which is checked below).
|
||||||
|
// The Serialx_available is just a wrapper around Serialx.available(),
|
||||||
|
// but we can refer to it weakly so we don't pull in the entire
|
||||||
|
// HardwareSerial instance if the user doesn't also refer to it.
|
||||||
|
void serialEvent() __attribute__((weak));
|
||||||
|
|
||||||
HardwareSerial::HardwareSerial(int uart_nr)
|
HardwareSerial::HardwareSerial(int uart_nr)
|
||||||
: _uart_nr(uart_nr), _rx_size(256)
|
: _uart_nr(uart_nr), _rx_size(256)
|
||||||
{}
|
{}
|
||||||
@ -162,6 +170,14 @@ size_t HardwareSerial::readBytes(char* buffer, size_t size)
|
|||||||
|
|
||||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
|
||||||
HardwareSerial Serial(UART0);
|
HardwareSerial Serial(UART0);
|
||||||
|
|
||||||
|
// Executed at end of loop() processing when > 0 bytes available in the Serial port
|
||||||
|
void serialEventRun(void)
|
||||||
|
{
|
||||||
|
if (serialEvent && Serial.available()) {
|
||||||
|
serialEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL1)
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL1)
|
||||||
HardwareSerial Serial1(UART1);
|
HardwareSerial Serial1(UART1);
|
||||||
|
@ -150,7 +150,7 @@ public:
|
|||||||
{
|
{
|
||||||
return readBytes((char*)buffer, size);
|
return readBytes((char*)buffer, size);
|
||||||
}
|
}
|
||||||
int availableForWrite(void)
|
int availableForWrite(void) override
|
||||||
{
|
{
|
||||||
return static_cast<int>(uart_tx_free(_uart));
|
return static_cast<int>(uart_tx_free(_uart));
|
||||||
}
|
}
|
||||||
@ -207,4 +207,6 @@ protected:
|
|||||||
extern HardwareSerial Serial;
|
extern HardwareSerial Serial;
|
||||||
extern HardwareSerial Serial1;
|
extern HardwareSerial Serial1;
|
||||||
|
|
||||||
|
extern void serialEventRun(void) __attribute__((weak));
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -32,7 +32,7 @@ IPAddress::IPAddress() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IPAddress::isSet () const {
|
bool IPAddress::isSet () const {
|
||||||
return !ip_addr_isany(&_ip);
|
return !ip_addr_isany(&_ip) && ((*this) != IPADDR_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) {
|
IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) {
|
||||||
@ -180,9 +180,13 @@ bool IPAddress::isValid(const char* arg) {
|
|||||||
return IPAddress().fromString(arg);
|
return IPAddress().fromString(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
CONST IPAddress INADDR_ANY; // generic "0.0.0.0" for IPv4 & IPv6
|
const IPAddress INADDR_ANY; // generic "0.0.0.0" for IPv4 & IPv6
|
||||||
const IPAddress INADDR_NONE(255,255,255,255);
|
const IPAddress INADDR_NONE(255,255,255,255);
|
||||||
|
|
||||||
|
void IPAddress::clear() {
|
||||||
|
(*this) = INADDR_ANY;
|
||||||
|
}
|
||||||
|
|
||||||
/**************************************/
|
/**************************************/
|
||||||
|
|
||||||
#if LWIP_IPV6
|
#if LWIP_IPV6
|
||||||
|
@ -26,28 +26,18 @@
|
|||||||
|
|
||||||
#include <lwip/init.h>
|
#include <lwip/init.h>
|
||||||
#include <lwip/ip_addr.h>
|
#include <lwip/ip_addr.h>
|
||||||
|
#include <lwip/ip4_addr.h>
|
||||||
|
|
||||||
#if LWIP_VERSION_MAJOR == 1
|
|
||||||
// compatibility macros to make lwIP-v1 compiling lwIP-v2 API
|
|
||||||
#define LWIP_IPV6_NUM_ADDRESSES 0
|
|
||||||
#define ip_2_ip4(x) (x)
|
|
||||||
#define ipv4_addr ip_addr
|
|
||||||
#define ipv4_addr_t ip_addr_t
|
|
||||||
#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 IP_ADDR_ANY
|
|
||||||
#define IPADDR4_INIT(x) { x }
|
|
||||||
#define CONST /* nothing: lwIP-v1 does not use const */
|
|
||||||
#define ip4_addr_netcmp ip_addr_netcmp
|
|
||||||
#define netif_dhcp_data(netif) ((netif)->dhcp)
|
|
||||||
#else // lwIP-v2+
|
|
||||||
#define CONST const
|
|
||||||
#if !LWIP_IPV6
|
#if !LWIP_IPV6
|
||||||
struct ip_addr: ipv4_addr { };
|
struct ip_addr: ipv4_addr { };
|
||||||
#endif // !LWIP_IPV6
|
#endif // !LWIP_IPV6
|
||||||
#endif // lwIP-v2+
|
|
||||||
|
// to display a netif id with printf:
|
||||||
|
#define NETIFID_STR "%c%c%u"
|
||||||
|
#define NETIFID_VAL(netif) \
|
||||||
|
((netif)? (netif)->name[0]: '-'), \
|
||||||
|
((netif)? (netif)->name[1]: '-'), \
|
||||||
|
((netif)? netif_get_index(netif): 42)
|
||||||
|
|
||||||
// A class to make it easier to handle and pass around IP addresses
|
// A class to make it easier to handle and pass around IP addresses
|
||||||
// IPv6 update:
|
// IPv6 update:
|
||||||
@ -142,6 +132,8 @@ class IPAddress: public Printable {
|
|||||||
virtual size_t printTo(Print& p) const;
|
virtual size_t printTo(Print& p) const;
|
||||||
String toString() const;
|
String toString() const;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
check if input string(arg) is a valid IPV4 address or not.
|
check if input string(arg) is a valid IPV4 address or not.
|
||||||
return true on valid.
|
return true on valid.
|
||||||
@ -220,7 +212,7 @@ class IPAddress: public Printable {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern CONST IPAddress INADDR_ANY;
|
extern const IPAddress INADDR_ANY;
|
||||||
extern const IPAddress INADDR_NONE;
|
extern const IPAddress INADDR_NONE;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
64
cores/esp8266/LwipDhcpServer-NonOS.cpp
Normal file
64
cores/esp8266/LwipDhcpServer-NonOS.cpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
lwIPDhcpServer-NonOS.cpp - DHCP server wrapper
|
||||||
|
|
||||||
|
Copyright (c) 2020 esp8266 arduino. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
// STARTS/STOPS DHCP SERVER ON WIFI AP INTERFACE
|
||||||
|
// these functions must exists as-is with "C" interface,
|
||||||
|
// nonos-sdk calls them at boot time and later
|
||||||
|
|
||||||
|
#include <lwip/init.h> // LWIP_VERSION
|
||||||
|
|
||||||
|
#include <lwip/netif.h>
|
||||||
|
#include "LwipDhcpServer.h"
|
||||||
|
|
||||||
|
extern netif netif_git[2];
|
||||||
|
|
||||||
|
// global DHCP instance for softAP interface
|
||||||
|
DhcpServer dhcpSoftAP(&netif_git[SOFTAP_IF]);
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
|
||||||
|
void dhcps_start(struct ip_info *info, netif* apnetif)
|
||||||
|
{
|
||||||
|
// apnetif is esp interface, replaced by lwip2's
|
||||||
|
// netif_git[SOFTAP_IF] interface in constructor
|
||||||
|
(void)apnetif;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// can't use C++ now, global ctors are not initialized yet
|
||||||
|
dhcpSoftAP.begin(info);
|
||||||
|
#else
|
||||||
|
(void)info;
|
||||||
|
// initial version: emulate nonos-sdk in DhcpServer class before
|
||||||
|
// trying to change legacy behavor
|
||||||
|
// `fw_has_started_softap_dhcps` will be read in DhcpServer::DhcpServer
|
||||||
|
// which is called when c++ ctors are initialized, specifically
|
||||||
|
// dhcpSoftAP intialized with AP interface number above.
|
||||||
|
fw_has_started_softap_dhcps = 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void dhcps_stop()
|
||||||
|
{
|
||||||
|
dhcpSoftAP.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
1606
cores/esp8266/LwipDhcpServer.cpp
Normal file
1606
cores/esp8266/LwipDhcpServer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
124
cores/esp8266/LwipDhcpServer.h
Normal file
124
cores/esp8266/LwipDhcpServer.h
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
lwIPDhcpServer.h - DHCP server
|
||||||
|
|
||||||
|
Copyright (c) 2016 Espressif. All rights reserved.
|
||||||
|
Copyright (c) 2020 esp8266 arduino. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
// original sources (no license provided)
|
||||||
|
// ESP8266_NONOS_SDK/third_party/lwip/app/dhcpserver.c
|
||||||
|
// ESP8266_NONOS_SDK/third_party/include/lwip/app/dhcpserver.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
// lwIPDhcpServer.{cc,h} encapsulate original nonos-sdk dhcp server
|
||||||
|
// nearly as-is. This is an initial version to guaranty legacy behavior
|
||||||
|
// with same default values.
|
||||||
|
|
||||||
|
#ifndef __DHCPS_H__
|
||||||
|
#define __DHCPS_H__
|
||||||
|
|
||||||
|
#include <lwip/init.h> // LWIP_VERSION
|
||||||
|
|
||||||
|
class DhcpServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
DhcpServer(netif* netif);
|
||||||
|
~DhcpServer();
|
||||||
|
|
||||||
|
void setDns(int num, const ipv4_addr_t* dns);
|
||||||
|
|
||||||
|
bool begin(ip_info* info);
|
||||||
|
void end();
|
||||||
|
bool isRunning();
|
||||||
|
|
||||||
|
// this is the C interface encapsulated in a class
|
||||||
|
// (originally dhcpserver.c in lwIP-v1.4 in NonOS-SDK)
|
||||||
|
// (not changing everything at once)
|
||||||
|
// the API below is subject to change
|
||||||
|
|
||||||
|
// legacy public C structure and API to eventually turn into C++
|
||||||
|
|
||||||
|
void init_dhcps_lease(uint32 ip);
|
||||||
|
bool set_dhcps_lease(struct dhcps_lease *please);
|
||||||
|
bool get_dhcps_lease(struct dhcps_lease *please);
|
||||||
|
bool set_dhcps_offer_option(uint8 level, void* optarg);
|
||||||
|
bool set_dhcps_lease_time(uint32 minute);
|
||||||
|
bool reset_dhcps_lease_time(void);
|
||||||
|
uint32 get_dhcps_lease_time(void);
|
||||||
|
bool add_dhcps_lease(uint8 *macaddr);
|
||||||
|
|
||||||
|
void dhcps_set_dns(int num, const ipv4_addr_t* dns);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// legacy C structure and API to eventually turn into C++
|
||||||
|
|
||||||
|
typedef struct _list_node
|
||||||
|
{
|
||||||
|
void *pnode;
|
||||||
|
struct _list_node *pnext;
|
||||||
|
} list_node;
|
||||||
|
|
||||||
|
void node_insert_to_list(list_node **phead, list_node* pinsert);
|
||||||
|
void node_remove_from_list(list_node **phead, list_node* pdelete);
|
||||||
|
uint8_t* add_msg_type(uint8_t *optptr, uint8_t type);
|
||||||
|
uint8_t* add_offer_options(uint8_t *optptr);
|
||||||
|
uint8_t* add_end(uint8_t *optptr);
|
||||||
|
void create_msg(struct dhcps_msg *m);
|
||||||
|
void send_offer(struct dhcps_msg *m);
|
||||||
|
void send_nak(struct dhcps_msg *m);
|
||||||
|
void send_ack(struct dhcps_msg *m);
|
||||||
|
uint8_t parse_options(uint8_t *optptr, sint16_t len);
|
||||||
|
sint16_t parse_msg(struct dhcps_msg *m, u16_t len);
|
||||||
|
static void S_handle_dhcp(void *arg,
|
||||||
|
struct udp_pcb *pcb,
|
||||||
|
struct pbuf *p,
|
||||||
|
const ip_addr_t *addr,
|
||||||
|
uint16_t port);
|
||||||
|
void handle_dhcp(
|
||||||
|
struct udp_pcb *pcb,
|
||||||
|
struct pbuf *p,
|
||||||
|
const ip_addr_t *addr,
|
||||||
|
uint16_t port);
|
||||||
|
void kill_oldest_dhcps_pool(void);
|
||||||
|
void dhcps_coarse_tmr(void); // CURRENTLY NOT CALLED
|
||||||
|
void dhcps_client_leave(u8 *bssid, struct ipv4_addr *ip, bool force);
|
||||||
|
uint32 dhcps_client_update(u8 *bssid, struct ipv4_addr *ip);
|
||||||
|
|
||||||
|
netif* _netif;
|
||||||
|
|
||||||
|
struct udp_pcb *pcb_dhcps;
|
||||||
|
ip_addr_t broadcast_dhcps;
|
||||||
|
struct ipv4_addr server_address;
|
||||||
|
struct ipv4_addr client_address;
|
||||||
|
struct ipv4_addr dns_address;
|
||||||
|
uint32 dhcps_lease_time;
|
||||||
|
|
||||||
|
struct dhcps_lease dhcps_lease;
|
||||||
|
list_node *plist;
|
||||||
|
uint8 offer;
|
||||||
|
bool renew;
|
||||||
|
|
||||||
|
static const uint32 magic_cookie;
|
||||||
|
};
|
||||||
|
|
||||||
|
// SoftAP DHCP server always exists and is started on boot
|
||||||
|
extern DhcpServer dhcpSoftAP;
|
||||||
|
extern "C" int fw_has_started_softap_dhcps;
|
||||||
|
|
||||||
|
#endif // __DHCPS_H__
|
165
cores/esp8266/LwipIntf.cpp
Normal file
165
cores/esp8266/LwipIntf.cpp
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "lwip/err.h"
|
||||||
|
#include "lwip/ip_addr.h"
|
||||||
|
#include "lwip/dns.h"
|
||||||
|
#include "lwip/dhcp.h"
|
||||||
|
#include "lwip/init.h" // LWIP_VERSION_
|
||||||
|
#if LWIP_IPV6
|
||||||
|
#include "lwip/netif.h" // struct netif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <user_interface.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
#include "LwipIntf.h"
|
||||||
|
|
||||||
|
// args | esp order arduino order
|
||||||
|
// ---- + --------- -------------
|
||||||
|
// local_ip | local_ip local_ip
|
||||||
|
// arg1 | gateway dns1
|
||||||
|
// arg2 | netmask gateway
|
||||||
|
// arg3 | dns1 netmask
|
||||||
|
//
|
||||||
|
// result stored into gateway/netmask/dns1
|
||||||
|
|
||||||
|
bool LwipIntf::ipAddressReorder(const IPAddress& local_ip, const IPAddress& arg1, const IPAddress& arg2, const IPAddress& arg3,
|
||||||
|
IPAddress& gateway, IPAddress& netmask, IPAddress& dns1)
|
||||||
|
{
|
||||||
|
//To allow compatibility, check first octet of 3rd arg. If 255, interpret as ESP order, otherwise Arduino order.
|
||||||
|
gateway = arg1;
|
||||||
|
netmask = arg2;
|
||||||
|
dns1 = arg3;
|
||||||
|
|
||||||
|
if (netmask[0] != 255)
|
||||||
|
{
|
||||||
|
//octet is not 255 => interpret as Arduino order
|
||||||
|
gateway = arg2;
|
||||||
|
netmask = arg3[0] == 0 ? IPAddress(255, 255, 255, 0) : arg3; //arg order is arduino and 4th arg not given => assign it arduino default
|
||||||
|
dns1 = arg1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check whether all is IPv4 (or gateway not set)
|
||||||
|
if (!(local_ip.isV4() && netmask.isV4() && (!gateway.isSet() || gateway.isV4())))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ip and gateway must be in the same netmask
|
||||||
|
if (gateway.isSet() && (local_ip.v4() & netmask.v4()) != (gateway.v4() & netmask.v4()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get ESP8266 station DHCP hostname
|
||||||
|
@return hostname
|
||||||
|
*/
|
||||||
|
String LwipIntf::hostname(void)
|
||||||
|
{
|
||||||
|
return wifi_station_get_hostname();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get ESP8266 station DHCP hostname
|
||||||
|
@return hostname
|
||||||
|
*/
|
||||||
|
const char* LwipIntf::getHostname(void)
|
||||||
|
{
|
||||||
|
return wifi_station_get_hostname();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set ESP8266 station DHCP hostname
|
||||||
|
@param aHostname max length:24
|
||||||
|
@return ok
|
||||||
|
*/
|
||||||
|
bool LwipIntf::hostname(const char* aHostname)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
vvvv RFC952 vvvv
|
||||||
|
ASSUMPTIONS
|
||||||
|
1. A "name" (Net, Host, Gateway, or Domain name) is a text string up
|
||||||
|
to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus
|
||||||
|
sign (-), and period (.). Note that periods are only allowed when
|
||||||
|
they serve to delimit components of "domain style names". (See
|
||||||
|
RFC-921, "Domain Name System Implementation Schedule", for
|
||||||
|
background). No blank or space characters are permitted as part of a
|
||||||
|
name. No distinction is made between upper and lower case. The first
|
||||||
|
character must be an alpha character. The last character must not be
|
||||||
|
a minus sign or period. A host which serves as a GATEWAY should have
|
||||||
|
"-GATEWAY" or "-GW" as part of its name. Hosts which do not serve as
|
||||||
|
Internet gateways should not use "-GATEWAY" and "-GW" as part of
|
||||||
|
their names. A host which is a TAC should have "-TAC" as the last
|
||||||
|
part of its host name, if it is a DoD host. Single character names
|
||||||
|
or nicknames are not allowed.
|
||||||
|
^^^^ RFC952 ^^^^
|
||||||
|
|
||||||
|
- 24 chars max
|
||||||
|
- only a..z A..Z 0..9 '-'
|
||||||
|
- no '-' as last char
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t len = strlen(aHostname);
|
||||||
|
|
||||||
|
if (len == 0 || len > 32)
|
||||||
|
{
|
||||||
|
// nonos-sdk limit is 32
|
||||||
|
// (dhcp hostname option minimum size is ~60)
|
||||||
|
DEBUGV("WiFi.(set)hostname(): empty or large(>32) name\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check RFC compliance
|
||||||
|
bool compliant = (len <= 24);
|
||||||
|
for (size_t i = 0; compliant && i < len; i++)
|
||||||
|
if (!isalnum(aHostname[i]) && aHostname[i] != '-')
|
||||||
|
{
|
||||||
|
compliant = false;
|
||||||
|
}
|
||||||
|
if (aHostname[len - 1] == '-')
|
||||||
|
{
|
||||||
|
compliant = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!compliant)
|
||||||
|
{
|
||||||
|
DEBUGV("hostname '%s' is not compliant with RFC952\n", aHostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = wifi_station_set_hostname(aHostname);
|
||||||
|
if (!ret)
|
||||||
|
{
|
||||||
|
DEBUGV("WiFi.hostname(%s): wifi_station_set_hostname() failed\n", aHostname);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we should inform dhcp server for this change, using lwip_renew()
|
||||||
|
// looping through all existing interface
|
||||||
|
// harmless for AP, also compatible with ethernet adapters (to come)
|
||||||
|
for (netif* intf = netif_list; intf; intf = intf->next)
|
||||||
|
{
|
||||||
|
|
||||||
|
// unconditionally update all known interfaces
|
||||||
|
intf->hostname = wifi_station_get_hostname();
|
||||||
|
|
||||||
|
if (netif_dhcp_data(intf) != nullptr)
|
||||||
|
{
|
||||||
|
// renew already started DHCP leases
|
||||||
|
err_t lwipret = dhcp_renew(intf);
|
||||||
|
if (lwipret != ERR_OK)
|
||||||
|
{
|
||||||
|
DEBUGV("WiFi.hostname(%s): lwIP error %d on interface %c%c (index %d)\n",
|
||||||
|
intf->hostname, (int)lwipret, intf->name[0], intf->name[1], intf->num);
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret && compliant;
|
||||||
|
}
|
||||||
|
|
49
cores/esp8266/LwipIntf.h
Normal file
49
cores/esp8266/LwipIntf.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
#ifndef _LWIPINTF_H
|
||||||
|
#define _LWIPINTF_H
|
||||||
|
|
||||||
|
#include <lwip/netif.h>
|
||||||
|
#include <IPAddress.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class LwipIntf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
using CBType = std::function <void(netif*)>;
|
||||||
|
|
||||||
|
static bool stateUpCB(LwipIntf::CBType&& cb);
|
||||||
|
|
||||||
|
// reorder WiFi.config() parameters for a esp8266/official Arduino dual-compatibility API
|
||||||
|
// args | esp order arduino order
|
||||||
|
// ---- + --------- -------------
|
||||||
|
// local_ip | local_ip local_ip
|
||||||
|
// arg1 | gateway dns1
|
||||||
|
// arg2 | netmask [Agateway
|
||||||
|
// arg3 | dns1 netmask
|
||||||
|
//
|
||||||
|
// result stored into gateway/netmask/dns1
|
||||||
|
static
|
||||||
|
bool ipAddressReorder(const IPAddress& local_ip, const IPAddress& arg1, const IPAddress& arg2, const IPAddress& arg3,
|
||||||
|
IPAddress& gateway, IPAddress& netmask, IPAddress& dns1);
|
||||||
|
|
||||||
|
String hostname();
|
||||||
|
bool hostname(const String& aHostname)
|
||||||
|
{
|
||||||
|
return hostname(aHostname.c_str());
|
||||||
|
}
|
||||||
|
bool hostname(const char* aHostname);
|
||||||
|
// ESP32 API compatibility
|
||||||
|
bool setHostname(const char* aHostName)
|
||||||
|
{
|
||||||
|
return hostname(aHostName);
|
||||||
|
}
|
||||||
|
const char* getHostname();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
static bool stateChangeSysCB(LwipIntf::CBType&& cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // _LWIPINTF_H
|
44
cores/esp8266/LwipIntfCB.cpp
Normal file
44
cores/esp8266/LwipIntfCB.cpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
#include <LwipIntf.h>
|
||||||
|
#include <Schedule.h>
|
||||||
|
#include <debug.h>
|
||||||
|
|
||||||
|
#define NETIF_STATUS_CB_SIZE 3
|
||||||
|
|
||||||
|
static int netifStatusChangeListLength = 0;
|
||||||
|
LwipIntf::CBType netifStatusChangeList [NETIF_STATUS_CB_SIZE];
|
||||||
|
|
||||||
|
extern "C" void netif_status_changed(struct netif* netif)
|
||||||
|
{
|
||||||
|
// override the default empty weak function
|
||||||
|
for (int i = 0; i < netifStatusChangeListLength; i++)
|
||||||
|
{
|
||||||
|
netifStatusChangeList[i](netif);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LwipIntf::stateChangeSysCB(LwipIntf::CBType&& cb)
|
||||||
|
{
|
||||||
|
if (netifStatusChangeListLength >= NETIF_STATUS_CB_SIZE)
|
||||||
|
{
|
||||||
|
#if defined(DEBUG_ESP_CORE)
|
||||||
|
DEBUGV("NETIF_STATUS_CB_SIZE is too low\n");
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
netifStatusChangeList[netifStatusChangeListLength++] = cb;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LwipIntf::stateUpCB(LwipIntf::CBType&& cb)
|
||||||
|
{
|
||||||
|
return stateChangeSysCB([cb](netif * nif)
|
||||||
|
{
|
||||||
|
if (netif_is_up(nif))
|
||||||
|
schedule_function([cb, nif]()
|
||||||
|
{
|
||||||
|
cb(nif);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
386
cores/esp8266/LwipIntfDev.h
Normal file
386
cores/esp8266/LwipIntfDev.h
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
|
||||||
|
#ifndef _LWIPINTFDEV_H
|
||||||
|
#define _LWIPINTFDEV_H
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// remove all Serial.print
|
||||||
|
// unchain pbufs
|
||||||
|
|
||||||
|
#include <netif/ethernet.h>
|
||||||
|
#include <lwip/init.h>
|
||||||
|
#include <lwip/netif.h>
|
||||||
|
#include <lwip/etharp.h>
|
||||||
|
#include <lwip/dhcp.h>
|
||||||
|
#include <lwip/apps/sntp.h>
|
||||||
|
|
||||||
|
#include <user_interface.h> // wifi_get_macaddr()
|
||||||
|
|
||||||
|
#include "SPI.h"
|
||||||
|
#include "Schedule.h"
|
||||||
|
#include "LwipIntf.h"
|
||||||
|
#include "wl_definitions.h"
|
||||||
|
|
||||||
|
#ifndef DEFAULT_MTU
|
||||||
|
#define DEFAULT_MTU 1500
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
class LwipIntfDev: public LwipIntf, public RawDev
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
LwipIntfDev(int8_t cs = SS, SPIClass& spi = SPI, int8_t intr = -1):
|
||||||
|
RawDev(cs, spi, intr),
|
||||||
|
_mtu(DEFAULT_MTU),
|
||||||
|
_intrPin(intr),
|
||||||
|
_started(false),
|
||||||
|
_default(false)
|
||||||
|
{
|
||||||
|
memset(&_netif, 0, sizeof(_netif));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean config(const IPAddress& local_ip, const IPAddress& arg1, const IPAddress& arg2, const IPAddress& arg3, const IPAddress& dns2);
|
||||||
|
|
||||||
|
// default mac-address is inferred from esp8266's STA interface
|
||||||
|
boolean begin(const uint8_t *macAddress = nullptr, const uint16_t mtu = DEFAULT_MTU);
|
||||||
|
|
||||||
|
const netif* getNetIf() const
|
||||||
|
{
|
||||||
|
return &_netif;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress localIP() const
|
||||||
|
{
|
||||||
|
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.ip_addr)));
|
||||||
|
}
|
||||||
|
IPAddress subnetMask() const
|
||||||
|
{
|
||||||
|
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.netmask)));
|
||||||
|
}
|
||||||
|
IPAddress gatewayIP() const
|
||||||
|
{
|
||||||
|
return IPAddress(ip4_addr_get_u32(ip_2_ip4(&_netif.gw)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDefault();
|
||||||
|
|
||||||
|
bool connected()
|
||||||
|
{
|
||||||
|
return !!ip4_addr_get_u32(ip_2_ip4(&_netif.ip_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESP8266WiFi API compatibility
|
||||||
|
|
||||||
|
wl_status_t status();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
err_t netif_init();
|
||||||
|
void netif_status_callback();
|
||||||
|
|
||||||
|
static err_t netif_init_s(netif* netif);
|
||||||
|
static err_t linkoutput_s(netif *netif, struct pbuf *p);
|
||||||
|
static void netif_status_callback_s(netif* netif);
|
||||||
|
|
||||||
|
// called on a regular basis or on interrupt
|
||||||
|
err_t handlePackets();
|
||||||
|
|
||||||
|
// members
|
||||||
|
|
||||||
|
netif _netif;
|
||||||
|
|
||||||
|
uint16_t _mtu;
|
||||||
|
int8_t _intrPin;
|
||||||
|
uint8_t _macAddress[6];
|
||||||
|
bool _started;
|
||||||
|
bool _default;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
boolean LwipIntfDev<RawDev>::config(const IPAddress& localIP, const IPAddress& gateway, const IPAddress& netmask, const IPAddress& dns1, const IPAddress& dns2)
|
||||||
|
{
|
||||||
|
if (_started)
|
||||||
|
{
|
||||||
|
DEBUGV("LwipIntfDev: use config() then begin()\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress realGateway, realNetmask, realDns1;
|
||||||
|
if (!ipAddressReorder(localIP, gateway, netmask, dns1, realGateway, realNetmask, realDns1))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ip4_addr_set_u32(ip_2_ip4(&_netif.ip_addr), localIP.v4());
|
||||||
|
ip4_addr_set_u32(ip_2_ip4(&_netif.gw), realGateway.v4());
|
||||||
|
ip4_addr_set_u32(ip_2_ip4(&_netif.netmask), realNetmask.v4());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
boolean LwipIntfDev<RawDev>::begin(const uint8_t* macAddress, const uint16_t mtu)
|
||||||
|
{
|
||||||
|
if (mtu)
|
||||||
|
{
|
||||||
|
_mtu = mtu;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (macAddress)
|
||||||
|
{
|
||||||
|
memcpy(_macAddress, macAddress, 6);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_netif.num = 2;
|
||||||
|
for (auto n = netif_list; n; n = n->next)
|
||||||
|
if (n->num >= _netif.num)
|
||||||
|
{
|
||||||
|
_netif.num = n->num + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
// forge a new mac-address from the esp's wifi sta one
|
||||||
|
// I understand this is cheating with an official mac-address
|
||||||
|
wifi_get_macaddr(STATION_IF, (uint8*)_macAddress);
|
||||||
|
#else
|
||||||
|
// https://serverfault.com/questions/40712/what-range-of-mac-addresses-can-i-safely-use-for-my-virtual-machines
|
||||||
|
memset(_macAddress, 0, 6);
|
||||||
|
_macAddress[0] = 0xEE;
|
||||||
|
#endif
|
||||||
|
_macAddress[3] += _netif.num; // alter base mac address
|
||||||
|
_macAddress[0] &= 0xfe; // set as locally administered, unicast, per
|
||||||
|
_macAddress[0] |= 0x02; // https://en.wikipedia.org/wiki/MAC_address#Universal_vs._local
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!RawDev::begin(_macAddress))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup lwIP netif
|
||||||
|
|
||||||
|
_netif.hwaddr_len = sizeof _macAddress;
|
||||||
|
memcpy(_netif.hwaddr, _macAddress, sizeof _macAddress);
|
||||||
|
|
||||||
|
// due to netif_add() api: ...
|
||||||
|
ip_addr_t ip_addr, netmask, gw;
|
||||||
|
ip_addr_copy(ip_addr, _netif.ip_addr);
|
||||||
|
ip_addr_copy(netmask, _netif.netmask);
|
||||||
|
ip_addr_copy(gw, _netif.gw);
|
||||||
|
|
||||||
|
if (!netif_add(&_netif, ip_2_ip4(&ip_addr), ip_2_ip4(&netmask), ip_2_ip4(&gw), this, netif_init_s, ethernet_input))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_netif.flags |= NETIF_FLAG_UP;
|
||||||
|
|
||||||
|
if (localIP().v4() == 0)
|
||||||
|
{
|
||||||
|
switch (dhcp_start(&_netif))
|
||||||
|
{
|
||||||
|
case ERR_OK:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ERR_IF:
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
netif_remove(&_netif);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_started = true;
|
||||||
|
|
||||||
|
if (_intrPin >= 0)
|
||||||
|
{
|
||||||
|
if (RawDev::interruptIsPossible())
|
||||||
|
{
|
||||||
|
//attachInterrupt(_intrPin, [&]() { this->handlePackets(); }, FALLING);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
::printf((PGM_P)F("lwIP_Intf: Interrupt not implemented yet, enabling transparent polling\r\n"));
|
||||||
|
_intrPin = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_intrPin < 0 && !schedule_recurrent_function_us([&]()
|
||||||
|
{
|
||||||
|
this->handlePackets();
|
||||||
|
return true;
|
||||||
|
}, 100))
|
||||||
|
{
|
||||||
|
netif_remove(&_netif);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
wl_status_t LwipIntfDev<RawDev>::status()
|
||||||
|
{
|
||||||
|
return _started ? (connected() ? WL_CONNECTED : WL_DISCONNECTED) : WL_NO_SHIELD;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
err_t LwipIntfDev<RawDev>::linkoutput_s(netif *netif, struct pbuf *pbuf)
|
||||||
|
{
|
||||||
|
LwipIntfDev* ths = (LwipIntfDev*)netif->state;
|
||||||
|
|
||||||
|
if (pbuf->len != pbuf->tot_len || pbuf->next)
|
||||||
|
{
|
||||||
|
Serial.println("ERRTOT\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t len = ths->sendFrame((const uint8_t*)pbuf->payload, pbuf->len);
|
||||||
|
|
||||||
|
#if PHY_HAS_CAPTURE
|
||||||
|
if (phy_capture)
|
||||||
|
{
|
||||||
|
phy_capture(ths->_netif.num, (const char*)pbuf->payload, pbuf->len, /*out*/1, /*success*/len == pbuf->len);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return len == pbuf->len ? ERR_OK : ERR_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
err_t LwipIntfDev<RawDev>::netif_init_s(struct netif* netif)
|
||||||
|
{
|
||||||
|
return ((LwipIntfDev*)netif->state)->netif_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
void LwipIntfDev<RawDev>::netif_status_callback_s(struct netif* netif)
|
||||||
|
{
|
||||||
|
((LwipIntfDev*)netif->state)->netif_status_callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
err_t LwipIntfDev<RawDev>::netif_init()
|
||||||
|
{
|
||||||
|
_netif.name[0] = 'e';
|
||||||
|
_netif.name[1] = '0' + _netif.num;
|
||||||
|
_netif.mtu = _mtu;
|
||||||
|
_netif.chksum_flags = NETIF_CHECKSUM_ENABLE_ALL;
|
||||||
|
_netif.flags =
|
||||||
|
NETIF_FLAG_ETHARP
|
||||||
|
| NETIF_FLAG_IGMP
|
||||||
|
| NETIF_FLAG_BROADCAST
|
||||||
|
| NETIF_FLAG_LINK_UP;
|
||||||
|
|
||||||
|
// lwIP's doc: This function typically first resolves the hardware
|
||||||
|
// address, then sends the packet. For ethernet physical layer, this is
|
||||||
|
// usually lwIP's etharp_output()
|
||||||
|
_netif.output = etharp_output;
|
||||||
|
|
||||||
|
// lwIP's doc: This function outputs the pbuf as-is on the link medium
|
||||||
|
// (this must points to the raw ethernet driver, meaning: us)
|
||||||
|
_netif.linkoutput = linkoutput_s;
|
||||||
|
|
||||||
|
_netif.status_callback = netif_status_callback_s;
|
||||||
|
|
||||||
|
return ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
void LwipIntfDev<RawDev>::netif_status_callback()
|
||||||
|
{
|
||||||
|
if (connected())
|
||||||
|
{
|
||||||
|
if (_default)
|
||||||
|
{
|
||||||
|
netif_set_default(&_netif);
|
||||||
|
}
|
||||||
|
sntp_stop();
|
||||||
|
sntp_init();
|
||||||
|
}
|
||||||
|
else if (netif_default == &_netif)
|
||||||
|
{
|
||||||
|
netif_set_default(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
err_t LwipIntfDev<RawDev>::handlePackets()
|
||||||
|
{
|
||||||
|
int pkt = 0;
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
if (++pkt == 10)
|
||||||
|
// prevent starvation
|
||||||
|
{
|
||||||
|
return ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t tot_len = RawDev::readFrameSize();
|
||||||
|
if (!tot_len)
|
||||||
|
{
|
||||||
|
return ERR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// from doc: use PBUF_RAM for TX, PBUF_POOL from RX
|
||||||
|
// however:
|
||||||
|
// PBUF_POOL can return chained pbuf (not in one piece)
|
||||||
|
// and WiznetDriver does not have the proper API to deal with that
|
||||||
|
// so in the meantime, we use PBUF_RAM instead which is currently
|
||||||
|
// guarantying to deliver a continuous chunk of memory.
|
||||||
|
// TODO: tweak the wiznet driver to allow copying partial chunk
|
||||||
|
// of received data and use PBUF_POOL.
|
||||||
|
pbuf* pbuf = pbuf_alloc(PBUF_RAW, tot_len, PBUF_RAM);
|
||||||
|
if (!pbuf || pbuf->len < tot_len)
|
||||||
|
{
|
||||||
|
if (pbuf)
|
||||||
|
{
|
||||||
|
pbuf_free(pbuf);
|
||||||
|
}
|
||||||
|
RawDev::discardFrame(tot_len);
|
||||||
|
return ERR_BUF;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t len = RawDev::readFrameData((uint8_t*)pbuf->payload, tot_len);
|
||||||
|
if (len != tot_len)
|
||||||
|
{
|
||||||
|
// tot_len is given by readFrameSize()
|
||||||
|
// and is supposed to be honoured by readFrameData()
|
||||||
|
// todo: ensure this test is unneeded, remove the print
|
||||||
|
Serial.println("read error?\r\n");
|
||||||
|
pbuf_free(pbuf);
|
||||||
|
return ERR_BUF;
|
||||||
|
}
|
||||||
|
|
||||||
|
err_t err = _netif.input(pbuf, &_netif);
|
||||||
|
|
||||||
|
#if PHY_HAS_CAPTURE
|
||||||
|
if (phy_capture)
|
||||||
|
{
|
||||||
|
phy_capture(_netif.num, (const char*)pbuf->payload, tot_len, /*out*/0, /*success*/err == ERR_OK);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (err != ERR_OK)
|
||||||
|
{
|
||||||
|
pbuf_free(pbuf);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
// (else) allocated pbuf is now lwIP's responsibility
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class RawDev>
|
||||||
|
void LwipIntfDev<RawDev>::setDefault()
|
||||||
|
{
|
||||||
|
_default = true;
|
||||||
|
if (connected())
|
||||||
|
{
|
||||||
|
netif_set_default(&_netif);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // _LWIPINTFDEV_H
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
PolledTimeout.h - Encapsulation of a polled Timeout
|
PolledTimeout.h - Encapsulation of a polled Timeout
|
||||||
|
|
||||||
Copyright (c) 2018 Daniel Salazar. All rights reserved.
|
Copyright (c) 2018 Daniel Salazar. All rights reserved.
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
@ -23,9 +23,10 @@
|
|||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <limits>
|
#include <c_types.h> // IRAM_ATTR
|
||||||
|
#include <limits> // std::numeric_limits
|
||||||
#include <Arduino.h>
|
#include <type_traits> // std::is_unsigned
|
||||||
|
#include <core_esp8266_features.h>
|
||||||
|
|
||||||
namespace esp8266
|
namespace esp8266
|
||||||
{
|
{
|
||||||
@ -70,13 +71,13 @@ struct TimeSourceMillis
|
|||||||
|
|
||||||
struct TimeSourceCycles
|
struct TimeSourceCycles
|
||||||
{
|
{
|
||||||
// time policy based on ESP.getCycleCount()
|
// time policy based on esp_get_cycle_count()
|
||||||
// this particular time measurement is intended to be called very often
|
// this particular time measurement is intended to be called very often
|
||||||
// (every loop, every yield)
|
// (every loop, every yield)
|
||||||
|
|
||||||
using timeType = decltype(ESP.getCycleCount());
|
using timeType = decltype(esp_get_cycle_count());
|
||||||
static timeType time() {return ESP.getCycleCount();}
|
static timeType time() {return esp_get_cycle_count();}
|
||||||
static constexpr timeType ticksPerSecond = ESP.getCpuFreqMHz() * 1000000UL; // 80'000'000 or 160'000'000 Hz
|
static constexpr timeType ticksPerSecond = esp_get_cpu_freq_mhz() * 1000000UL; // 80'000'000 or 160'000'000 Hz
|
||||||
static constexpr timeType ticksPerSecondMax = 160000000; // 160MHz
|
static constexpr timeType ticksPerSecondMax = 160000000; // 160MHz
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -161,13 +162,13 @@ public:
|
|||||||
return expiredRetrigger();
|
return expiredRetrigger();
|
||||||
return expiredOneShot();
|
return expiredOneShot();
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR // fast
|
IRAM_ATTR // fast
|
||||||
operator bool()
|
operator bool()
|
||||||
{
|
{
|
||||||
return expired();
|
return expired();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool canExpire () const
|
bool canExpire () const
|
||||||
{
|
{
|
||||||
return !_neverExpires;
|
return !_neverExpires;
|
||||||
@ -178,6 +179,7 @@ public:
|
|||||||
return _timeout != alwaysExpired;
|
return _timeout != alwaysExpired;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resets, will trigger after this new timeout.
|
||||||
IRAM_ATTR // called from ISR
|
IRAM_ATTR // called from ISR
|
||||||
void reset(const timeType newUserTimeout)
|
void reset(const timeType newUserTimeout)
|
||||||
{
|
{
|
||||||
@ -186,12 +188,30 @@ public:
|
|||||||
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
|
_neverExpires = (newUserTimeout < 0) || (newUserTimeout > timeMax());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resets, will trigger after the timeout previously set.
|
||||||
IRAM_ATTR // called from ISR
|
IRAM_ATTR // called from ISR
|
||||||
void reset()
|
void reset()
|
||||||
{
|
{
|
||||||
_start = TimePolicyT::time();
|
_start = TimePolicyT::time();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resets to just expired so that on next poll the check will immediately trigger for the user,
|
||||||
|
// also change timeout (after next immediate trigger).
|
||||||
|
IRAM_ATTR // called from ISR
|
||||||
|
void resetAndSetExpired (const timeType newUserTimeout)
|
||||||
|
{
|
||||||
|
reset(newUserTimeout);
|
||||||
|
_start -= _timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resets to just expired so that on next poll the check will immediately trigger for the user.
|
||||||
|
IRAM_ATTR // called from ISR
|
||||||
|
void resetAndSetExpired ()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
_start -= _timeout;
|
||||||
|
}
|
||||||
|
|
||||||
void resetToNeverExpires ()
|
void resetToNeverExpires ()
|
||||||
{
|
{
|
||||||
_timeout = alwaysExpired + 1; // because canWait() has precedence
|
_timeout = alwaysExpired + 1; // because canWait() has precedence
|
||||||
@ -202,7 +222,7 @@ public:
|
|||||||
{
|
{
|
||||||
return TimePolicyT::toUserUnit(_timeout);
|
return TimePolicyT::toUserUnit(_timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr timeType timeMax()
|
static constexpr timeType timeMax()
|
||||||
{
|
{
|
||||||
return TimePolicyT::timeMax;
|
return TimePolicyT::timeMax;
|
||||||
@ -235,14 +255,14 @@ protected:
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR // fast
|
IRAM_ATTR // fast
|
||||||
bool expiredOneShot() const
|
bool expiredOneShot() const
|
||||||
{
|
{
|
||||||
// returns "always expired" or "has expired"
|
// returns "always expired" or "has expired"
|
||||||
return !canWait() || checkExpired(TimePolicyT::time());
|
return !canWait() || checkExpired(TimePolicyT::time());
|
||||||
}
|
}
|
||||||
|
|
||||||
timeType _timeout;
|
timeType _timeout;
|
||||||
timeType _start;
|
timeType _start;
|
||||||
bool _neverExpires;
|
bool _neverExpires;
|
||||||
@ -259,14 +279,14 @@ using periodic = polledTimeout::timeoutTemplate<true> /*__attribute__((deprecate
|
|||||||
using oneShotMs = polledTimeout::timeoutTemplate<false>;
|
using oneShotMs = polledTimeout::timeoutTemplate<false>;
|
||||||
using periodicMs = polledTimeout::timeoutTemplate<true>;
|
using periodicMs = polledTimeout::timeoutTemplate<true>;
|
||||||
|
|
||||||
// Time policy based on ESP.getCycleCount(), and intended to be called very often:
|
// Time policy based on esp_get_cycle_count(), and intended to be called very often:
|
||||||
// "Fast" versions sacrifices time range for improved precision and reduced execution time (by 86%)
|
// "Fast" versions sacrifices time range for improved precision and reduced execution time (by 86%)
|
||||||
// (cpu cycles for ::expired(): 372 (millis()) vs 52 (ESP.getCycleCount()))
|
// (cpu cycles for ::expired(): 372 (millis()) vs 52 (esp_get_cycle_count()))
|
||||||
// timeMax() values:
|
// timeMax() values:
|
||||||
// Ms: max is 26843 ms (26.8 s)
|
// Ms: max is 26843 ms (26.8 s)
|
||||||
// Us: max is 26843545 us (26.8 s)
|
// Us: max is 26843545 us (26.8 s)
|
||||||
// Ns: max is 1073741823 ns ( 1.07 s)
|
// Ns: max is 1073741823 ns ( 1.07 s)
|
||||||
// (time policy based on ESP.getCycleCount() is intended to be called very often)
|
// (time policy based on esp_get_cycle_count() is intended to be called very often)
|
||||||
|
|
||||||
using oneShotFastMs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
|
using oneShotFastMs = polledTimeout::timeoutTemplate<false, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
|
||||||
using periodicFastMs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
|
using periodicFastMs = polledTimeout::timeoutTemplate<true, YieldPolicy::DoNothing, TimePolicy::TimeFastMillis>;
|
||||||
|
@ -63,7 +63,7 @@ size_t Print::printf(const char *format, ...) {
|
|||||||
size_t len = vsnprintf(temp, sizeof(temp), format, arg);
|
size_t len = vsnprintf(temp, sizeof(temp), format, arg);
|
||||||
va_end(arg);
|
va_end(arg);
|
||||||
if (len > sizeof(temp) - 1) {
|
if (len > sizeof(temp) - 1) {
|
||||||
buffer = new char[len + 1];
|
buffer = new (std::nothrow) char[len + 1];
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -86,7 +86,7 @@ size_t Print::printf_P(PGM_P format, ...) {
|
|||||||
size_t len = vsnprintf_P(temp, sizeof(temp), format, arg);
|
size_t len = vsnprintf_P(temp, sizeof(temp), format, arg);
|
||||||
va_end(arg);
|
va_end(arg);
|
||||||
if (len > sizeof(temp) - 1) {
|
if (len > sizeof(temp) - 1) {
|
||||||
buffer = new char[len + 1];
|
buffer = new (std::nothrow) char[len + 1];
|
||||||
if (!buffer) {
|
if (!buffer) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -146,35 +146,39 @@ size_t Print::print(unsigned int n, int base) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::print(long n, int base) {
|
size_t Print::print(long n, int base) {
|
||||||
if(base == 0) {
|
int t = 0;
|
||||||
return write(n);
|
if (base == 10 && n < 0) {
|
||||||
} else if(base == 10) {
|
t = print('-');
|
||||||
if(n < 0) {
|
n = -n;
|
||||||
int t = print('-');
|
|
||||||
n = -n;
|
|
||||||
return printNumber(n, 10) + t;
|
|
||||||
}
|
|
||||||
return printNumber(n, 10);
|
|
||||||
} else {
|
|
||||||
return printNumber(n, base);
|
|
||||||
}
|
}
|
||||||
|
return printNumber(static_cast<unsigned long>(n), base) + t;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::print(unsigned long n, int base) {
|
size_t Print::print(unsigned long n, int base) {
|
||||||
if(base == 0)
|
if (base == 0) {
|
||||||
return write(n);
|
return write(n);
|
||||||
else
|
}
|
||||||
return printNumber(n, base);
|
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) {
|
size_t Print::print(double n, int digits) {
|
||||||
return printFloat(n, digits);
|
return printNumber(n, digits);
|
||||||
}
|
|
||||||
|
|
||||||
size_t Print::println(const __FlashStringHelper *ifsh) {
|
|
||||||
size_t n = print(ifsh);
|
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::print(const Printable& x) {
|
size_t Print::print(const Printable& x) {
|
||||||
@ -185,89 +189,90 @@ size_t Print::println(void) {
|
|||||||
return print("\r\n");
|
return print("\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t Print::println(const __FlashStringHelper* ifsh) {
|
||||||
|
return _println<const __FlashStringHelper*>(ifsh);
|
||||||
|
}
|
||||||
|
|
||||||
size_t Print::println(const String &s) {
|
size_t Print::println(const String &s) {
|
||||||
size_t n = print(s);
|
return _println(s);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(const char c[]) {
|
size_t Print::println(const char c[]) {
|
||||||
size_t n = print(c);
|
return _println(c);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(char c) {
|
size_t Print::println(char c) {
|
||||||
size_t n = print(c);
|
return _println(c);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(unsigned char b, int base) {
|
size_t Print::println(unsigned char b, int base) {
|
||||||
size_t n = print(b, base);
|
return _println(b, base);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(int num, int base) {
|
size_t Print::println(int num, int base) {
|
||||||
size_t n = print(num, base);
|
return _println(num, base);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(unsigned int num, int base) {
|
size_t Print::println(unsigned int num, int base) {
|
||||||
size_t n = print(num, base);
|
return _println(num, base);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(long num, int base) {
|
size_t Print::println(long num, int base) {
|
||||||
size_t n = print(num, base);
|
return _println(num, base);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(unsigned long num, int base) {
|
size_t Print::println(unsigned long num, int base) {
|
||||||
size_t n = print(num, base);
|
return _println(num, base);
|
||||||
n += println();
|
}
|
||||||
return n;
|
|
||||||
|
size_t Print::println(long long num, int base) {
|
||||||
|
return _println(num, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(unsigned long long num, int base) {
|
||||||
|
return _println(num, base);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(double num, int digits) {
|
size_t Print::println(double num, int digits) {
|
||||||
size_t n = print(num, digits);
|
return _println(num, digits);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::println(const Printable& x) {
|
size_t Print::println(const Printable& x) {
|
||||||
size_t n = print(x);
|
return _println<const Printable&>(x);
|
||||||
n += println();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private Methods /////////////////////////////////////////////////////////////
|
// Private Methods /////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
size_t Print::printNumber(unsigned long n, uint8_t base) {
|
template<typename T, typename... P> inline size_t Print::_println(T v, P... args)
|
||||||
char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte.
|
{
|
||||||
char *str = &buf[sizeof(buf) - 1];
|
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';
|
*str = '\0';
|
||||||
|
|
||||||
// prevent crash if called with base == 1
|
// prevent crash if called with base == 1
|
||||||
if(base < 2)
|
if (base < 2) {
|
||||||
base = 10;
|
base = 10;
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
unsigned long m = n;
|
auto m = n;
|
||||||
n /= base;
|
n /= base;
|
||||||
char c = m - base * n;
|
char c = m - base * n;
|
||||||
|
|
||||||
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
||||||
} while(n);
|
} while (n);
|
||||||
|
|
||||||
return write(str);
|
return write(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Print::printFloat(double number, uint8_t digits) {
|
template<> size_t Print::printNumber(double number, uint8_t digits) {
|
||||||
char buf[40];
|
char buf[40];
|
||||||
return write(dtostrf(number, 0, digits, buf));
|
return write(dtostrf(number, 0, digits, buf));
|
||||||
}
|
}
|
||||||
|
@ -35,17 +35,15 @@
|
|||||||
|
|
||||||
class Print {
|
class Print {
|
||||||
private:
|
private:
|
||||||
int write_error;
|
int write_error = 0;
|
||||||
size_t printNumber(unsigned long, uint8_t);
|
template<typename T> size_t printNumber(T n, uint8_t base);
|
||||||
size_t printFloat(double, uint8_t);
|
template<typename T, typename... P> inline size_t _println(T v, P... args);
|
||||||
protected:
|
protected:
|
||||||
void setWriteError(int err = 1) {
|
void setWriteError(int err = 1) {
|
||||||
write_error = err;
|
write_error = err;
|
||||||
}
|
}
|
||||||
public:
|
public:
|
||||||
Print() :
|
Print() {}
|
||||||
write_error(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
int getWriteError() {
|
int getWriteError() {
|
||||||
return write_error;
|
return write_error;
|
||||||
@ -71,10 +69,16 @@ class Print {
|
|||||||
inline size_t write(unsigned int t) { return write((uint8_t)t); }
|
inline size_t write(unsigned int t) { return write((uint8_t)t); }
|
||||||
inline size_t write(long t) { return write((uint8_t)t); }
|
inline size_t write(long t) { return write((uint8_t)t); }
|
||||||
inline size_t write(unsigned long t) { return write((uint8_t)t); }
|
inline size_t write(unsigned long t) { return write((uint8_t)t); }
|
||||||
|
inline size_t write(long long t) { return write((uint8_t)t); }
|
||||||
|
inline size_t write(unsigned long long t) { return write((uint8_t)t); }
|
||||||
// Enable write(char) to fall through to write(uint8_t)
|
// Enable write(char) to fall through to write(uint8_t)
|
||||||
inline size_t write(char c) { return write((uint8_t) c); }
|
inline size_t write(char c) { return write((uint8_t) c); }
|
||||||
inline size_t write(int8_t c) { return write((uint8_t) c); }
|
inline size_t write(int8_t c) { return write((uint8_t) c); }
|
||||||
|
|
||||||
|
// default to zero, meaning "a single write may block"
|
||||||
|
// should be overriden by subclasses with buffering
|
||||||
|
virtual int availableForWrite() { return 0; }
|
||||||
|
|
||||||
size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));
|
size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));
|
||||||
size_t printf_P(PGM_P format, ...) __attribute__((format(printf, 2, 3)));
|
size_t printf_P(PGM_P format, ...) __attribute__((format(printf, 2, 3)));
|
||||||
size_t print(const __FlashStringHelper *);
|
size_t print(const __FlashStringHelper *);
|
||||||
@ -86,6 +90,8 @@ class Print {
|
|||||||
size_t print(unsigned int, int = DEC);
|
size_t print(unsigned int, int = DEC);
|
||||||
size_t print(long, int = DEC);
|
size_t print(long, int = DEC);
|
||||||
size_t print(unsigned long, int = DEC);
|
size_t print(unsigned long, int = DEC);
|
||||||
|
size_t print(long long, int = DEC);
|
||||||
|
size_t print(unsigned long long, int = DEC);
|
||||||
size_t print(double, int = 2);
|
size_t print(double, int = 2);
|
||||||
size_t print(const Printable&);
|
size_t print(const Printable&);
|
||||||
|
|
||||||
@ -98,6 +104,8 @@ class Print {
|
|||||||
size_t println(unsigned int, int = DEC);
|
size_t println(unsigned int, int = DEC);
|
||||||
size_t println(long, int = DEC);
|
size_t println(long, int = DEC);
|
||||||
size_t println(unsigned long, int = DEC);
|
size_t println(unsigned long, int = DEC);
|
||||||
|
size_t println(long long, int = DEC);
|
||||||
|
size_t println(unsigned long long, int = DEC);
|
||||||
size_t println(double, int = 2);
|
size_t println(double, int = 2);
|
||||||
size_t println(const Printable&);
|
size_t println(const Printable&);
|
||||||
size_t println(void);
|
size_t println(void);
|
||||||
@ -105,4 +113,6 @@ class Print {
|
|||||||
virtual void flush() { /* Empty implementation for backward compatibility */ }
|
virtual void flush() { /* Empty implementation for backward compatibility */ }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<> size_t Print::printNumber(double number, uint8_t digits);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
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 <assert.h>
|
||||||
|
|
||||||
@ -85,6 +102,7 @@ bool schedule_function(const std::function<void(void)>& fn)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IRAM_ATTR // (not only) called from ISR
|
||||||
bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
|
bool schedule_recurrent_function_us(const std::function<bool(void)>& fn,
|
||||||
uint32_t repeat_us, const std::function<bool(void)>& alarm)
|
uint32_t repeat_us, const std::function<bool(void)>& alarm)
|
||||||
{
|
{
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Schedule.h - Header file for scheduled functions.
|
||||||
|
Copyright (c) 2020 esp8266/Arduino
|
||||||
|
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef ESP_SCHEDULE_H
|
#ifndef ESP_SCHEDULE_H
|
||||||
#define ESP_SCHEDULE_H
|
#define ESP_SCHEDULE_H
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@
|
|||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "StackThunk.h"
|
#include "StackThunk.h"
|
||||||
#include <ets_sys.h>
|
#include <ets_sys.h>
|
||||||
|
#include <umm_malloc/umm_malloc.h>
|
||||||
|
#include <umm_malloc/umm_heap_select.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
@ -48,7 +50,14 @@ void stack_thunk_add_ref()
|
|||||||
{
|
{
|
||||||
stack_thunk_refcnt++;
|
stack_thunk_refcnt++;
|
||||||
if (stack_thunk_refcnt == 1) {
|
if (stack_thunk_refcnt == 1) {
|
||||||
|
DBG_MMU_PRINTF("\nStackThunk malloc(%u)\n", _stackSize * sizeof(uint32_t));
|
||||||
|
// The stack must be in DRAM, or an Soft WDT will follow. Not sure why,
|
||||||
|
// maybe too much time is consumed with the non32-bit exception handler.
|
||||||
|
// Also, interrupt handling on an IRAM stack would be very slow.
|
||||||
|
// Strings on the stack would be very slow to access as well.
|
||||||
|
HeapSelectDram ephemeral;
|
||||||
stack_thunk_ptr = (uint32_t *)malloc(_stackSize * sizeof(uint32_t));
|
stack_thunk_ptr = (uint32_t *)malloc(_stackSize * sizeof(uint32_t));
|
||||||
|
DBG_MMU_PRINTF("StackThunk stack_thunk_ptr: %p\n", stack_thunk_ptr);
|
||||||
if (!stack_thunk_ptr) {
|
if (!stack_thunk_ptr) {
|
||||||
// This is a fatal error, stop the sketch
|
// This is a fatal error, stop the sketch
|
||||||
DEBUGV("Unable to allocate BearSSL stack\n");
|
DEBUGV("Unable to allocate BearSSL stack\n");
|
||||||
|
@ -51,7 +51,7 @@ extern uint32_t stack_thunk_refcnt;
|
|||||||
|
|
||||||
// Thunking macro
|
// Thunking macro
|
||||||
#define make_stack_thunk(fcnToThunk) \
|
#define make_stack_thunk(fcnToThunk) \
|
||||||
__asm("\n\
|
__asm__ ("\n\
|
||||||
.text\n\
|
.text\n\
|
||||||
.literal_position\n\
|
.literal_position\n\
|
||||||
.literal .LC_STACK_VALUE"#fcnToThunk", 0xdeadbeef\n\
|
.literal .LC_STACK_VALUE"#fcnToThunk", 0xdeadbeef\n\
|
||||||
|
@ -173,7 +173,7 @@ float Stream::parseFloat(char skipChar) {
|
|||||||
boolean isFraction = false;
|
boolean isFraction = false;
|
||||||
long value = 0;
|
long value = 0;
|
||||||
int c;
|
int c;
|
||||||
float fraction = 1.0;
|
float fraction = 1.0f;
|
||||||
|
|
||||||
c = peekNextDigit();
|
c = peekNextDigit();
|
||||||
// ignore non numeric leading characters
|
// ignore non numeric leading characters
|
||||||
@ -190,7 +190,7 @@ float Stream::parseFloat(char skipChar) {
|
|||||||
else if(c >= '0' && c <= '9') { // is c a digit?
|
else if(c >= '0' && c <= '9') { // is c a digit?
|
||||||
value = value * 10 + c - '0';
|
value = value * 10 + c - '0';
|
||||||
if(isFraction)
|
if(isFraction)
|
||||||
fraction *= 0.1;
|
fraction *= 0.1f;
|
||||||
}
|
}
|
||||||
read(); // consume the character we got with peek
|
read(); // consume the character we got with peek
|
||||||
c = timedPeek();
|
c = timedPeek();
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
class Stream: public Print {
|
class Stream: public Print {
|
||||||
protected:
|
protected:
|
||||||
unsigned long _timeout; // number of milliseconds to wait for the next char before aborting timed read
|
unsigned long _timeout = 1000; // number of milliseconds to wait for the next char before aborting timed read
|
||||||
unsigned long _startMillis; // used for timeout measurement
|
unsigned long _startMillis; // used for timeout measurement
|
||||||
int timedRead(); // private method to read stream with timeout
|
int timedRead(); // private method to read stream with timeout
|
||||||
int timedPeek(); // private method to peek stream with timeout
|
int timedPeek(); // private method to peek stream with timeout
|
||||||
@ -48,9 +48,7 @@ class Stream: public Print {
|
|||||||
virtual int read() = 0;
|
virtual int read() = 0;
|
||||||
virtual int peek() = 0;
|
virtual int peek() = 0;
|
||||||
|
|
||||||
Stream() {
|
Stream() {}
|
||||||
_timeout = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsing methods
|
// parsing methods
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
|
||||||
// autogenerated from https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv
|
// autogenerated from https://raw.githubusercontent.com/nayarsystems/posix_tz_db/master/zones.csv
|
||||||
// by script <esp8266 arduino core>/tools/TZupdate.sh
|
// by script <esp8266 arduino core>/tools/TZupdate.sh
|
||||||
// Thu May 7 19:02:21 UTC 2020
|
// Thu Nov 12 04:07:03 UTC 2020
|
||||||
//
|
//
|
||||||
// This database is autogenerated from IANA timezone database
|
// This database is autogenerated from IANA timezone database
|
||||||
// https://www.iana.org/time-zones
|
// 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
|
// and can be updated on demand in this repository
|
||||||
// or by yourself using the above script
|
// or by yourself using the above script
|
||||||
|
|
||||||
@ -105,7 +106,7 @@
|
|||||||
#define TZ_America_Cuiaba PSTR("<-04>4")
|
#define TZ_America_Cuiaba PSTR("<-04>4")
|
||||||
#define TZ_America_Curacao PSTR("AST4")
|
#define TZ_America_Curacao PSTR("AST4")
|
||||||
#define TZ_America_Danmarkshavn PSTR("GMT0")
|
#define TZ_America_Danmarkshavn PSTR("GMT0")
|
||||||
#define TZ_America_Dawson PSTR("PST8PDT,M3.2.0,M11.1.0")
|
#define TZ_America_Dawson PSTR("MST7")
|
||||||
#define TZ_America_Dawson_Creek PSTR("MST7")
|
#define TZ_America_Dawson_Creek PSTR("MST7")
|
||||||
#define TZ_America_Denver PSTR("MST7MDT,M3.2.0,M11.1.0")
|
#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_Detroit PSTR("EST5EDT,M3.2.0,M11.1.0")
|
||||||
@ -207,14 +208,14 @@
|
|||||||
#define TZ_America_Toronto PSTR("EST5EDT,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_Tortola PSTR("AST4")
|
||||||
#define TZ_America_Vancouver PSTR("PST8PDT,M3.2.0,M11.1.0")
|
#define TZ_America_Vancouver PSTR("PST8PDT,M3.2.0,M11.1.0")
|
||||||
#define TZ_America_Whitehorse 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_Winnipeg PSTR("CST6CDT,M3.2.0,M11.1.0")
|
||||||
#define TZ_America_Yakutat PSTR("AKST9AKDT,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_America_Yellowknife PSTR("MST7MDT,M3.2.0,M11.1.0")
|
||||||
#define TZ_Antarctica_Casey PSTR("<+08>-8")
|
#define TZ_Antarctica_Casey PSTR("<+11>-11")
|
||||||
#define TZ_Antarctica_Davis PSTR("<+07>-7")
|
#define TZ_Antarctica_Davis PSTR("<+07>-7")
|
||||||
#define TZ_Antarctica_DumontDUrville PSTR("<+10>-10")
|
#define TZ_Antarctica_DumontDUrville PSTR("<+10>-10")
|
||||||
#define TZ_Antarctica_Macquarie PSTR("<+11>-11")
|
#define TZ_Antarctica_Macquarie PSTR("AEST-10AEDT,M10.1.0,M4.1.0/3")
|
||||||
#define TZ_Antarctica_Mawson PSTR("<+05>-5")
|
#define TZ_Antarctica_Mawson PSTR("<+05>-5")
|
||||||
#define TZ_Antarctica_McMurdo PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3")
|
#define TZ_Antarctica_McMurdo PSTR("NZST-12NZDT,M9.5.0,M4.1.0/3")
|
||||||
#define TZ_Antarctica_Palmer PSTR("<-03>3")
|
#define TZ_Antarctica_Palmer PSTR("<-03>3")
|
||||||
@ -248,8 +249,8 @@
|
|||||||
#define TZ_Asia_Dubai PSTR("<+04>-4")
|
#define TZ_Asia_Dubai PSTR("<+04>-4")
|
||||||
#define TZ_Asia_Dushanbe PSTR("<+05>-5")
|
#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_Famagusta PSTR("EET-2EEST,M3.5.0/3,M10.5.0/4")
|
||||||
#define TZ_Asia_Gaza PSTR("EET-2EEST,M3.5.5/0,M10.5.6/1")
|
#define TZ_Asia_Gaza PSTR("EET-2EEST,M3.4.4/48,M10.4.4/49")
|
||||||
#define TZ_Asia_Hebron PSTR("EET-2EEST,M3.5.5/0,M10.5.6/1")
|
#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_Ho_Chi_Minh PSTR("<+07>-7")
|
||||||
#define TZ_Asia_Hong_Kong PSTR("HKT-8")
|
#define TZ_Asia_Hong_Kong PSTR("HKT-8")
|
||||||
#define TZ_Asia_Hovd PSTR("<+07>-7")
|
#define TZ_Asia_Hovd PSTR("<+07>-7")
|
||||||
|
@ -25,15 +25,16 @@
|
|||||||
#include "core_esp8266_waveform.h"
|
#include "core_esp8266_waveform.h"
|
||||||
#include "user_interface.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, uint32_t duration) {
|
static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t duration) {
|
||||||
if (_pin > 16) {
|
if (_pin > 16) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop any analogWrites (PWM) because they are a different generator
|
||||||
|
_stopPWM(_pin);
|
||||||
|
// If there's another Tone or startWaveform on this pin
|
||||||
|
// it will be changed on-the-fly (no need to stop it)
|
||||||
|
|
||||||
pinMode(_pin, OUTPUT);
|
pinMode(_pin, OUTPUT);
|
||||||
|
|
||||||
high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency,
|
high = std::max(high, (uint32_t)microsecondsToClockCycles(25)); // new 20KHz maximum tone frequency,
|
||||||
@ -42,9 +43,7 @@ static void _startTone(uint8_t _pin, uint32_t high, uint32_t low, uint32_t durat
|
|||||||
duration = microsecondsToClockCycles(duration * 1000UL);
|
duration = microsecondsToClockCycles(duration * 1000UL);
|
||||||
duration += high + low - 1;
|
duration += high + low - 1;
|
||||||
duration -= duration % (high + low);
|
duration -= duration % (high + low);
|
||||||
if (startWaveformClockCycles(_pin, high, low, duration)) {
|
startWaveformClockCycles(_pin, high, low, duration);
|
||||||
_toneMap |= 1 << _pin;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -86,6 +85,5 @@ void noTone(uint8_t _pin) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
stopWaveform(_pin);
|
stopWaveform(_pin);
|
||||||
_toneMap &= ~(1 << _pin);
|
|
||||||
digitalWrite(_pin, 0);
|
digitalWrite(_pin, 0);
|
||||||
}
|
}
|
||||||
|
@ -83,14 +83,14 @@ class UDP: public Stream {
|
|||||||
// Return the port of the host who sent the current incoming packet
|
// Return the port of the host who sent the current incoming packet
|
||||||
virtual uint16_t remotePort() =0;
|
virtual uint16_t remotePort() =0;
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
uint8_t* rawIPAddress(IPAddress& addr) {
|
uint8_t* rawIPAddress(IPAddress& addr) {
|
||||||
return addr.raw_address();
|
return addr.raw_address();
|
||||||
}
|
}
|
||||||
#if LWIP_VERSION_MAJOR != 1
|
|
||||||
const uint8_t* rawIPAddress(const IPAddress& addr) {
|
const uint8_t* rawIPAddress(const IPAddress& addr) {
|
||||||
return addr.raw_address();
|
return addr.raw_address();
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -27,17 +27,6 @@ extern "C" uint32_t _FS_start;
|
|||||||
extern "C" uint32_t _FS_end;
|
extern "C" uint32_t _FS_end;
|
||||||
|
|
||||||
UpdaterClass::UpdaterClass()
|
UpdaterClass::UpdaterClass()
|
||||||
: _async(false)
|
|
||||||
, _error(0)
|
|
||||||
, _buffer(0)
|
|
||||||
, _bufferLen(0)
|
|
||||||
, _size(0)
|
|
||||||
, _startAddress(0)
|
|
||||||
, _currentAddress(0)
|
|
||||||
, _command(U_FLASH)
|
|
||||||
, _hash(nullptr)
|
|
||||||
, _verify(nullptr)
|
|
||||||
, _progress_callback(nullptr)
|
|
||||||
{
|
{
|
||||||
#if ARDUINO_SIGNING
|
#if ARDUINO_SIGNING
|
||||||
installSignature(&esp8266::updaterSigningHash, &esp8266::updaterSigningVerifier);
|
installSignature(&esp8266::updaterSigningHash, &esp8266::updaterSigningVerifier);
|
||||||
@ -112,6 +101,8 @@ bool UpdaterClass::begin(size_t size, int command, int ledPin, uint8_t ledOn) {
|
|||||||
|
|
||||||
_reset();
|
_reset();
|
||||||
clearError(); // _error = 0
|
clearError(); // _error = 0
|
||||||
|
_target_md5 = emptyString;
|
||||||
|
_md5 = MD5Builder();
|
||||||
|
|
||||||
#ifndef HOST_MOCK
|
#ifndef HOST_MOCK
|
||||||
wifi_set_sleep_type(NONE_SLEEP_T);
|
wifi_set_sleep_type(NONE_SLEEP_T);
|
||||||
@ -212,6 +203,11 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updating w/o any data is an error we detect here
|
||||||
|
if (!progress()) {
|
||||||
|
_setError(UPDATE_ERROR_NO_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
if(hasError() || (!isFinished() && !evenIfRemaining)){
|
if(hasError() || (!isFinished() && !evenIfRemaining)){
|
||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
DEBUG_UPDATER.printf_P(PSTR("premature end: res:%u, pos:%zu/%zu\n"), getError(), progress(), _size);
|
DEBUG_UPDATER.printf_P(PSTR("premature end: res:%u, pos:%zu/%zu\n"), getError(), progress(), _size);
|
||||||
@ -245,7 +241,7 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
|||||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Adjusted binsize: %d\n"), binSize);
|
DEBUG_UPDATER.printf_P(PSTR("[Updater] Adjusted binsize: %d\n"), binSize);
|
||||||
#endif
|
#endif
|
||||||
// Calculate the MD5 and hash using proper size
|
// Calculate the MD5 and hash using proper size
|
||||||
uint8_t buff[128];
|
uint8_t buff[128] __attribute__((aligned(4)));
|
||||||
for(int i = 0; i < binSize; i += sizeof(buff)) {
|
for(int i = 0; i < binSize; i += sizeof(buff)) {
|
||||||
ESP.flashRead(_startAddress + i, (uint32_t *)buff, sizeof(buff));
|
ESP.flashRead(_startAddress + i, (uint32_t *)buff, sizeof(buff));
|
||||||
size_t read = std::min((int)sizeof(buff), binSize - i);
|
size_t read = std::min((int)sizeof(buff), binSize - i);
|
||||||
@ -264,7 +260,7 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
|||||||
_reset();
|
_reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ESP.flashRead(_startAddress + binSize, (uint32_t *)sig, sigLen);
|
ESP.flashRead(_startAddress + binSize, sig, sigLen);
|
||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Received Signature:"));
|
DEBUG_UPDATER.printf_P(PSTR("[Updater] Received Signature:"));
|
||||||
for (size_t i=0; i<sigLen; i++) {
|
for (size_t i=0; i<sigLen; i++) {
|
||||||
@ -279,6 +275,8 @@ bool UpdaterClass::end(bool evenIfRemaining){
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
free(sig);
|
free(sig);
|
||||||
|
_size = binSize; // Adjust size to remove signature, not part of bin payload
|
||||||
|
|
||||||
#ifdef DEBUG_UPDATER
|
#ifdef DEBUG_UPDATER
|
||||||
DEBUG_UPDATER.printf_P(PSTR("[Updater] Signature matches\n"));
|
DEBUG_UPDATER.printf_P(PSTR("[Updater] Signature matches\n"));
|
||||||
#endif
|
#endif
|
||||||
@ -364,7 +362,7 @@ bool UpdaterClass::_writeBuffer(){
|
|||||||
|
|
||||||
if (eraseResult) {
|
if (eraseResult) {
|
||||||
if(!_async) yield();
|
if(!_async) yield();
|
||||||
writeResult = ESP.flashWrite(_currentAddress, (uint32_t*) _buffer, _bufferLen);
|
writeResult = ESP.flashWrite(_currentAddress, _buffer, _bufferLen);
|
||||||
} else { // if erase was unsuccessful
|
} else { // if erase was unsuccessful
|
||||||
_currentAddress = (_startAddress + _size);
|
_currentAddress = (_startAddress + _size);
|
||||||
_setError(UPDATE_ERROR_ERASE);
|
_setError(UPDATE_ERROR_ERASE);
|
||||||
@ -442,7 +440,7 @@ bool UpdaterClass::_verifyHeader(uint8_t data) {
|
|||||||
bool UpdaterClass::_verifyEnd() {
|
bool UpdaterClass::_verifyEnd() {
|
||||||
if(_command == U_FLASH) {
|
if(_command == U_FLASH) {
|
||||||
|
|
||||||
uint8_t buf[4];
|
uint8_t buf[4] __attribute__((aligned(4)));
|
||||||
if(!ESP.flashRead(_startAddress, (uint32_t *) &buf[0], 4)) {
|
if(!ESP.flashRead(_startAddress, (uint32_t *) &buf[0], 4)) {
|
||||||
_currentAddress = (_startAddress);
|
_currentAddress = (_startAddress);
|
||||||
_setError(UPDATE_ERROR_READ);
|
_setError(UPDATE_ERROR_READ);
|
||||||
@ -558,6 +556,8 @@ void UpdaterClass::printError(Print &out){
|
|||||||
out.println(F("Bad Size Given"));
|
out.println(F("Bad Size Given"));
|
||||||
} else if(_error == UPDATE_ERROR_STREAM){
|
} else if(_error == UPDATE_ERROR_STREAM){
|
||||||
out.println(F("Stream Read Timeout"));
|
out.println(F("Stream Read Timeout"));
|
||||||
|
} else if(_error == UPDATE_ERROR_NO_DATA){
|
||||||
|
out.println(F("No data supplied"));
|
||||||
} else if(_error == UPDATE_ERROR_MD5){
|
} else if(_error == UPDATE_ERROR_MD5){
|
||||||
out.printf_P(PSTR("MD5 Failed: expected:%s, calculated:%s\n"), _target_md5.c_str(), _md5.toString().c_str());
|
out.printf_P(PSTR("MD5 Failed: expected:%s, calculated:%s\n"), _target_md5.c_str(), _md5.toString().c_str());
|
||||||
} else if(_error == UPDATE_ERROR_SIGN){
|
} else if(_error == UPDATE_ERROR_SIGN){
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#define UPDATE_ERROR_MAGIC_BYTE (10)
|
#define UPDATE_ERROR_MAGIC_BYTE (10)
|
||||||
#define UPDATE_ERROR_BOOTSTRAP (11)
|
#define UPDATE_ERROR_BOOTSTRAP (11)
|
||||||
#define UPDATE_ERROR_SIGN (12)
|
#define UPDATE_ERROR_SIGN (12)
|
||||||
|
#define UPDATE_ERROR_NO_DATA (13)
|
||||||
|
|
||||||
#define U_FLASH 0
|
#define U_FLASH 0
|
||||||
#define U_FS 100
|
#define U_FS 100
|
||||||
@ -182,27 +183,27 @@ class UpdaterClass {
|
|||||||
|
|
||||||
void _setError(int error);
|
void _setError(int error);
|
||||||
|
|
||||||
bool _async;
|
bool _async = false;
|
||||||
uint8_t _error;
|
uint8_t _error = 0;
|
||||||
uint8_t *_buffer;
|
uint8_t *_buffer = nullptr;
|
||||||
size_t _bufferLen; // amount of data written into _buffer
|
size_t _bufferLen = 0; // amount of data written into _buffer
|
||||||
size_t _bufferSize; // total size of _buffer
|
size_t _bufferSize = 0; // total size of _buffer
|
||||||
size_t _size;
|
size_t _size = 0;
|
||||||
uint32_t _startAddress;
|
uint32_t _startAddress = 0;
|
||||||
uint32_t _currentAddress;
|
uint32_t _currentAddress = 0;
|
||||||
uint32_t _command;
|
uint32_t _command = U_FLASH;
|
||||||
|
|
||||||
String _target_md5;
|
String _target_md5;
|
||||||
MD5Builder _md5;
|
MD5Builder _md5;
|
||||||
|
|
||||||
int _ledPin;
|
int _ledPin = -1;
|
||||||
uint8_t _ledOn;
|
uint8_t _ledOn;
|
||||||
|
|
||||||
// Optional signed binary verification
|
// Optional signed binary verification
|
||||||
UpdaterHashClass *_hash;
|
UpdaterHashClass *_hash = nullptr;
|
||||||
UpdaterVerifyClass *_verify;
|
UpdaterVerifyClass *_verify = nullptr;
|
||||||
// Optional progress callback function
|
// Optional progress callback function
|
||||||
THandlerFunction_Progress _progress_callback;
|
THandlerFunction_Progress _progress_callback = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern UpdaterClass Update;
|
extern UpdaterClass Update;
|
||||||
|
@ -25,6 +25,12 @@
|
|||||||
#include "WString.h"
|
#include "WString.h"
|
||||||
#include "stdlib_noniso.h"
|
#include "stdlib_noniso.h"
|
||||||
|
|
||||||
|
#define OOM_STRING_BORDER_DISPLAY 10
|
||||||
|
#define OOM_STRING_THRESHOLD_REALLOC_WARN 128
|
||||||
|
|
||||||
|
#define __STRHELPER(x) #x
|
||||||
|
#define STR(x) __STRHELPER(x) // stringifier
|
||||||
|
|
||||||
/*********************************************/
|
/*********************************************/
|
||||||
/* Constructors */
|
/* Constructors */
|
||||||
/*********************************************/
|
/*********************************************/
|
||||||
@ -32,7 +38,7 @@
|
|||||||
String::String(const char *cstr) {
|
String::String(const char *cstr) {
|
||||||
init();
|
init();
|
||||||
if (cstr)
|
if (cstr)
|
||||||
copy(cstr, strlen(cstr));
|
copy(cstr, strlen_P(cstr));
|
||||||
}
|
}
|
||||||
|
|
||||||
String::String(const String &value) {
|
String::String(const String &value) {
|
||||||
@ -45,25 +51,15 @@ String::String(const __FlashStringHelper *pstr) {
|
|||||||
*this = pstr; // see operator =
|
*this = pstr; // see operator =
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
String::String(String &&rval) noexcept {
|
||||||
String::String(String &&rval) {
|
|
||||||
init();
|
init();
|
||||||
move(rval);
|
move(rval);
|
||||||
}
|
}
|
||||||
|
|
||||||
String::String(StringSumHelper &&rval) {
|
String::String(StringSumHelper &&rval) noexcept {
|
||||||
init();
|
init();
|
||||||
move(rval);
|
move(rval);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
String::String(char c) {
|
|
||||||
init();
|
|
||||||
char buf[2];
|
|
||||||
buf[0] = c;
|
|
||||||
buf[1] = 0;
|
|
||||||
*this = buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
String::String(unsigned char value, unsigned char base) {
|
String::String(unsigned char value, unsigned char base) {
|
||||||
init();
|
init();
|
||||||
@ -93,7 +89,7 @@ String::String(unsigned int value, unsigned char base) {
|
|||||||
String::String(long value, unsigned char base) {
|
String::String(long value, unsigned char base) {
|
||||||
init();
|
init();
|
||||||
char buf[2 + 8 * sizeof(long)];
|
char buf[2 + 8 * sizeof(long)];
|
||||||
if (base==10) {
|
if (base == 10) {
|
||||||
sprintf(buf, "%ld", value);
|
sprintf(buf, "%ld", value);
|
||||||
} else {
|
} else {
|
||||||
ltoa(value, buf, base);
|
ltoa(value, buf, base);
|
||||||
@ -108,6 +104,32 @@ String::String(unsigned long value, unsigned char base) {
|
|||||||
*this = buf;
|
*this = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String::String(long long value) {
|
||||||
|
init();
|
||||||
|
char buf[2 + 8 * sizeof(long long)];
|
||||||
|
sprintf(buf, "%lld", value);
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(unsigned long long value) {
|
||||||
|
init();
|
||||||
|
char buf[1 + 8 * sizeof(unsigned long long)];
|
||||||
|
sprintf(buf, "%llu", value);
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(long long value, unsigned char base) {
|
||||||
|
init();
|
||||||
|
char buf[2 + 8 * sizeof(long long)];
|
||||||
|
*this = lltoa(value, buf, sizeof(buf), base);
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(unsigned long long value, unsigned char base) {
|
||||||
|
init();
|
||||||
|
char buf[1 + 8 * sizeof(unsigned long long)];
|
||||||
|
*this = ulltoa(value, buf, sizeof(buf), base);
|
||||||
|
}
|
||||||
|
|
||||||
String::String(float value, unsigned char decimalPlaces) {
|
String::String(float value, unsigned char decimalPlaces) {
|
||||||
init();
|
init();
|
||||||
char buf[33];
|
char buf[33];
|
||||||
@ -120,31 +142,21 @@ String::String(double value, unsigned char decimalPlaces) {
|
|||||||
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
|
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
String::~String() {
|
/*********************************************/
|
||||||
invalidate();
|
/* Memory Management */
|
||||||
}
|
/*********************************************/
|
||||||
|
|
||||||
// /*********************************************/
|
|
||||||
// /* Memory Management */
|
|
||||||
// /*********************************************/
|
|
||||||
|
|
||||||
inline void String::init(void) {
|
|
||||||
setSSO(true);
|
|
||||||
setLen(0);
|
|
||||||
wbuffer()[0] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::invalidate(void) {
|
void String::invalidate(void) {
|
||||||
if(!isSSO() && wbuffer())
|
if (!isSSO() && wbuffer())
|
||||||
free(wbuffer());
|
free(wbuffer());
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::reserve(unsigned int size) {
|
unsigned char String::reserve(unsigned int size) {
|
||||||
if(buffer() && capacity() >= size)
|
if (buffer() && capacity() >= size)
|
||||||
return 1;
|
return 1;
|
||||||
if(changeBuffer(size)) {
|
if (changeBuffer(size)) {
|
||||||
if(len() == 0)
|
if (len() == 0)
|
||||||
wbuffer()[0] = 0;
|
wbuffer()[0] = 0;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -159,35 +171,40 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
|
|||||||
uint16_t oldLen = len();
|
uint16_t oldLen = len();
|
||||||
setSSO(true);
|
setSSO(true);
|
||||||
setLen(oldLen);
|
setLen(oldLen);
|
||||||
return 1;
|
|
||||||
} else { // if bufptr && !isSSO()
|
} else { // if bufptr && !isSSO()
|
||||||
// Using bufptr, need to shrink into sso.buff
|
// Using bufptr, need to shrink into sso.buff
|
||||||
char temp[sizeof(sso.buff)];
|
const char *temp = buffer();
|
||||||
memcpy(temp, buffer(), maxStrLen);
|
|
||||||
free(wbuffer());
|
|
||||||
uint16_t oldLen = len();
|
uint16_t oldLen = len();
|
||||||
setSSO(true);
|
setSSO(true);
|
||||||
setLen(oldLen);
|
setLen(oldLen);
|
||||||
memcpy(wbuffer(), temp, maxStrLen);
|
memcpy(wbuffer(), temp, maxStrLen);
|
||||||
return 1;
|
free((void *)temp);
|
||||||
}
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
// Fallthrough to normal allocator
|
// Fallthrough to normal allocator
|
||||||
size_t newSize = (maxStrLen + 16) & (~0xf);
|
size_t newSize = (maxStrLen + 16) & (~0xf);
|
||||||
|
#ifdef DEBUG_ESP_OOM
|
||||||
|
if (!isSSO() && capacity() >= OOM_STRING_THRESHOLD_REALLOC_WARN && maxStrLen > capacity()) {
|
||||||
|
// warn when badly re-allocating
|
||||||
|
DEBUGV("[offending String op %d->%d ('%." STR(OOM_STRING_BORDER_DISPLAY) "s ... %." STR(OOM_STRING_BORDER_DISPLAY) "s')]\n",
|
||||||
|
len(), maxStrLen, c_str(),
|
||||||
|
len() > OOM_STRING_BORDER_DISPLAY? c_str() + std::max((int)len() - OOM_STRING_BORDER_DISPLAY, OOM_STRING_BORDER_DISPLAY): "");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
// Make sure we can fit newsize in the buffer
|
// Make sure we can fit newsize in the buffer
|
||||||
if (newSize > CAPACITY_MAX) {
|
if (newSize > CAPACITY_MAX) {
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
uint16_t oldLen = len();
|
uint16_t oldLen = len();
|
||||||
char *newbuffer = (char *) realloc(isSSO() ? nullptr : wbuffer(), newSize);
|
char *newbuffer = (char *)realloc(isSSO() ? nullptr : wbuffer(), newSize);
|
||||||
if (newbuffer) {
|
if (newbuffer) {
|
||||||
size_t oldSize = capacity() + 1; // include NULL.
|
size_t oldSize = capacity() + 1; // include NULL.
|
||||||
if (isSSO()) {
|
if (isSSO()) {
|
||||||
// Copy the SSO buffer into allocated space
|
// Copy the SSO buffer into allocated space
|
||||||
memmove_P(newbuffer, sso.buff, sizeof(sso.buff));
|
memmove_P(newbuffer, sso.buff, sizeof(sso.buff));
|
||||||
}
|
}
|
||||||
if (newSize > oldSize)
|
if (newSize > oldSize) {
|
||||||
{
|
|
||||||
memset(newbuffer + oldSize, 0, newSize - oldSize);
|
memset(newbuffer + oldSize, 0, newSize - oldSize);
|
||||||
}
|
}
|
||||||
setSSO(false);
|
setSSO(false);
|
||||||
@ -199,11 +216,11 @@ unsigned char String::changeBuffer(unsigned int maxStrLen) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Copy and Move */
|
/* Copy and Move */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
String & String::copy(const char *cstr, unsigned int length) {
|
String &String::copy(const char *cstr, unsigned int length) {
|
||||||
if (!reserve(length)) {
|
if (!reserve(length)) {
|
||||||
invalidate();
|
invalidate();
|
||||||
return *this;
|
return *this;
|
||||||
@ -213,7 +230,7 @@ String & String::copy(const char *cstr, unsigned int length) {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
String & String::copy(const __FlashStringHelper *pstr, unsigned int length) {
|
String &String::copy(const __FlashStringHelper *pstr, unsigned int length) {
|
||||||
if (!reserve(length)) {
|
if (!reserve(length)) {
|
||||||
invalidate();
|
invalidate();
|
||||||
return *this;
|
return *this;
|
||||||
@ -223,83 +240,47 @@ String & String::copy(const __FlashStringHelper *pstr, unsigned int length) {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
void String::move(String &rhs) noexcept {
|
||||||
void String::move(String &rhs) {
|
invalidate();
|
||||||
if (buffer()) {
|
sso = rhs.sso;
|
||||||
if (capacity() >= rhs.len()) {
|
rhs.init();
|
||||||
memmove_P(wbuffer(), rhs.buffer(), rhs.length() + 1);
|
|
||||||
setLen(rhs.len());
|
|
||||||
rhs.invalidate();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (!isSSO()) {
|
|
||||||
free(wbuffer());
|
|
||||||
setBuffer(nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rhs.isSSO()) {
|
|
||||||
setSSO(true);
|
|
||||||
memmove_P(sso.buff, rhs.sso.buff, sizeof(sso.buff));
|
|
||||||
} else {
|
|
||||||
setSSO(false);
|
|
||||||
setBuffer(rhs.wbuffer());
|
|
||||||
}
|
|
||||||
setCapacity(rhs.capacity());
|
|
||||||
setLen(rhs.len());
|
|
||||||
rhs.setSSO(false);
|
|
||||||
rhs.setCapacity(0);
|
|
||||||
rhs.setLen(0);
|
|
||||||
rhs.setBuffer(nullptr);
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
String & String::operator =(const String &rhs) {
|
String &String::operator =(const String &rhs) {
|
||||||
if (this == &rhs)
|
if (this == &rhs)
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
if (rhs.buffer())
|
if (rhs.buffer())
|
||||||
copy(rhs.buffer(), rhs.len());
|
copy(rhs.buffer(), rhs.len());
|
||||||
else
|
else
|
||||||
invalidate();
|
invalidate();
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
String &String::operator =(String &&rval) noexcept {
|
||||||
String & String::operator =(String &&rval) {
|
|
||||||
if (this != &rval)
|
if (this != &rval)
|
||||||
move(rval);
|
move(rval);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
String & String::operator =(StringSumHelper &&rval) {
|
String &String::operator =(const char *cstr) {
|
||||||
if (this != &rval)
|
|
||||||
move(rval);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
String & String::operator =(const char *cstr) {
|
|
||||||
if (cstr)
|
if (cstr)
|
||||||
copy(cstr, strlen(cstr));
|
copy(cstr, strlen(cstr));
|
||||||
else
|
else
|
||||||
invalidate();
|
invalidate();
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
String & String::operator = (const __FlashStringHelper *pstr)
|
String &String::operator =(const __FlashStringHelper *pstr) {
|
||||||
{
|
if (pstr)
|
||||||
if (pstr) copy(pstr, strlen_P((PGM_P)pstr));
|
copy(pstr, strlen_P((PGM_P)pstr));
|
||||||
else invalidate();
|
else
|
||||||
|
invalidate();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* concat */
|
/* concat */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
unsigned char String::concat(const String &s) {
|
unsigned char String::concat(const String &s) {
|
||||||
// Special case if we're concatting ourself (s += s;) since we may end up
|
// Special case if we're concatting ourself (s += s;) since we may end up
|
||||||
@ -314,7 +295,7 @@ unsigned char String::concat(const String &s) {
|
|||||||
return 0;
|
return 0;
|
||||||
memmove_P(wbuffer() + len(), buffer(), len());
|
memmove_P(wbuffer() + len(), buffer(), len());
|
||||||
setLen(newlen);
|
setLen(newlen);
|
||||||
wbuffer()[len()] = 0;
|
wbuffer()[newlen] = 0;
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return concat(s.buffer(), s.len());
|
return concat(s.buffer(), s.len());
|
||||||
@ -342,22 +323,17 @@ unsigned char String::concat(const char *cstr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(char c) {
|
unsigned char String::concat(char c) {
|
||||||
char buf[2];
|
return concat(&c, 1);
|
||||||
buf[0] = c;
|
|
||||||
buf[1] = 0;
|
|
||||||
return concat(buf, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(unsigned char num) {
|
unsigned char String::concat(unsigned char num) {
|
||||||
char buf[1 + 3 * sizeof(unsigned char)];
|
char buf[1 + 3 * sizeof(unsigned char)];
|
||||||
sprintf(buf, "%d", num);
|
return concat(buf, sprintf(buf, "%d", num));
|
||||||
return concat(buf, strlen(buf));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(int num) {
|
unsigned char String::concat(int num) {
|
||||||
char buf[2 + 3 * sizeof(int)];
|
char buf[2 + 3 * sizeof(int)];
|
||||||
sprintf(buf, "%d", num);
|
return concat(buf, sprintf(buf, "%d", num));
|
||||||
return concat(buf, strlen(buf));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(unsigned int num) {
|
unsigned char String::concat(unsigned int num) {
|
||||||
@ -368,8 +344,7 @@ unsigned char String::concat(unsigned int num) {
|
|||||||
|
|
||||||
unsigned char String::concat(long num) {
|
unsigned char String::concat(long num) {
|
||||||
char buf[2 + 3 * sizeof(long)];
|
char buf[2 + 3 * sizeof(long)];
|
||||||
sprintf(buf, "%ld", num);
|
return concat(buf, sprintf(buf, "%ld", num));
|
||||||
return concat(buf, strlen(buf));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(unsigned long num) {
|
unsigned char String::concat(unsigned long num) {
|
||||||
@ -378,24 +353,37 @@ unsigned char String::concat(unsigned long num) {
|
|||||||
return concat(buf, strlen(buf));
|
return concat(buf, strlen(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned char String::concat(long long num) {
|
||||||
|
char buf[2 + 3 * sizeof(long long)];
|
||||||
|
return concat(buf, sprintf(buf, "%lld", num));
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char String::concat(unsigned long long num) {
|
||||||
|
char buf[1 + 3 * sizeof(unsigned long long)];
|
||||||
|
return concat(buf, sprintf(buf, "%llu", num));
|
||||||
|
}
|
||||||
|
|
||||||
unsigned char String::concat(float num) {
|
unsigned char String::concat(float num) {
|
||||||
char buf[20];
|
char buf[20];
|
||||||
char* string = dtostrf(num, 4, 2, buf);
|
char *string = dtostrf(num, 4, 2, buf);
|
||||||
return concat(string, strlen(string));
|
return concat(string, strlen(string));
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(double num) {
|
unsigned char String::concat(double num) {
|
||||||
char buf[20];
|
char buf[20];
|
||||||
char* string = dtostrf(num, 4, 2, buf);
|
char *string = dtostrf(num, 4, 2, buf);
|
||||||
return concat(string, strlen(string));
|
return concat(string, strlen(string));
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::concat(const __FlashStringHelper * str) {
|
unsigned char String::concat(const __FlashStringHelper *str) {
|
||||||
if (!str) return 0;
|
if (!str)
|
||||||
|
return 0;
|
||||||
int length = strlen_P((PGM_P)str);
|
int length = strlen_P((PGM_P)str);
|
||||||
if (length == 0) return 1;
|
if (length == 0)
|
||||||
|
return 1;
|
||||||
unsigned int newlen = len() + length;
|
unsigned int newlen = len() + length;
|
||||||
if (!reserve(newlen)) return 0;
|
if (!reserve(newlen))
|
||||||
|
return 0;
|
||||||
memcpy_P(wbuffer() + len(), (PGM_P)str, length + 1);
|
memcpy_P(wbuffer() + len(), (PGM_P)str, length + 1);
|
||||||
setLen(newlen);
|
setLen(newlen);
|
||||||
return 1;
|
return 1;
|
||||||
@ -405,94 +393,107 @@ unsigned char String::concat(const __FlashStringHelper * str) {
|
|||||||
/* Concatenate */
|
/* Concatenate */
|
||||||
/*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs) {
|
StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
if (!a.concat(rhs.buffer(), rhs.len()))
|
if (!a.concat(rhs.buffer(), rhs.len()))
|
||||||
a.invalidate();
|
a.invalidate();
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr) {
|
StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
if (!cstr || !a.concat(cstr, strlen(cstr)))
|
if (!cstr || !a.concat(cstr, strlen(cstr)))
|
||||||
a.invalidate();
|
a.invalidate();
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, char c) {
|
StringSumHelper &operator +(const StringSumHelper &lhs, char c) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
if (!a.concat(c))
|
if (!a.concat(c))
|
||||||
a.invalidate();
|
a.invalidate();
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num) {
|
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
if (!a.concat(num))
|
if (!a.concat(num))
|
||||||
a.invalidate();
|
a.invalidate();
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, int num) {
|
StringSumHelper &operator +(const StringSumHelper &lhs, int num) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
if (!a.concat(num))
|
if (!a.concat(num))
|
||||||
a.invalidate();
|
a.invalidate();
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num) {
|
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned int num) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
if (!a.concat(num))
|
if (!a.concat(num))
|
||||||
a.invalidate();
|
a.invalidate();
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, long num) {
|
StringSumHelper &operator +(const StringSumHelper &lhs, long num) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
if (!a.concat(num))
|
if (!a.concat(num))
|
||||||
a.invalidate();
|
a.invalidate();
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num) {
|
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long num) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
if (!a.concat(num))
|
if (!a.concat(num))
|
||||||
a.invalidate();
|
a.invalidate();
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, float num) {
|
StringSumHelper &operator +(const StringSumHelper &lhs, long long num) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
if (!a.concat(num))
|
if (!a.concat(num))
|
||||||
a.invalidate();
|
a.invalidate();
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator +(const StringSumHelper &lhs, double num) {
|
StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long long num) {
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
if (!a.concat(num))
|
if (!a.concat(num))
|
||||||
a.invalidate();
|
a.invalidate();
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringSumHelper & operator + (const StringSumHelper &lhs, const __FlashStringHelper *rhs)
|
StringSumHelper &operator +(const StringSumHelper &lhs, float num) {
|
||||||
{
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
if (!a.concat(num))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper &operator +(const StringSumHelper &lhs, double num) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
|
if (!a.concat(num))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper &>(lhs);
|
||||||
if (!a.concat(rhs))
|
if (!a.concat(rhs))
|
||||||
a.invalidate();
|
a.invalidate();
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Comparison */
|
/* Comparison */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
int String::compareTo(const String &s) const {
|
int String::compareTo(const String &s) const {
|
||||||
if(!buffer() || !s.buffer()) {
|
if (!buffer() || !s.buffer()) {
|
||||||
if(s.buffer() && s.len() > 0)
|
if (s.buffer() && s.len() > 0)
|
||||||
return 0 - *(unsigned char *) s.buffer();
|
return 0 - *(unsigned char *)s.buffer();
|
||||||
if(buffer() && len() > 0)
|
if (buffer() && len() > 0)
|
||||||
return *(unsigned char *) buffer();
|
return *(unsigned char *)buffer();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return strcmp(buffer(), s.buffer());
|
return strcmp(buffer(), s.buffer());
|
||||||
@ -550,7 +551,7 @@ unsigned char String::equalsConstantTime(const String &s2) const {
|
|||||||
//at this point lengths are the same
|
//at this point lengths are the same
|
||||||
if (len() == 0)
|
if (len() == 0)
|
||||||
return 1;
|
return 1;
|
||||||
//at this point lenghts are the same and non-zero
|
//at this point lengths are the same and non-zero
|
||||||
const char *p1 = buffer();
|
const char *p1 = buffer();
|
||||||
const char *p2 = s2.buffer();
|
const char *p2 = s2.buffer();
|
||||||
unsigned int equalchars = 0;
|
unsigned int equalchars = 0;
|
||||||
@ -570,37 +571,33 @@ unsigned char String::equalsConstantTime(const String &s2) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::startsWith(const String &s2) const {
|
unsigned char String::startsWith(const String &s2) const {
|
||||||
if(len() < s2.len())
|
if (len() < s2.len())
|
||||||
return 0;
|
return 0;
|
||||||
return startsWith(s2, 0);
|
return startsWith(s2, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::startsWith(const String &s2, unsigned int offset) const {
|
unsigned char String::startsWith(const String &s2, unsigned int offset) const {
|
||||||
if(offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer())
|
if (offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer())
|
||||||
return 0;
|
return 0;
|
||||||
return strncmp(&buffer()[offset], s2.buffer(), s2.len()) == 0;
|
return strncmp(&buffer()[offset], s2.buffer(), s2.len()) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char String::endsWith(const String &s2) const {
|
unsigned char String::endsWith(const String &s2) const {
|
||||||
if(len() < s2.len() || !buffer() || !s2.buffer())
|
if (len() < s2.len() || !buffer() || !s2.buffer())
|
||||||
return 0;
|
return 0;
|
||||||
return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0;
|
return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Character Access */
|
/* Character Access */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
char String::charAt(unsigned int loc) const {
|
|
||||||
return operator[](loc);
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::setCharAt(unsigned int loc, char c) {
|
void String::setCharAt(unsigned int loc, char c) {
|
||||||
if (loc < len())
|
if (loc < len())
|
||||||
wbuffer()[loc] = c;
|
wbuffer()[loc] = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
char & String::operator[](unsigned int index) {
|
char &String::operator[](unsigned int index) {
|
||||||
static char dummy_writable_char;
|
static char dummy_writable_char;
|
||||||
if (index >= len() || !buffer()) {
|
if (index >= len() || !buffer()) {
|
||||||
dummy_writable_char = 0;
|
dummy_writable_char = 0;
|
||||||
@ -625,54 +622,51 @@ void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int ind
|
|||||||
unsigned int n = bufsize - 1;
|
unsigned int n = bufsize - 1;
|
||||||
if (n > len() - index)
|
if (n > len() - index)
|
||||||
n = len() - index;
|
n = len() - index;
|
||||||
strncpy((char *) buf, buffer() + index, n);
|
strncpy((char *)buf, buffer() + index, n);
|
||||||
buf[n] = 0;
|
buf[n] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Search */
|
/* Search */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
int String::indexOf(char c) const {
|
|
||||||
return indexOf(c, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int String::indexOf(char ch, unsigned int fromIndex) const {
|
int String::indexOf(char ch, unsigned int fromIndex) const {
|
||||||
if (fromIndex >= len())
|
if (fromIndex >= len())
|
||||||
return -1;
|
return -1;
|
||||||
const char* temp = strchr(buffer() + fromIndex, ch);
|
const char *temp = strchr(buffer() + fromIndex, ch);
|
||||||
if (temp == NULL)
|
if (temp == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
return temp - buffer();
|
return temp - buffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::indexOf(const String &s2) const {
|
int String::indexOf(const char *s2, unsigned int fromIndex) const {
|
||||||
return indexOf(s2, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int String::indexOf(const String &s2, unsigned int fromIndex) const {
|
|
||||||
if (fromIndex >= len())
|
if (fromIndex >= len())
|
||||||
return -1;
|
return -1;
|
||||||
const char *found = strstr(buffer() + fromIndex, s2.buffer());
|
const char *found = strstr_P(buffer() + fromIndex, s2);
|
||||||
if (found == NULL)
|
if (found == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
return found - buffer();
|
return found - buffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::lastIndexOf(char theChar) const {
|
int String::indexOf(const String &s2, unsigned int fromIndex) const {
|
||||||
return lastIndexOf(theChar, len() - 1);
|
return indexOf(s2.c_str(), fromIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
int String::lastIndexOf(char ch) const {
|
||||||
|
return lastIndexOf(ch, len() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::lastIndexOf(char ch, unsigned int fromIndex) const {
|
int String::lastIndexOf(char ch, unsigned int fromIndex) const {
|
||||||
if (fromIndex >= len())
|
if (fromIndex >= len())
|
||||||
return -1;
|
return -1;
|
||||||
char tempchar = buffer()[fromIndex + 1];
|
char *writeTo = wbuffer();
|
||||||
wbuffer()[fromIndex + 1] = '\0';
|
char tempchar = writeTo[fromIndex + 1]; // save the replaced character
|
||||||
char* temp = strrchr(wbuffer(), ch);
|
writeTo[fromIndex + 1] = '\0';
|
||||||
wbuffer()[fromIndex + 1] = tempchar;
|
char *temp = strrchr(writeTo, ch);
|
||||||
|
writeTo[fromIndex + 1] = tempchar; // restore character
|
||||||
if (temp == NULL)
|
if (temp == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
return temp - buffer();
|
return temp - writeTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
int String::lastIndexOf(const String &s2) const {
|
int String::lastIndexOf(const String &s2) const {
|
||||||
@ -685,11 +679,11 @@ int String::lastIndexOf(const String &s2, unsigned int fromIndex) const {
|
|||||||
if (fromIndex >= len())
|
if (fromIndex >= len())
|
||||||
fromIndex = len() - 1;
|
fromIndex = len() - 1;
|
||||||
int found = -1;
|
int found = -1;
|
||||||
for (char *p = wbuffer(); p <= wbuffer() + fromIndex; p++) {
|
for (const char *p = buffer(); p <= buffer() + fromIndex; p++) {
|
||||||
p = strstr(p, s2.buffer());
|
p = strstr(p, s2.buffer());
|
||||||
if (!p)
|
if (!p)
|
||||||
break;
|
break;
|
||||||
if ((unsigned int) (p - wbuffer()) <= fromIndex)
|
if ((unsigned int)(p - buffer()) <= fromIndex)
|
||||||
found = p - buffer();
|
found = p - buffer();
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
@ -706,16 +700,17 @@ String String::substring(unsigned int left, unsigned int right) const {
|
|||||||
return out;
|
return out;
|
||||||
if (right > len())
|
if (right > len())
|
||||||
right = len();
|
right = len();
|
||||||
char temp = buffer()[right]; // save the replaced character
|
char *writeTo = wbuffer();
|
||||||
wbuffer()[right] = '\0';
|
char tempchar = writeTo[right]; // save the replaced character
|
||||||
out = wbuffer() + left; // pointer arithmetic
|
writeTo[right] = '\0';
|
||||||
wbuffer()[right] = temp; //restore character
|
out = writeTo + left; // pointer arithmetic
|
||||||
|
writeTo[right] = tempchar; // restore character
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Modification */
|
/* Modification */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
void String::replace(char find, char replace) {
|
void String::replace(char find, char replace) {
|
||||||
if (!buffer())
|
if (!buffer())
|
||||||
@ -726,7 +721,7 @@ void String::replace(char find, char replace) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::replace(const String& find, const String& replace) {
|
void String::replace(const String &find, const String &replace) {
|
||||||
if (len() == 0 || find.len() == 0)
|
if (len() == 0 || find.len() == 0)
|
||||||
return;
|
return;
|
||||||
int diff = replace.len() - find.len();
|
int diff = replace.len() - find.len();
|
||||||
@ -748,7 +743,7 @@ void String::replace(const String& find, const String& replace) {
|
|||||||
readFrom = foundAt + find.len();
|
readFrom = foundAt + find.len();
|
||||||
setLen(len() + diff);
|
setLen(len() + diff);
|
||||||
}
|
}
|
||||||
memmove_P(writeTo, readFrom, strlen(readFrom)+1);
|
memmove_P(writeTo, readFrom, strlen(readFrom) + 1);
|
||||||
} else {
|
} else {
|
||||||
unsigned int size = len(); // compute size needed for result
|
unsigned int size = len(); // compute size needed for result
|
||||||
while ((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
while ((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
||||||
@ -772,13 +767,6 @@ void String::replace(const String& find, const String& replace) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void String::remove(unsigned int index) {
|
|
||||||
// Pass the biggest integer as the count. The remove method
|
|
||||||
// below will take care of truncating it at the end of the
|
|
||||||
// string.
|
|
||||||
remove(index, (unsigned int) -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
void String::remove(unsigned int index, unsigned int count) {
|
void String::remove(unsigned int index, unsigned int count) {
|
||||||
if (index >= len()) {
|
if (index >= len()) {
|
||||||
return;
|
return;
|
||||||
@ -828,9 +816,9 @@ void String::trim(void) {
|
|||||||
wbuffer()[newlen] = 0;
|
wbuffer()[newlen] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
// /* Parsing / Conversion */
|
/* Parsing / Conversion */
|
||||||
// /*********************************************/
|
/*********************************************/
|
||||||
|
|
||||||
long String::toInt(void) const {
|
long String::toInt(void) const {
|
||||||
if (buffer())
|
if (buffer())
|
||||||
@ -841,11 +829,10 @@ long String::toInt(void) const {
|
|||||||
float String::toFloat(void) const {
|
float String::toFloat(void) const {
|
||||||
if (buffer())
|
if (buffer())
|
||||||
return atof(buffer());
|
return atof(buffer());
|
||||||
return 0;
|
return 0.0F;
|
||||||
}
|
}
|
||||||
|
|
||||||
double String::toDouble(void) const
|
double String::toDouble(void) const {
|
||||||
{
|
|
||||||
if (buffer())
|
if (buffer())
|
||||||
return atof(buffer());
|
return atof(buffer());
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
@ -53,52 +53,60 @@ class String {
|
|||||||
// if the initial value is null or invalid, or if memory allocation
|
// if the initial value is null or invalid, or if memory allocation
|
||||||
// fails, the string will be marked as invalid (i.e. "if (s)" will
|
// fails, the string will be marked as invalid (i.e. "if (s)" will
|
||||||
// be false).
|
// be false).
|
||||||
String(const char *cstr = nullptr);
|
String() __attribute__((always_inline)) { // See init()
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
String(const char *cstr);
|
||||||
String(const String &str);
|
String(const String &str);
|
||||||
String(const __FlashStringHelper *str);
|
String(const __FlashStringHelper *str);
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
String(String &&rval) noexcept;
|
||||||
String(String &&rval);
|
String(StringSumHelper &&rval) noexcept;
|
||||||
String(StringSumHelper &&rval);
|
explicit String(char c) {
|
||||||
#endif
|
sso.buff[0] = c;
|
||||||
explicit String(char c);
|
sso.buff[1] = 0;
|
||||||
|
sso.len = 1;
|
||||||
|
sso.isHeap = 0;
|
||||||
|
}
|
||||||
explicit String(unsigned char, unsigned char base = 10);
|
explicit String(unsigned char, unsigned char base = 10);
|
||||||
explicit String(int, unsigned char base = 10);
|
explicit String(int, unsigned char base = 10);
|
||||||
explicit String(unsigned int, unsigned char base = 10);
|
explicit String(unsigned int, unsigned char base = 10);
|
||||||
explicit String(long, unsigned char base = 10);
|
explicit String(long, unsigned char base = 10);
|
||||||
explicit String(unsigned long, unsigned char base = 10);
|
explicit String(unsigned long, unsigned char base = 10);
|
||||||
|
explicit String(long long /* base 10 */);
|
||||||
|
explicit String(long long, unsigned char base);
|
||||||
|
explicit String(unsigned long long /* base 10 */);
|
||||||
|
explicit String(unsigned long long, unsigned char base);
|
||||||
explicit String(float, unsigned char decimalPlaces = 2);
|
explicit String(float, unsigned char decimalPlaces = 2);
|
||||||
explicit String(double, unsigned char decimalPlaces = 2);
|
explicit String(double, unsigned char decimalPlaces = 2);
|
||||||
~String(void);
|
~String() {
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
// memory management
|
// memory management
|
||||||
// return true on success, false on failure (in which case, the string
|
// return true on success, false on failure (in which case, the string
|
||||||
// is left unchanged). reserve(0), if successful, will validate an
|
// is left unchanged). reserve(0), if successful, will validate an
|
||||||
// invalid string (i.e., "if (s)" will be true afterwards)
|
// invalid string (i.e., "if (s)" will be true afterwards)
|
||||||
unsigned char reserve(unsigned int size);
|
unsigned char reserve(unsigned int size);
|
||||||
inline unsigned int length(void) const {
|
unsigned int length(void) const {
|
||||||
if(buffer()) {
|
return buffer() ? len() : 0;
|
||||||
return len();
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
inline void clear(void) {
|
void clear(void) {
|
||||||
setLen(0);
|
setLen(0);
|
||||||
}
|
}
|
||||||
inline bool isEmpty(void) const {
|
bool isEmpty(void) const {
|
||||||
return length() == 0;
|
return length() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates a copy of the assigned value. if the value is null or
|
// creates a copy of the assigned value. if the value is null or
|
||||||
// invalid, or if the memory allocation fails, the string will be
|
// invalid, or if the memory allocation fails, the string will be
|
||||||
// marked as invalid ("if (s)" will be false).
|
// marked as invalid ("if (s)" will be false).
|
||||||
String & operator =(const String &rhs);
|
String &operator =(const String &rhs);
|
||||||
String & operator =(const char *cstr);
|
String &operator =(const char *cstr);
|
||||||
String & operator = (const __FlashStringHelper *str);
|
String &operator =(const __FlashStringHelper *str);
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
String &operator =(String &&rval) noexcept;
|
||||||
String & operator =(String &&rval);
|
String &operator =(StringSumHelper &&rval) noexcept {
|
||||||
String & operator =(StringSumHelper &&rval);
|
return operator =((String &&)rval);
|
||||||
#endif
|
}
|
||||||
|
|
||||||
// concatenate (works w/ built-in types)
|
// concatenate (works w/ built-in types)
|
||||||
|
|
||||||
@ -113,69 +121,81 @@ class String {
|
|||||||
unsigned char concat(unsigned int num);
|
unsigned char concat(unsigned int num);
|
||||||
unsigned char concat(long num);
|
unsigned char concat(long num);
|
||||||
unsigned char concat(unsigned long num);
|
unsigned char concat(unsigned long num);
|
||||||
|
unsigned char concat(long long num);
|
||||||
|
unsigned char concat(unsigned long long num);
|
||||||
unsigned char concat(float num);
|
unsigned char concat(float num);
|
||||||
unsigned char concat(double num);
|
unsigned char concat(double num);
|
||||||
unsigned char concat(const __FlashStringHelper * str);
|
unsigned char concat(const __FlashStringHelper *str);
|
||||||
unsigned char concat(const char *cstr, unsigned int length);
|
unsigned char concat(const char *cstr, unsigned int length);
|
||||||
|
|
||||||
// if there's not enough memory for the concatenated value, the string
|
// if there's not enough memory for the concatenated value, the string
|
||||||
// will be left unchanged (but this isn't signalled in any way)
|
// will be left unchanged (but this isn't signalled in any way)
|
||||||
String & operator +=(const String &rhs) {
|
String &operator +=(const String &rhs) {
|
||||||
concat(rhs);
|
concat(rhs);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
String & operator +=(const char *cstr) {
|
String &operator +=(const char *cstr) {
|
||||||
concat(cstr);
|
concat(cstr);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
String & operator +=(char c) {
|
String &operator +=(char c) {
|
||||||
concat(c);
|
concat(c);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
String & operator +=(unsigned char num) {
|
String &operator +=(unsigned char num) {
|
||||||
concat(num);
|
concat(num);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
String & operator +=(int num) {
|
String &operator +=(int num) {
|
||||||
concat(num);
|
concat(num);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
String & operator +=(unsigned int num) {
|
String &operator +=(unsigned int num) {
|
||||||
concat(num);
|
concat(num);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
String & operator +=(long num) {
|
String &operator +=(long num) {
|
||||||
concat(num);
|
concat(num);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
String & operator +=(unsigned long num) {
|
String &operator +=(unsigned long num) {
|
||||||
concat(num);
|
concat(num);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
String & operator +=(float num) {
|
String &operator +=(long long num) {
|
||||||
concat(num);
|
concat(num);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
String & operator +=(double num) {
|
String &operator +=(unsigned long long num) {
|
||||||
concat(num);
|
concat(num);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
String & operator += (const __FlashStringHelper *str){
|
String &operator +=(float num) {
|
||||||
|
concat(num);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
String &operator +=(double num) {
|
||||||
|
concat(num);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
String &operator +=(const __FlashStringHelper *str) {
|
||||||
concat(str);
|
concat(str);
|
||||||
return (*this);
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs);
|
friend StringSumHelper &operator +(const StringSumHelper &lhs, const String &rhs);
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr);
|
friend StringSumHelper &operator +(const StringSumHelper &lhs, const char *cstr);
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, char c);
|
friend StringSumHelper &operator +(const StringSumHelper &lhs, char c);
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num);
|
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned char num);
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, int num);
|
friend StringSumHelper &operator +(const StringSumHelper &lhs, int num);
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned 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, long num);
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned 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, long long num);
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, double num);
|
friend StringSumHelper &operator +(const StringSumHelper &lhs, unsigned long long num);
|
||||||
friend StringSumHelper & operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs);
|
friend StringSumHelper &operator +(const StringSumHelper &lhs, float num);
|
||||||
|
friend StringSumHelper &operator +(const StringSumHelper &lhs, double num);
|
||||||
|
friend StringSumHelper &operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs);
|
||||||
|
|
||||||
// comparison (only works w/ Strings and "strings")
|
// comparison (only works w/ Strings and "strings")
|
||||||
operator StringIfHelperType() const {
|
operator StringIfHelperType() const {
|
||||||
@ -203,41 +223,45 @@ class String {
|
|||||||
unsigned char equalsIgnoreCase(const String &s) const;
|
unsigned char equalsIgnoreCase(const String &s) const;
|
||||||
unsigned char equalsConstantTime(const String &s) const;
|
unsigned char equalsConstantTime(const String &s) const;
|
||||||
unsigned char startsWith(const String &prefix) const;
|
unsigned char startsWith(const String &prefix) const;
|
||||||
unsigned char startsWith(const char * prefix) const {
|
unsigned char startsWith(const char *prefix) const {
|
||||||
return this->startsWith(String(prefix));
|
return this->startsWith(String(prefix));
|
||||||
}
|
}
|
||||||
unsigned char startsWith(const __FlashStringHelper * prefix) const {
|
unsigned char startsWith(const __FlashStringHelper *prefix) const {
|
||||||
return this->startsWith(String(prefix));
|
return this->startsWith(String(prefix));
|
||||||
}
|
}
|
||||||
unsigned char startsWith(const String &prefix, unsigned int offset) const;
|
unsigned char startsWith(const String &prefix, unsigned int offset) const;
|
||||||
unsigned char endsWith(const String &suffix) const;
|
unsigned char endsWith(const String &suffix) const;
|
||||||
unsigned char endsWith(const char * suffix) const {
|
unsigned char endsWith(const char *suffix) const {
|
||||||
return this->endsWith(String(suffix));
|
return this->endsWith(String(suffix));
|
||||||
}
|
}
|
||||||
unsigned char endsWith(const __FlashStringHelper * suffix) const {
|
unsigned char endsWith(const __FlashStringHelper *suffix) const {
|
||||||
return this->endsWith(String(suffix));
|
return this->endsWith(String(suffix));
|
||||||
}
|
}
|
||||||
|
|
||||||
// character access
|
// character access
|
||||||
char charAt(unsigned int index) const;
|
char charAt(unsigned int index) const {
|
||||||
|
return operator [](index);
|
||||||
|
}
|
||||||
void setCharAt(unsigned int index, char c);
|
void setCharAt(unsigned int index, char c);
|
||||||
char operator [](unsigned int index) const;
|
char operator [](unsigned int index) const;
|
||||||
char& operator [](unsigned int index);
|
char &operator [](unsigned int index);
|
||||||
void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index = 0) const;
|
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 {
|
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const {
|
||||||
getBytes((unsigned char *) buf, bufsize, index);
|
getBytes((unsigned char *) buf, bufsize, index);
|
||||||
}
|
}
|
||||||
const char* c_str() const { return buffer(); }
|
const char *c_str() const { return buffer(); }
|
||||||
char* begin() { return wbuffer(); }
|
char *begin() { return wbuffer(); }
|
||||||
char* end() { return wbuffer() + length(); }
|
char *end() { return wbuffer() + length(); }
|
||||||
const char* begin() const { return c_str(); }
|
const char *begin() const { return c_str(); }
|
||||||
const char* end() const { return c_str() + length(); }
|
const char *end() const { return c_str() + length(); }
|
||||||
|
|
||||||
// search
|
// search
|
||||||
int indexOf(char ch) const;
|
int indexOf(char ch, unsigned int fromIndex = 0) const;
|
||||||
int indexOf(char ch, unsigned int fromIndex) const;
|
int indexOf(const char *str, unsigned int fromIndex = 0) const;
|
||||||
int indexOf(const String &str) const;
|
int indexOf(const __FlashStringHelper *str, unsigned int fromIndex = 0) const {
|
||||||
int indexOf(const String &str, unsigned int fromIndex) const;
|
return indexOf((const char*)str, fromIndex);
|
||||||
|
}
|
||||||
|
int indexOf(const String &str, unsigned int fromIndex = 0) const;
|
||||||
int lastIndexOf(char ch) const;
|
int lastIndexOf(char ch) const;
|
||||||
int lastIndexOf(char ch, unsigned int fromIndex) const;
|
int lastIndexOf(char ch, unsigned int fromIndex) const;
|
||||||
int lastIndexOf(const String &str) const;
|
int lastIndexOf(const String &str) const;
|
||||||
@ -245,29 +269,29 @@ class String {
|
|||||||
String substring(unsigned int beginIndex) const {
|
String substring(unsigned int beginIndex) const {
|
||||||
return substring(beginIndex, len());
|
return substring(beginIndex, len());
|
||||||
}
|
}
|
||||||
;
|
|
||||||
String substring(unsigned int beginIndex, unsigned int endIndex) const;
|
String substring(unsigned int beginIndex, unsigned int endIndex) const;
|
||||||
|
|
||||||
// modification
|
// modification
|
||||||
void replace(char find, char replace);
|
void replace(char find, char replace);
|
||||||
void replace(const String& find, const String& replace);
|
void replace(const String &find, const String &replace);
|
||||||
void replace(const char * find, const String& replace) {
|
void replace(const char *find, const String &replace) {
|
||||||
this->replace(String(find), replace);
|
this->replace(String(find), replace);
|
||||||
}
|
}
|
||||||
void replace(const __FlashStringHelper * find, const String& replace) {
|
void replace(const __FlashStringHelper *find, const String &replace) {
|
||||||
this->replace(String(find), replace);
|
this->replace(String(find), replace);
|
||||||
}
|
}
|
||||||
void replace(const char * find, const char * replace) {
|
void replace(const char *find, const char *replace) {
|
||||||
this->replace(String(find), String(replace));
|
this->replace(String(find), String(replace));
|
||||||
}
|
}
|
||||||
void replace(const __FlashStringHelper * find, const char * replace) {
|
void replace(const __FlashStringHelper *find, const char *replace) {
|
||||||
this->replace(String(find), String(replace));
|
this->replace(String(find), String(replace));
|
||||||
}
|
}
|
||||||
void replace(const __FlashStringHelper * find, const __FlashStringHelper * replace) {
|
void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) {
|
||||||
this->replace(String(find), String(replace));
|
this->replace(String(find), String(replace));
|
||||||
}
|
}
|
||||||
void remove(unsigned int index);
|
// Pass the biggest integer if the count is not specified.
|
||||||
void remove(unsigned int index, unsigned int count);
|
// The remove method below will take care of truncating it at the end of the string.
|
||||||
|
void remove(unsigned int index, unsigned int count = (unsigned int)-1);
|
||||||
void toLowerCase(void);
|
void toLowerCase(void);
|
||||||
void toUpperCase(void);
|
void toUpperCase(void);
|
||||||
void trim(void);
|
void trim(void);
|
||||||
@ -275,11 +299,11 @@ class String {
|
|||||||
// parsing/conversion
|
// parsing/conversion
|
||||||
long toInt(void) const;
|
long toInt(void) const;
|
||||||
float toFloat(void) const;
|
float toFloat(void) const;
|
||||||
double toDouble(void) const;
|
double toDouble(void) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// Contains the string info when we're not in SSO mode
|
// Contains the string info when we're not in SSO mode
|
||||||
struct _ptr {
|
struct _ptr {
|
||||||
char * buff;
|
char * buff;
|
||||||
uint16_t cap;
|
uint16_t cap;
|
||||||
uint16_t len;
|
uint16_t len;
|
||||||
@ -288,8 +312,8 @@ class String {
|
|||||||
enum { SSOSIZE = sizeof(struct _ptr) + 4 - 1 }; // Characters to allocate space for SSO, must be 12 or more
|
enum { SSOSIZE = sizeof(struct _ptr) + 4 - 1 }; // Characters to allocate space for SSO, must be 12 or more
|
||||||
struct _sso {
|
struct _sso {
|
||||||
char buff[SSOSIZE];
|
char buff[SSOSIZE];
|
||||||
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
|
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
|
||||||
unsigned char isSSO : 1;
|
unsigned char isHeap : 1;
|
||||||
} __attribute__((packed)); // Ensure that GCC doesn't expand the flag byte to a 32-bit word for alignment issues
|
} __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
|
enum { CAPACITY_MAX = 65535 }; // If typeof(cap) changed from uint16_t, be sure to update this enum to the max value storable in the type
|
||||||
union {
|
union {
|
||||||
@ -297,28 +321,48 @@ class String {
|
|||||||
struct _sso sso;
|
struct _sso sso;
|
||||||
};
|
};
|
||||||
// Accessor functions
|
// Accessor functions
|
||||||
inline bool isSSO() const { return sso.isSSO; }
|
bool isSSO() const { return !sso.isHeap; }
|
||||||
inline unsigned int len() const { return isSSO() ? sso.len : ptr.len; }
|
unsigned int len() const { return isSSO() ? sso.len : ptr.len; }
|
||||||
inline unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL
|
unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL
|
||||||
inline void setSSO(bool set) { sso.isSSO = set; }
|
void setSSO(bool set) { sso.isHeap = !set; }
|
||||||
inline void setLen(int len) { if (isSSO()) sso.len = len; else ptr.len = len; }
|
void setLen(int len) {
|
||||||
inline void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; }
|
if (isSSO()) {
|
||||||
inline void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; }
|
setSSO(true); // Avoid emitting of bitwise EXTRACT-AND-OR ops (store-merging optimization)
|
||||||
|
sso.len = len;
|
||||||
|
} else
|
||||||
|
ptr.len = len;
|
||||||
|
}
|
||||||
|
void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; }
|
||||||
|
void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; }
|
||||||
// Buffer accessor functions
|
// Buffer accessor functions
|
||||||
inline const char *buffer() const { return (const char *)(isSSO() ? sso.buff : ptr.buff); }
|
const char *buffer() const { return wbuffer(); }
|
||||||
inline char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
|
char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void init(void);
|
void init(void) __attribute__((always_inline)) {
|
||||||
|
sso.buff[0] = 0;
|
||||||
|
sso.len = 0;
|
||||||
|
sso.isHeap = 0;
|
||||||
|
// Without the 6 statements shown below, GCC simply emits such as: "MOVI.N aX,0", "S8I aX,a2,0" and "S8I aX,a2,11" (8 bytes in total)
|
||||||
|
sso.buff[1] = 0;
|
||||||
|
sso.buff[2] = 0;
|
||||||
|
sso.buff[3] = 0;
|
||||||
|
sso.buff[8] = 0;
|
||||||
|
sso.buff[9] = 0;
|
||||||
|
sso.buff[10] = 0;
|
||||||
|
// With the above, thanks to store-merging, GCC can use the narrow form of 32-bit store insn ("S32I.N") and emits:
|
||||||
|
// "MOVI.N aX,0", "S32I.N aX,a2,0" and "S32I.N aX,a2,8" (6 bytes in total)
|
||||||
|
// (Literature: Xtensa(R) Instruction Set Reference Manual, "S8I - Store 8-bit" [p.504] and "S32I.N - Narrow Store 32-bit" [p.512])
|
||||||
|
// Unfortunately, GCC seems not to re-evaluate the cost of inlining after the store-merging optimizer stage,
|
||||||
|
// `always_inline` attribute is necessary in order to keep inlining.
|
||||||
|
}
|
||||||
void invalidate(void);
|
void invalidate(void);
|
||||||
unsigned char changeBuffer(unsigned int maxStrLen);
|
unsigned char changeBuffer(unsigned int maxStrLen);
|
||||||
|
|
||||||
// copy and move
|
// copy and move
|
||||||
String & copy(const char *cstr, unsigned int length);
|
String ©(const char *cstr, unsigned int length);
|
||||||
String & copy(const __FlashStringHelper *pstr, unsigned int length);
|
String ©(const __FlashStringHelper *pstr, unsigned int length);
|
||||||
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
void move(String &rhs) noexcept;
|
||||||
void move(String &rhs);
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class StringSumHelper: public String {
|
class StringSumHelper: public String {
|
||||||
@ -347,12 +391,21 @@ class StringSumHelper: public String {
|
|||||||
StringSumHelper(unsigned long num) :
|
StringSumHelper(unsigned long num) :
|
||||||
String(num) {
|
String(num) {
|
||||||
}
|
}
|
||||||
|
StringSumHelper(long long num) :
|
||||||
|
String(num) {
|
||||||
|
}
|
||||||
|
StringSumHelper(unsigned long long num) :
|
||||||
|
String(num) {
|
||||||
|
}
|
||||||
StringSumHelper(float num) :
|
StringSumHelper(float num) :
|
||||||
String(num) {
|
String(num) {
|
||||||
}
|
}
|
||||||
StringSumHelper(double num) :
|
StringSumHelper(double num) :
|
||||||
String(num) {
|
String(num) {
|
||||||
}
|
}
|
||||||
|
StringSumHelper(const __FlashStringHelper *s) :
|
||||||
|
String(s) {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const String emptyString;
|
extern const String emptyString;
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <debug.h>
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <cxxabi.h>
|
#include <cxxabi.h>
|
||||||
|
|
||||||
@ -32,18 +31,33 @@ extern "C" void __cxa_pure_virtual(void) __attribute__ ((__noreturn__));
|
|||||||
extern "C" void __cxa_deleted_virtual(void) __attribute__ ((__noreturn__));
|
extern "C" void __cxa_deleted_virtual(void) __attribute__ ((__noreturn__));
|
||||||
|
|
||||||
|
|
||||||
#if !defined(__cpp_exceptions) && !defined(NEW_OOM_ABORT)
|
#if !defined(__cpp_exceptions)
|
||||||
void *operator new(size_t size)
|
|
||||||
|
// overwrite weak operators new/new[] definitions
|
||||||
|
|
||||||
|
void* operator new(size_t size)
|
||||||
{
|
{
|
||||||
void *ret = malloc(size);
|
void *ret = malloc(size);
|
||||||
if (0 != size && 0 == ret) {
|
if (0 != size && 0 == ret) {
|
||||||
umm_last_fail_alloc_addr = __builtin_return_address(0);
|
umm_last_fail_alloc_addr = __builtin_return_address(0);
|
||||||
umm_last_fail_alloc_size = size;
|
umm_last_fail_alloc_size = size;
|
||||||
|
__unhandled_exception(PSTR("OOM"));
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *operator new[](size_t size)
|
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);
|
void *ret = malloc(size);
|
||||||
if (0 != size && 0 == ret) {
|
if (0 != size && 0 == ret) {
|
||||||
@ -52,7 +66,18 @@ void *operator new[](size_t size)
|
|||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
#endif // arduino's std::new legacy
|
|
||||||
|
void* operator new[] (size_t size, const std::nothrow_t&)
|
||||||
|
{
|
||||||
|
void *ret = malloc(size);
|
||||||
|
if (0 != size && 0 == ret) {
|
||||||
|
umm_last_fail_alloc_addr = __builtin_return_address(0);
|
||||||
|
umm_last_fail_alloc_size = size;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // !defined(__cpp_exceptions)
|
||||||
|
|
||||||
void __cxa_pure_virtual(void)
|
void __cxa_pure_virtual(void)
|
||||||
{
|
{
|
||||||
|
163
cores/esp8266/aes_unwrap.cpp
Normal file
163
cores/esp8266/aes_unwrap.cpp
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* Replacement for the ROM aes_unwrap() function. It uses the heap instead of
|
||||||
|
* the static DRAM address at 0x3FFFEA80, which may step on the SYS stack in
|
||||||
|
* special circumstances such as HWDT Stack Dump.
|
||||||
|
*
|
||||||
|
* When not using WPS, the address space 0x3FFFE000 up to 0x40000000 is mostly
|
||||||
|
* available for the stacks. The one known exception is the ROM AES APIs. When
|
||||||
|
* `aes_decrypt_init` is called, it uses memory at 0x3FFFEA80 up to 0x3FFFEB30
|
||||||
|
* for a buffer. At the finish, `aes_decrypt_deinit` zeros out the buffer.
|
||||||
|
*
|
||||||
|
* The NONOS SDK appears to have replacements for most of the ROM's AES APIs.
|
||||||
|
* However, the SDK still calls on the ROM's aes_unwrap function, which uses
|
||||||
|
* the ROM's AES APIs to operate. These calls can overwrite some of the stack
|
||||||
|
* space. To resolve the problem, this module replaces `aes_unwrap`.
|
||||||
|
*
|
||||||
|
* Final note, so far, I have not seen a problem when using the extra 4K heap
|
||||||
|
* option without the "debug HWDT". It is when combined with the HWDT Stack
|
||||||
|
* Dump that a problem shows. This combination adds a Boot ROM stack, which
|
||||||
|
* pushes up the SYS and CONT stacks into the AES Buffer space. Then the
|
||||||
|
* problem shows.
|
||||||
|
*
|
||||||
|
* While debugging with painted stack space, during WiFi Connect, Reconnect,
|
||||||
|
* and about every hour, a block of memory 0x3FFFEA80 - 0x3FFFEB30 (176 bytes)
|
||||||
|
* was zeroed by the Boot ROM function aes_decrypt_init. All other painted
|
||||||
|
* memory in the area was untouched after starting WiFi.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(KEEP_ROM_AES_UNWRAP)
|
||||||
|
// Using the ROM version of aes_unwrap should be fine for the no extra 4K case
|
||||||
|
// which is usually used in conjunction with WPS.
|
||||||
|
|
||||||
|
#else
|
||||||
|
// This is required for DEBUG_ESP_HWDT.
|
||||||
|
// The need is unconfirmed for the extra 4K heap case.
|
||||||
|
#include "umm_malloc/umm_malloc.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
// Uses this function from the Boot ROM
|
||||||
|
void rijndaelKeySetupDec(u32 rk[], const u8 cipherKey[]);
|
||||||
|
|
||||||
|
// This replaces the Boot ROM version just for this module
|
||||||
|
// Uses a malloc-ed buffer instead of the static buffer in stack address space.
|
||||||
|
static void *aes_decrypt_init(const u8 *key, size_t len) {
|
||||||
|
if (16u != len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
u32 *rk = (u32 *)malloc(16*11);
|
||||||
|
// u32 *rk = (u32 *)0x3FFFEA80u; // This is what the ROM would have used.
|
||||||
|
if (rk) {
|
||||||
|
rijndaelKeySetupDec(rk, key);
|
||||||
|
}
|
||||||
|
return (void *)rk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This replaces the Boot ROM version just for this module
|
||||||
|
static void aes_decrypt_deinit(void *ctx) {
|
||||||
|
if (ctx) {
|
||||||
|
ets_memset(ctx, 0, 16*11);
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The NONOS SDK has an override on this function. To replace the aes_unwrap
|
||||||
|
* without changing its behavior too much. We need access to the ROM version of
|
||||||
|
* the AES APIs to make our aes_unwrap functionally equal to the current
|
||||||
|
* environment except for the AES Buffer.
|
||||||
|
*/
|
||||||
|
#ifndef ROM_aes_decrypt
|
||||||
|
#define ROM_aes_decrypt 0x400092d4
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void (*fp_aes_decrypt_t)(void *ctx, const u8 *crypt, u8 *plain);
|
||||||
|
#define AES_DECRYPT (reinterpret_cast<fp_aes_decrypt_t>(ROM_aes_decrypt))
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
/*
|
||||||
|
* This aes_unwrap() function overrides/replaces the Boot ROM version.
|
||||||
|
*
|
||||||
|
* It was adapted from aes_unwrap() found in the ESP8266 RTOS SDK
|
||||||
|
* .../components/wap_supplicant/src/crypto/aes-unwrap.c
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
/*
|
||||||
|
* AES key unwrap (128-bit KEK, RFC3394)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2003-2007, Jouni Malinen <j@w1.fi>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* Alternatively, this software may be distributed under the terms of BSD
|
||||||
|
* license.
|
||||||
|
*
|
||||||
|
* See README and COPYING for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** based on RTOS SDK
|
||||||
|
* aes_unwrap - Unwrap key with AES Key Wrap Algorithm (128-bit KEK) (RFC3394)
|
||||||
|
* @kek: Key encryption key (KEK)
|
||||||
|
* @n: Length of the plaintext key in 64-bit units; e.g., 2 = 128-bit = 16
|
||||||
|
* bytes
|
||||||
|
* @cipher: Wrapped key to be unwrapped, (n + 1) * 64 bits
|
||||||
|
* @plain: Plaintext key, n * 64 bits
|
||||||
|
* Returns: 0 on success, -1 on failure (e.g., integrity verification failed)
|
||||||
|
*/
|
||||||
|
int aes_unwrap(const u8 *kek, int n, const u8 *cipher, u8 *plain)
|
||||||
|
{
|
||||||
|
u8 a[8], *r, b[16];
|
||||||
|
int i, j;
|
||||||
|
void *ctx;
|
||||||
|
|
||||||
|
/* 1) Initialize variables. */
|
||||||
|
ets_memcpy(a, cipher, 8);
|
||||||
|
r = plain;
|
||||||
|
ets_memcpy(r, cipher + 8, 8 * n);
|
||||||
|
|
||||||
|
ctx = aes_decrypt_init(kek, 16);
|
||||||
|
if (ctx == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
/* 2) Compute intermediate values.
|
||||||
|
* For j = 5 to 0
|
||||||
|
* For i = n to 1
|
||||||
|
* B = AES-1(K, (A ^ t) | R[i]) where t = n*j+i
|
||||||
|
* A = MSB(64, B)
|
||||||
|
* R[i] = LSB(64, B)
|
||||||
|
*/
|
||||||
|
for (j = 5; j >= 0; j--) {
|
||||||
|
r = plain + (n - 1) * 8;
|
||||||
|
for (i = n; i >= 1; i--) {
|
||||||
|
ets_memcpy(b, a, 8);
|
||||||
|
b[7] ^= n * j + i;
|
||||||
|
|
||||||
|
ets_memcpy(b + 8, r, 8);
|
||||||
|
AES_DECRYPT(ctx, b, b);
|
||||||
|
ets_memcpy(a, b, 8);
|
||||||
|
ets_memcpy(r, b + 8, 8);
|
||||||
|
r -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
aes_decrypt_deinit(ctx);
|
||||||
|
|
||||||
|
/* 3) Output results.
|
||||||
|
*
|
||||||
|
* These are already in @plain due to the location of temporary
|
||||||
|
* variables. Just verify that the IV matches with the expected value.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
if (a[i] != 0xa6)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
@ -25,6 +25,8 @@
|
|||||||
#ifndef CORE_BASE64_H_
|
#ifndef CORE_BASE64_H_
|
||||||
#define CORE_BASE64_H_
|
#define CORE_BASE64_H_
|
||||||
|
|
||||||
|
#include <WString.h>
|
||||||
|
|
||||||
class base64
|
class base64
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <new> // std::nothrow
|
||||||
#include "cbuf.h"
|
#include "cbuf.h"
|
||||||
#include "c_types.h"
|
#include "c_types.h"
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ size_t cbuf::resize(size_t newSize) {
|
|||||||
return _size;
|
return _size;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *newbuf = new char[newSize];
|
char *newbuf = new (std::nothrow) char[newSize];
|
||||||
char *oldbuf = _buf;
|
char *oldbuf = _buf;
|
||||||
|
|
||||||
if(!newbuf) {
|
if(!newbuf) {
|
||||||
|
@ -37,8 +37,9 @@ void precache(void *f, uint32_t bytes) {
|
|||||||
// page (ie 1 word in 8) for this to work.
|
// page (ie 1 word in 8) for this to work.
|
||||||
#define CACHE_PAGE_SIZE 32
|
#define CACHE_PAGE_SIZE 32
|
||||||
|
|
||||||
register uint32_t a0 asm("a0");
|
uint32_t a0;
|
||||||
register uint32_t lines = (bytes/CACHE_PAGE_SIZE)+2;
|
__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);
|
volatile uint32_t *p = (uint32_t*)((f ? (uint32_t)f : a0) & ~0x03);
|
||||||
uint32_t x;
|
uint32_t x;
|
||||||
for (uint32_t i=0; i<lines; i++, p+=CACHE_PAGE_SIZE/sizeof(uint32_t)) x=*p;
|
for (uint32_t i=0; i<lines; i++, p+=CACHE_PAGE_SIZE/sizeof(uint32_t)) x=*p;
|
||||||
|
@ -36,35 +36,6 @@
|
|||||||
#include <stddef.h> // size_t
|
#include <stddef.h> // size_t
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
|
|
||||||
namespace arduino
|
|
||||||
{
|
|
||||||
extern "C++"
|
|
||||||
template <typename T, typename ...TConstructorArgs>
|
|
||||||
T* new0 (size_t n, TConstructorArgs... TconstructorArgs)
|
|
||||||
{
|
|
||||||
// n==0: single allocation, otherwise it is an array
|
|
||||||
size_t offset = n? sizeof(size_t): 0;
|
|
||||||
size_t arraysize = n? n: 1;
|
|
||||||
T* ptr = (T*)malloc(offset + (arraysize * sizeof(T)));
|
|
||||||
if (ptr)
|
|
||||||
{
|
|
||||||
if (n)
|
|
||||||
*(size_t*)(ptr) = n;
|
|
||||||
for (size_t i = 0; i < arraysize; i++)
|
|
||||||
new (ptr + offset + i * sizeof(T)) T(TconstructorArgs...);
|
|
||||||
return ptr + offset;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#define arduino_new(Type, ...) arduino::new0<Type>(0, ##__VA_ARGS__)
|
|
||||||
#define arduino_newarray(Type, n, ...) arduino::new0<Type>(n, ##__VA_ARGS__)
|
|
||||||
|
|
||||||
#endif // __cplusplus
|
|
||||||
|
|
||||||
#ifndef __STRINGIFY
|
#ifndef __STRINGIFY
|
||||||
#define __STRINGIFY(a) #a
|
#define __STRINGIFY(a) #a
|
||||||
#endif
|
#endif
|
||||||
@ -83,6 +54,7 @@ namespace arduino
|
|||||||
// level 0 will enable ALL interrupts,
|
// level 0 will enable ALL interrupts,
|
||||||
//
|
//
|
||||||
#ifndef CORE_MOCK
|
#ifndef CORE_MOCK
|
||||||
|
|
||||||
#define xt_rsil(level) (__extension__({uint32_t state; __asm__ __volatile__("rsil %0," __STRINGIFY(level) : "=a" (state) :: "memory"); state;}))
|
#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")
|
#define xt_wsr_ps(state) __asm__ __volatile__("wsr %0,ps; isync" :: "a" (state) : "memory")
|
||||||
|
|
||||||
@ -92,7 +64,22 @@ inline uint32_t esp_get_cycle_count() {
|
|||||||
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
|
__asm__ __volatile__("rsr %0,ccount":"=a"(ccount));
|
||||||
return ccount;
|
return ccount;
|
||||||
}
|
}
|
||||||
#endif // not CORE_MOCK
|
|
||||||
|
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
|
// Tools for preloading code into the flash cache
|
||||||
@ -111,6 +98,29 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void precache(void *f, uint32_t bytes);
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
#include "osapi.h"
|
#include "osapi.h"
|
||||||
#include "ets_sys.h"
|
#include "ets_sys.h"
|
||||||
#include "i2s_reg.h"
|
#include "i2s_reg.h"
|
||||||
#include "i2s.h"
|
#include "core_esp8266_i2s.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
@ -64,6 +64,7 @@ typedef struct i2s_state {
|
|||||||
// Callback function should be defined as 'void ICACHE_RAM_ATTR function_name()',
|
// Callback function should be defined as 'void ICACHE_RAM_ATTR function_name()',
|
||||||
// and be placed in IRAM for faster execution. Avoid long computational tasks in this
|
// and be placed in IRAM for faster execution. Avoid long computational tasks in this
|
||||||
// function, use it to set flags and process later.
|
// function, use it to set flags and process later.
|
||||||
|
bool driveClocks;
|
||||||
} i2s_state_t;
|
} i2s_state_t;
|
||||||
|
|
||||||
// RX = I2S receive (i.e. microphone), TX = I2S transmit (i.e. DAC)
|
// RX = I2S receive (i.e. microphone), TX = I2S transmit (i.e. DAC)
|
||||||
@ -72,6 +73,7 @@ static i2s_state_t *tx = NULL;
|
|||||||
|
|
||||||
// Last I2S sample rate requested
|
// Last I2S sample rate requested
|
||||||
static uint32_t _i2s_sample_rate;
|
static uint32_t _i2s_sample_rate;
|
||||||
|
static int _i2s_bits = 16;
|
||||||
|
|
||||||
// IOs used for I2S. Not defined in i2s.h, unfortunately.
|
// IOs used for I2S. Not defined in i2s.h, unfortunately.
|
||||||
// Note these are internal GPIO numbers and not pins on an
|
// Note these are internal GPIO numbers and not pins on an
|
||||||
@ -83,6 +85,14 @@ static uint32_t _i2s_sample_rate;
|
|||||||
#define I2SI_BCK 13
|
#define I2SI_BCK 13
|
||||||
#define I2SI_WS 14
|
#define I2SI_WS 14
|
||||||
|
|
||||||
|
bool i2s_set_bits(int bits) {
|
||||||
|
if (tx || rx || (bits != 16 && bits != 24)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_i2s_bits = bits;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool _i2s_is_full(const i2s_state_t *ch) {
|
static bool _i2s_is_full(const i2s_state_t *ch) {
|
||||||
if (!ch) {
|
if (!ch) {
|
||||||
return false;
|
return false;
|
||||||
@ -184,11 +194,11 @@ static void ICACHE_RAM_ATTR i2s_slc_isr(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void i2s_set_callback(void (*callback) (void)) {
|
void i2s_set_callback(void (*callback) (void)) {
|
||||||
tx->callback = callback;
|
if (tx) tx->callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void i2s_rx_set_callback(void (*callback) (void)) {
|
void i2s_rx_set_callback(void (*callback) (void)) {
|
||||||
rx->callback = callback;
|
if (rx) rx->callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _alloc_channel(i2s_state_t *ch) {
|
static bool _alloc_channel(i2s_state_t *ch) {
|
||||||
@ -333,7 +343,7 @@ bool i2s_write_lr(int16_t left, int16_t right){
|
|||||||
|
|
||||||
// writes a buffer of frames into the DMA memory, returns the amount of frames written
|
// writes a buffer of frames into the DMA memory, returns the amount of frames written
|
||||||
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
|
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
|
||||||
static uint16_t _i2s_write_buffer(int16_t *frames, uint16_t frame_count, bool mono, bool nb) {
|
static uint16_t _i2s_write_buffer(const int16_t *frames, uint16_t frame_count, bool mono, bool nb) {
|
||||||
uint16_t frames_written=0;
|
uint16_t frames_written=0;
|
||||||
|
|
||||||
while(frame_count>0) {
|
while(frame_count>0) {
|
||||||
@ -391,13 +401,13 @@ static uint16_t _i2s_write_buffer(int16_t *frames, uint16_t frame_count, bool mo
|
|||||||
return frames_written;
|
return frames_written;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t i2s_write_buffer_mono_nb(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, true); }
|
uint16_t i2s_write_buffer_mono_nb(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, true); }
|
||||||
|
|
||||||
uint16_t i2s_write_buffer_mono(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, false); }
|
uint16_t i2s_write_buffer_mono(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, true, false); }
|
||||||
|
|
||||||
uint16_t i2s_write_buffer_nb(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, true); }
|
uint16_t i2s_write_buffer_nb(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, true); }
|
||||||
|
|
||||||
uint16_t i2s_write_buffer(int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, false); }
|
uint16_t i2s_write_buffer(const int16_t *frames, uint16_t frame_count) { return _i2s_write_buffer(frames, frame_count, false, false); }
|
||||||
|
|
||||||
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) {
|
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking) {
|
||||||
if (!rx) {
|
if (!rx) {
|
||||||
@ -440,7 +450,7 @@ void i2s_set_rate(uint32_t rate) { //Rate in HZ
|
|||||||
}
|
}
|
||||||
_i2s_sample_rate = rate;
|
_i2s_sample_rate = rate;
|
||||||
|
|
||||||
uint32_t scaled_base_freq = I2SBASEFREQ/32;
|
uint32_t scaled_base_freq = I2SBASEFREQ / (_i2s_bits * 2);
|
||||||
float delta_best = scaled_base_freq;
|
float delta_best = scaled_base_freq;
|
||||||
|
|
||||||
uint8_t sbd_div_best=1;
|
uint8_t sbd_div_best=1;
|
||||||
@ -482,6 +492,9 @@ void i2s_set_dividers(uint8_t div1, uint8_t div2) {
|
|||||||
// div1, div2 = Set I2S WS clock frequency. BCLK seems to be generated from 32x this
|
// div1, div2 = Set I2S WS clock frequency. BCLK seems to be generated from 32x this
|
||||||
i2sc_temp |= 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 = i2sc_temp;
|
||||||
|
|
||||||
i2sc_temp &= ~(I2STXR); // Release reset
|
i2sc_temp &= ~(I2STXR); // Release reset
|
||||||
@ -489,10 +502,14 @@ void i2s_set_dividers(uint8_t div1, uint8_t div2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float i2s_get_real_rate(){
|
float i2s_get_real_rate(){
|
||||||
return (float)I2SBASEFREQ/32/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM);
|
return (float)I2SBASEFREQ/(_i2s_bits * 2)/((I2SC>>I2SBD) & I2SBDM)/((I2SC >> I2SCD) & I2SCDM);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
||||||
|
return i2s_rxtxdrive_begin(enableRx, enableTx, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool i2s_rxtxdrive_begin(bool enableRx, bool enableTx, bool driveRxClocks, bool driveTxClocks) {
|
||||||
if (tx || rx) {
|
if (tx || rx) {
|
||||||
i2s_end(); // Stop and free any ongoing stuff
|
i2s_end(); // Stop and free any ongoing stuff
|
||||||
}
|
}
|
||||||
@ -503,9 +520,12 @@ bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
|||||||
// Nothing to clean up yet
|
// Nothing to clean up yet
|
||||||
return false; // OOM Error!
|
return false; // OOM Error!
|
||||||
}
|
}
|
||||||
pinMode(I2SO_WS, FUNCTION_1);
|
tx->driveClocks = driveTxClocks;
|
||||||
pinMode(I2SO_DATA, FUNCTION_1);
|
pinMode(I2SO_DATA, FUNCTION_1);
|
||||||
pinMode(I2SO_BCK, FUNCTION_1);
|
if (driveTxClocks) {
|
||||||
|
pinMode(I2SO_WS, FUNCTION_1);
|
||||||
|
pinMode(I2SO_BCK, FUNCTION_1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (enableRx) {
|
if (enableRx) {
|
||||||
rx = (i2s_state_t*)calloc(1, sizeof(*rx));
|
rx = (i2s_state_t*)calloc(1, sizeof(*rx));
|
||||||
@ -513,12 +533,15 @@ bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
|||||||
i2s_end(); // Clean up any TX or pin changes
|
i2s_end(); // Clean up any TX or pin changes
|
||||||
return false; // OOM error!
|
return false; // OOM error!
|
||||||
}
|
}
|
||||||
pinMode(I2SI_WS, OUTPUT);
|
rx->driveClocks = driveRxClocks;
|
||||||
pinMode(I2SI_BCK, OUTPUT);
|
|
||||||
pinMode(I2SI_DATA, INPUT);
|
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_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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!i2s_slc_begin()) {
|
if (!i2s_slc_begin()) {
|
||||||
@ -538,6 +561,9 @@ bool i2s_rxtx_begin(bool enableRx, bool enableTx) {
|
|||||||
|
|
||||||
// I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data shifted in/out
|
// I2STXFMM, I2SRXFMM=0 => 16-bit, dual channel data shifted in/out
|
||||||
I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only)
|
I2SFC &= ~(I2SDE | (I2STXFMM << I2STXFM) | (I2SRXFMM << I2SRXFM)); // Set RX/TX FIFO_MOD=0 and disable DMA (FIFO only)
|
||||||
|
if (_i2s_bits == 24) {
|
||||||
|
I2SFC |= (2 << I2STXFM) | (2 << I2SRXFM);
|
||||||
|
}
|
||||||
I2SFC |= I2SDE; // Enable DMA
|
I2SFC |= I2SDE; // Enable DMA
|
||||||
|
|
||||||
// I2STXCMM, I2SRXCMM=0 => Dual channel mode
|
// I2STXCMM, I2SRXCMM=0 => Dual channel mode
|
||||||
@ -579,15 +605,19 @@ void i2s_end() {
|
|||||||
|
|
||||||
if (tx) {
|
if (tx) {
|
||||||
pinMode(I2SO_DATA, INPUT);
|
pinMode(I2SO_DATA, INPUT);
|
||||||
pinMode(I2SO_BCK, INPUT);
|
if (tx->driveClocks) {
|
||||||
pinMode(I2SO_WS, INPUT);
|
pinMode(I2SO_BCK, INPUT);
|
||||||
|
pinMode(I2SO_WS, INPUT);
|
||||||
|
}
|
||||||
free(tx);
|
free(tx);
|
||||||
tx = NULL;
|
tx = NULL;
|
||||||
}
|
}
|
||||||
if (rx) {
|
if (rx) {
|
||||||
pinMode(I2SI_DATA, INPUT);
|
pinMode(I2SI_DATA, INPUT);
|
||||||
pinMode(I2SI_BCK, INPUT);
|
if (rx->driveClocks) {
|
||||||
pinMode(I2SI_WS, INPUT);
|
pinMode(I2SI_BCK, INPUT);
|
||||||
|
pinMode(I2SI_WS, INPUT);
|
||||||
|
}
|
||||||
free(rx);
|
free(rx);
|
||||||
rx = NULL;
|
rx = NULL;
|
||||||
}
|
}
|
||||||
|
81
cores/esp8266/core_esp8266_i2s.h
Normal file
81
cores/esp8266/core_esp8266_i2s.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
i2s.h - Software I2S library for esp8266
|
||||||
|
|
||||||
|
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
#ifndef I2S_h
|
||||||
|
#define I2S_h
|
||||||
|
|
||||||
|
#define I2S_HAS_BEGIN_RXTX_DRIVE_CLOCKS 1
|
||||||
|
|
||||||
|
/*
|
||||||
|
How does this work? Basically, to get sound, you need to:
|
||||||
|
- Connect an I2S codec to the I2S pins on the ESP.
|
||||||
|
- Start up a thread that's going to do the sound output
|
||||||
|
- Call i2s_set_bits() if you want to enable 24-bit mode
|
||||||
|
- Call i2s_begin()
|
||||||
|
- Call i2s_set_rate() with the sample rate you want.
|
||||||
|
- Generate sound and call i2s_write_sample() with 32-bit samples.
|
||||||
|
The 32bit samples basically are 2 16-bit signed values (the analog values for
|
||||||
|
the left and right channel) concatenated as (Rout<<16)+Lout
|
||||||
|
|
||||||
|
i2s_write_sample will block when you're sending data too quickly, so you can just
|
||||||
|
generate and push data as fast as you can and i2s_write_sample will regulate the
|
||||||
|
speed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool i2s_set_bits(int bits); // Set bits per sample, only 16 or 24 supported. Call before begin.
|
||||||
|
// Note that in 24 bit mode each sample must be left-aligned (i.e. 0x00000000 .. 0xffffff00) as the
|
||||||
|
// hardware shifts starting at bit 31, not bit 23.
|
||||||
|
|
||||||
|
void i2s_begin(); // Enable TX only, for compatibility
|
||||||
|
bool i2s_rxtx_begin(bool enableRx, bool enableTx); // Allow TX and/or RX, returns false on OOM error
|
||||||
|
bool i2s_rxtxdrive_begin(bool enableRx, bool enableTx, bool driveRxClocks, bool driveTxClocks);
|
||||||
|
void i2s_end();
|
||||||
|
void i2s_set_rate(uint32_t rate);//Sample Rate in Hz (ex 44100, 48000)
|
||||||
|
void i2s_set_dividers(uint8_t div1, uint8_t div2);//Direct control over output rate
|
||||||
|
float i2s_get_real_rate();//The actual Sample Rate on output
|
||||||
|
bool i2s_write_sample(uint32_t sample);//32bit sample with channels being upper and lower 16 bits (blocking when DMA is full)
|
||||||
|
bool i2s_write_sample_nb(uint32_t sample);//same as above but does not block when DMA is full and returns false instead
|
||||||
|
bool i2s_write_lr(int16_t left, int16_t right);//combines both channels and calls i2s_write_sample with the result
|
||||||
|
bool i2s_read_sample(int16_t *left, int16_t *right, bool blocking); // RX data returned in both 16-bit outputs.
|
||||||
|
bool i2s_is_full();//returns true if DMA is full and can not take more bytes (overflow)
|
||||||
|
bool i2s_is_empty();//returns true if DMA is empty (underflow)
|
||||||
|
bool i2s_rx_is_full();
|
||||||
|
bool i2s_rx_is_empty();
|
||||||
|
uint16_t i2s_available();// returns the number of samples than can be written before blocking
|
||||||
|
uint16_t i2s_rx_available();// returns the number of samples than can be written before blocking
|
||||||
|
void i2s_set_callback(void (*callback) (void));
|
||||||
|
void i2s_rx_set_callback(void (*callback) (void));
|
||||||
|
|
||||||
|
// writes a buffer of frames into the DMA memory, returns the amount of frames written
|
||||||
|
// A frame is just a int16_t for mono, for stereo a frame is two int16_t, one for each channel.
|
||||||
|
uint16_t i2s_write_buffer_mono(const int16_t *frames, uint16_t frame_count);
|
||||||
|
uint16_t i2s_write_buffer_mono_nb(const int16_t *frames, uint16_t frame_count);
|
||||||
|
uint16_t i2s_write_buffer(const int16_t *frames, uint16_t frame_count);
|
||||||
|
uint16_t i2s_write_buffer_nb(const int16_t *frames, uint16_t frame_count);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -35,6 +35,9 @@ extern "C" {
|
|||||||
#include <core_version.h>
|
#include <core_version.h>
|
||||||
#include "gdb_hooks.h"
|
#include "gdb_hooks.h"
|
||||||
#include "flash_quirks.h"
|
#include "flash_quirks.h"
|
||||||
|
#include <umm_malloc/umm_malloc.h>
|
||||||
|
#include <core_esp8266_non32xfer.h>
|
||||||
|
|
||||||
|
|
||||||
#define LOOP_TASK_PRIORITY 1
|
#define LOOP_TASK_PRIORITY 1
|
||||||
#define LOOP_QUEUE_SIZE 1
|
#define LOOP_QUEUE_SIZE 1
|
||||||
@ -172,7 +175,7 @@ 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) {
|
extern "C" bool IRAM_ATTR ets_post(uint8 prio, ETSSignal sig, ETSParam par) {
|
||||||
uint32_t saved;
|
uint32_t saved;
|
||||||
asm volatile ("rsr %0,ps":"=a" (saved));
|
__asm__ __volatile__ ("rsr %0,ps":"=a" (saved));
|
||||||
bool rc=ets_post_rom(prio, sig, par);
|
bool rc=ets_post_rom(prio, sig, par);
|
||||||
xt_wsr_ps(saved);
|
xt_wsr_ps(saved);
|
||||||
return rc;
|
return rc;
|
||||||
@ -195,13 +198,18 @@ static void loop_wrapper() {
|
|||||||
}
|
}
|
||||||
loop();
|
loop();
|
||||||
loop_end();
|
loop_end();
|
||||||
|
if (serialEventRun) {
|
||||||
|
serialEventRun();
|
||||||
|
}
|
||||||
esp_schedule();
|
esp_schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void loop_task(os_event_t *events) {
|
static void loop_task(os_event_t *events) {
|
||||||
(void) events;
|
(void) events;
|
||||||
s_cycles_at_yield_start = ESP.getCycleCount();
|
s_cycles_at_yield_start = ESP.getCycleCount();
|
||||||
|
ESP.resetHeap();
|
||||||
cont_run(g_pcont, &loop_wrapper);
|
cont_run(g_pcont, &loop_wrapper);
|
||||||
|
ESP.setDramHeap();
|
||||||
if (cont_check(g_pcont) != 0) {
|
if (cont_check(g_pcont) != 0) {
|
||||||
panic();
|
panic();
|
||||||
}
|
}
|
||||||
@ -253,6 +261,7 @@ void init_done() {
|
|||||||
std::set_terminate(__unhandled_exception_cpp);
|
std::set_terminate(__unhandled_exception_cpp);
|
||||||
do_global_ctors();
|
do_global_ctors();
|
||||||
esp_schedule();
|
esp_schedule();
|
||||||
|
ESP.setDramHeap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This is the entry point of the application.
|
/* This is the entry point of the application.
|
||||||
@ -311,11 +320,11 @@ extern "C" void app_entry_redefinable(void)
|
|||||||
/* Call the entry point of the SDK code. */
|
/* Call the entry point of the SDK code. */
|
||||||
call_user_start();
|
call_user_start();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void app_entry_custom (void) __attribute__((weakref("app_entry_redefinable")));
|
static void app_entry_custom (void) __attribute__((weakref("app_entry_redefinable")));
|
||||||
|
|
||||||
extern "C" void app_entry (void)
|
extern "C" void app_entry (void)
|
||||||
{
|
{
|
||||||
|
umm_init();
|
||||||
return app_entry_custom();
|
return app_entry_custom();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,6 +348,12 @@ extern "C" void user_init(void) {
|
|||||||
|
|
||||||
cont_init(g_pcont);
|
cont_init(g_pcont);
|
||||||
|
|
||||||
|
#if defined(NON32XFER_HANDLER) || defined(MMU_IRAM_HEAP)
|
||||||
|
install_non32xfer_exception_handler();
|
||||||
|
#endif
|
||||||
|
#if defined(MMU_IRAM_HEAP)
|
||||||
|
umm_init_iram();
|
||||||
|
#endif
|
||||||
preinit(); // Prior to C++ Dynamic Init (not related to above init() ). Meant to be user redefinable.
|
preinit(); // Prior to C++ Dynamic Init (not related to above init() ). Meant to be user redefinable.
|
||||||
|
|
||||||
ets_task(loop_task,
|
ets_task(loop_task,
|
||||||
|
219
cores/esp8266/core_esp8266_non32xfer.cpp
Normal file
219
cores/esp8266/core_esp8266_non32xfer.cpp
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/* 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 {
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
uint32_t insn, excvaddr;
|
||||||
|
#if 1
|
||||||
|
{
|
||||||
|
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) :);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
{
|
||||||
|
__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
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
14
cores/esp8266/core_esp8266_non32xfer.h
Normal file
14
cores/esp8266/core_esp8266_non32xfer.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef __CORE_ESP8266_NON32XFER_H
|
||||||
|
#define __CORE_ESP8266_NON32XFER_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern void install_non32xfer_exception_handler();
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
@ -117,4 +117,36 @@ char * dtostrf(double number, signed char width, unsigned char prec, char *s) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
strrstr (static)
|
||||||
|
|
||||||
|
Backwards search for p_pcPattern in p_pcString
|
||||||
|
Based on: https://stackoverflow.com/a/1634398/2778898
|
||||||
|
|
||||||
|
*/
|
||||||
|
const char* strrstr(const char*__restrict p_pcString,
|
||||||
|
const char*__restrict p_pcPattern)
|
||||||
|
{
|
||||||
|
const char* pcResult = 0;
|
||||||
|
|
||||||
|
size_t stStringLength = (p_pcString ? strlen(p_pcString) : 0);
|
||||||
|
size_t stPatternLength = (p_pcPattern ? strlen(p_pcPattern) : 0);
|
||||||
|
|
||||||
|
if ((stStringLength) &&
|
||||||
|
(stPatternLength) &&
|
||||||
|
(stPatternLength <= stStringLength))
|
||||||
|
{
|
||||||
|
// Pattern is shorter or has the same length than the string
|
||||||
|
for (const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s)
|
||||||
|
{
|
||||||
|
if (0 == strncmp(s, p_pcPattern, stPatternLength))
|
||||||
|
{
|
||||||
|
pcResult = s;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pcResult;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -45,6 +45,8 @@ static const char* s_panic_what = 0;
|
|||||||
static bool s_abort_called = false;
|
static bool s_abort_called = false;
|
||||||
static const char* s_unhandled_exception = NULL;
|
static const char* s_unhandled_exception = NULL;
|
||||||
|
|
||||||
|
static uint32_t s_stacksmash_addr = 0;
|
||||||
|
|
||||||
void abort() __attribute__((noreturn));
|
void abort() __attribute__((noreturn));
|
||||||
static void uart_write_char_d(char c);
|
static void uart_write_char_d(char c);
|
||||||
static void uart0_write_char_d(char c);
|
static void uart0_write_char_d(char c);
|
||||||
@ -54,6 +56,7 @@ static void print_stack(uint32_t start, uint32_t end);
|
|||||||
// using numbers different from "REASON_" in user_interface.h (=0..6)
|
// using numbers different from "REASON_" in user_interface.h (=0..6)
|
||||||
enum rst_reason_sw
|
enum rst_reason_sw
|
||||||
{
|
{
|
||||||
|
REASON_USER_STACK_SMASH = 253,
|
||||||
REASON_USER_SWEXCEPTION_RST = 254
|
REASON_USER_SWEXCEPTION_RST = 254
|
||||||
};
|
};
|
||||||
static int s_user_reset_reason = REASON_DEFAULT_RST;
|
static int s_user_reset_reason = REASON_DEFAULT_RST;
|
||||||
@ -141,12 +144,20 @@ void __wrap_system_restart_local() {
|
|||||||
ets_printf_P(PSTR("\nAbort called\n"));
|
ets_printf_P(PSTR("\nAbort called\n"));
|
||||||
}
|
}
|
||||||
else if (rst_info.reason == REASON_EXCEPTION_RST) {
|
else if (rst_info.reason == REASON_EXCEPTION_RST) {
|
||||||
|
// The GCC divide routine in ROM jumps to the address below and executes ILL (00 00 00) on div-by-zero
|
||||||
|
// In that case, print the exception as (6) which is IntegerDivZero
|
||||||
|
bool div_zero = (rst_info.exccause == 0) && (rst_info.epc1 == 0x4000dce5);
|
||||||
ets_printf_P(PSTR("\nException (%d):\nepc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n"),
|
ets_printf_P(PSTR("\nException (%d):\nepc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n"),
|
||||||
rst_info.exccause, rst_info.epc1, rst_info.epc2, rst_info.epc3, rst_info.excvaddr, rst_info.depc);
|
div_zero ? 6 : rst_info.exccause, rst_info.epc1, rst_info.epc2, rst_info.epc3, rst_info.excvaddr, rst_info.depc);
|
||||||
}
|
}
|
||||||
else if (rst_info.reason == REASON_SOFT_WDT_RST) {
|
else if (rst_info.reason == REASON_SOFT_WDT_RST) {
|
||||||
ets_printf_P(PSTR("\nSoft WDT reset\n"));
|
ets_printf_P(PSTR("\nSoft WDT reset\n"));
|
||||||
}
|
}
|
||||||
|
else if (rst_info.reason == REASON_USER_STACK_SMASH) {
|
||||||
|
ets_printf_P(PSTR("\nStack overflow detected.\n"));
|
||||||
|
ets_printf_P(PSTR("\nException (%d):\nepc1=0x%08x epc2=0x%08x epc3=0x%08x excvaddr=0x%08x depc=0x%08x\n"),
|
||||||
|
5 /* Alloca exception, closest thing to stack fault*/, s_stacksmash_addr, 0, 0, 0, 0);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
ets_printf_P(PSTR("\nGeneric Reset\n"));
|
ets_printf_P(PSTR("\nGeneric Reset\n"));
|
||||||
}
|
}
|
||||||
@ -209,6 +220,12 @@ void __wrap_system_restart_local() {
|
|||||||
|
|
||||||
cut_here();
|
cut_here();
|
||||||
|
|
||||||
|
if (s_unhandled_exception && umm_last_fail_alloc_addr) {
|
||||||
|
// now outside from the "cut-here" zone, print correctly the `new` caller address,
|
||||||
|
// idf-monitor.py will be able to decode this one and show exact location in sources
|
||||||
|
ets_printf_P(PSTR("\nlast failed alloc caller: 0x%08x\n"), (uint32_t)umm_last_fail_alloc_addr);
|
||||||
|
}
|
||||||
|
|
||||||
custom_crash_callback( &rst_info, sp_dump + offset, stack_end );
|
custom_crash_callback( &rst_info, sp_dump + offset, stack_end );
|
||||||
|
|
||||||
ets_delay_us(10000);
|
ets_delay_us(10000);
|
||||||
@ -290,4 +307,20 @@ void __panic_func(const char* file, int line, const char* func) {
|
|||||||
raise_exception();
|
raise_exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uintptr_t __stack_chk_guard = 0x08675309 ^ RANDOM_REG32;
|
||||||
|
void __stack_chk_fail(void) {
|
||||||
|
s_user_reset_reason = REASON_USER_STACK_SMASH;
|
||||||
|
ets_printf_P(PSTR("\nPANIC: Stack overrun"));
|
||||||
|
|
||||||
|
s_stacksmash_addr = (uint32_t)__builtin_return_address(0);
|
||||||
|
|
||||||
|
if (gdb_present())
|
||||||
|
__asm__ __volatile__ ("syscall"); // triggers GDB when enabled
|
||||||
|
|
||||||
|
__wrap_system_restart_local();
|
||||||
|
|
||||||
|
while (1); // never reached, needed to satisfy "noreturn" attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -234,7 +234,7 @@ void ICACHE_RAM_ATTR Twi::busywait(unsigned int v)
|
|||||||
unsigned int i;
|
unsigned int i;
|
||||||
for (i = 0; i < v; i++) // loop time is 5 machine cycles: 31.25ns @ 160MHz, 62.5ns @ 80MHz
|
for (i = 0; i < v; i++) // loop time is 5 machine cycles: 31.25ns @ 160MHz, 62.5ns @ 80MHz
|
||||||
{
|
{
|
||||||
asm("nop"); // minimum element to keep GCC from optimizing this function out.
|
__asm__ __volatile__("nop"); // minimum element to keep GCC from optimizing this function out.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +31,9 @@ extern "C" {
|
|||||||
|
|
||||||
static volatile timercallback timer1_user_cb = NULL;
|
static volatile timercallback timer1_user_cb = NULL;
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer1_isr_handler(void *para){
|
void ICACHE_RAM_ATTR timer1_isr_handler(void *para, void *frame) {
|
||||||
(void) para;
|
(void) para;
|
||||||
|
(void) frame;
|
||||||
if ((T1C & ((1 << TCAR) | (1 << TCIT))) == 0) TEIE &= ~TEIE1;//edge int disable
|
if ((T1C & ((1 << TCAR) | (1 << TCIT))) == 0) TEIE &= ~TEIE1;//edge int disable
|
||||||
T1I = 0;
|
T1I = 0;
|
||||||
if (timer1_user_cb) {
|
if (timer1_user_cb) {
|
||||||
@ -79,8 +80,9 @@ void ICACHE_RAM_ATTR timer1_disable(){
|
|||||||
|
|
||||||
static volatile timercallback timer0_user_cb = NULL;
|
static volatile timercallback timer0_user_cb = NULL;
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR timer0_isr_handler(void* para){
|
void ICACHE_RAM_ATTR timer0_isr_handler(void *para, void *frame) {
|
||||||
(void) para;
|
(void) para;
|
||||||
|
(void) frame;
|
||||||
if (timer0_user_cb) {
|
if (timer0_user_cb) {
|
||||||
// to make ISR compatible to Arduino AVR model where interrupts are disabled
|
// to make ISR compatible to Arduino AVR model where interrupts are disabled
|
||||||
// we disable them before we call the client ISR
|
// we disable them before we call the client ISR
|
||||||
|
@ -1,312 +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 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"
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return startWaveformClockCycles(pin, microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), microsecondsToClockCycles(runTimeUS));
|
|
||||||
}
|
|
||||||
|
|
||||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles) {
|
|
||||||
if ((pin > 16) || isFlashInterfacePin(pin)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Waveform *wave = &waveform[pin];
|
|
||||||
// Adjust to shave off some of the IRQ time, approximately
|
|
||||||
wave->nextTimeHighCycles = timeHighCycles;
|
|
||||||
wave->nextTimeLowCycles = timeLowCycles;
|
|
||||||
wave->expiryCycle = runTimeCycles ? GetCycleCount() + runTimeCycles : 0;
|
|
||||||
if (runTimeCycles && !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
|
|
||||||
if (waveformEnabled & (1UL << pin)) {
|
|
||||||
waveformToDisable = 1UL << pin;
|
|
||||||
// Must not interfere if Timer is due shortly
|
|
||||||
if (T1L > microsecondsToClockCycles(10)) {
|
|
||||||
timer1_write(microsecondsToClockCycles(10));
|
|
||||||
}
|
|
||||||
while (waveformToDisable) {
|
|
||||||
/* no-op */ // Can't delay() since stopWaveform may be called from an IRQ
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!waveformEnabled && !timer1CB) {
|
|
||||||
deinitTimer();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The SDK and hardware take some time to actually get to our NMI code, so
|
|
||||||
// decrement the next IRQ's timer value by a bit so we can actually catch the
|
|
||||||
// real CPU cycle counter we want for the waveforms.
|
|
||||||
#if F_CPU == 80000000
|
|
||||||
#define DELTAIRQ (microsecondsToClockCycles(3))
|
|
||||||
#else
|
|
||||||
#define DELTAIRQ (microsecondsToClockCycles(2))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
static ICACHE_RAM_ATTR void timer1Interrupt() {
|
|
||||||
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
|
|
||||||
// are generating. In the common case (1 PWM) these may be the same pin and
|
|
||||||
// we can avoid looking at the other pins.
|
|
||||||
static int startPin = 0;
|
|
||||||
static int endPin = 0;
|
|
||||||
|
|
||||||
uint32_t nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
|
||||||
uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14);
|
|
||||||
|
|
||||||
if (waveformToEnable || waveformToDisable) {
|
|
||||||
// Handle enable/disable requests from main app.
|
|
||||||
waveformEnabled = (waveformEnabled & ~waveformToDisable) | waveformToEnable; // Set the requested waveforms on/off
|
|
||||||
waveformState &= ~waveformToEnable; // And clear the state of any just started
|
|
||||||
waveformToEnable = 0;
|
|
||||||
waveformToDisable = 0;
|
|
||||||
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
|
||||||
startPin = __builtin_ffs(waveformEnabled) - 1;
|
|
||||||
// Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one)
|
|
||||||
endPin = 32 - __builtin_clz(waveformEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool done = false;
|
|
||||||
if (waveformEnabled) {
|
|
||||||
do {
|
|
||||||
nextEventCycles = microsecondsToClockCycles(MAXIRQUS);
|
|
||||||
for (int i = startPin; i <= endPin; i++) {
|
|
||||||
uint32_t mask = 1<<i;
|
|
||||||
|
|
||||||
// If it's not on, ignore!
|
|
||||||
if (!(waveformEnabled & mask)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Waveform *wave = &waveform[i];
|
|
||||||
uint32_t now = GetCycleCountIRQ();
|
|
||||||
|
|
||||||
// Disable any waveforms that are done
|
|
||||||
if (wave->expiryCycle) {
|
|
||||||
int32_t expiryToGo = wave->expiryCycle - now;
|
|
||||||
if (expiryToGo < 0) {
|
|
||||||
// Done, remove!
|
|
||||||
waveformEnabled &= ~mask;
|
|
||||||
if (i == 16) {
|
|
||||||
GP16O &= ~1;
|
|
||||||
} else {
|
|
||||||
ClearGPIO(mask);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for toggles
|
|
||||||
int32_t cyclesToGo = wave->nextServiceCycle - now;
|
|
||||||
if (cyclesToGo < 0) {
|
|
||||||
waveformState ^= mask;
|
|
||||||
if (waveformState & mask) {
|
|
||||||
if (i == 16) {
|
|
||||||
GP16O |= 1; // GPIO16 write slow as it's RMW
|
|
||||||
} else {
|
|
||||||
SetGPIO(mask);
|
|
||||||
}
|
|
||||||
wave->nextServiceCycle = now + wave->nextTimeHighCycles;
|
|
||||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeHighCycles);
|
|
||||||
} else {
|
|
||||||
if (i == 16) {
|
|
||||||
GP16O &= ~1; // GPIO16 write slow as it's RMW
|
|
||||||
} else {
|
|
||||||
ClearGPIO(mask);
|
|
||||||
}
|
|
||||||
wave->nextServiceCycle = now + wave->nextTimeLowCycles;
|
|
||||||
nextEventCycles = min_u32(nextEventCycles, wave->nextTimeLowCycles);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uint32_t deltaCycles = wave->nextServiceCycle - now;
|
|
||||||
nextEventCycles = min_u32(nextEventCycles, deltaCycles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur
|
|
||||||
uint32_t now = GetCycleCountIRQ();
|
|
||||||
int32_t cycleDeltaNextEvent = timeoutCycle - (now + nextEventCycles);
|
|
||||||
int32_t cyclesLeftTimeout = timeoutCycle - now;
|
|
||||||
done = (cycleDeltaNextEvent < 0) || (cyclesLeftTimeout < 0);
|
|
||||||
} while (!done);
|
|
||||||
} // if (waveformEnabled)
|
|
||||||
|
|
||||||
if (timer1CB) {
|
|
||||||
nextEventCycles = min_u32(nextEventCycles, timer1CB());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextEventCycles < microsecondsToClockCycles(10)) {
|
|
||||||
nextEventCycles = microsecondsToClockCycles(10);
|
|
||||||
}
|
|
||||||
nextEventCycles -= DELTAIRQ;
|
|
||||||
|
|
||||||
// Do it here instead of global function to save time and because we know it's edge-IRQ
|
|
||||||
#if F_CPU == 160000000
|
|
||||||
T1L = nextEventCycles >> 1; // Already know we're in range by MAXIRQUS
|
|
||||||
#else
|
|
||||||
T1L = nextEventCycles; // Already know we're in range by MAXIRQUS
|
|
||||||
#endif
|
|
||||||
TEIE |= TEIE1; // Edge int enable
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
@ -2,6 +2,7 @@
|
|||||||
esp8266_waveform - General purpose waveform generation and control,
|
esp8266_waveform - General purpose waveform generation and control,
|
||||||
supporting outputs on all pins in parallel.
|
supporting outputs on all pins in parallel.
|
||||||
|
|
||||||
|
-- Default, PWM locked version --
|
||||||
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||||
|
|
||||||
The core idea is to have a programmable waveform generator with a unique
|
The core idea is to have a programmable waveform generator with a unique
|
||||||
@ -22,6 +23,30 @@
|
|||||||
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
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
|
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
||||||
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||||
|
----------
|
||||||
|
|
||||||
|
-- Phase locked version --
|
||||||
|
Copyright (c) 2020 Dirk O. Kaar.
|
||||||
|
|
||||||
|
The core idea is to have a programmable waveform generator with a unique
|
||||||
|
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||||
|
set to 1-shot mode and is always loaded with the time until the next edge
|
||||||
|
of any live waveforms.
|
||||||
|
|
||||||
|
Up to one waveform generator per pin supported.
|
||||||
|
|
||||||
|
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||||
|
timer. This allows for removing interrupt jitter and delay as the counter
|
||||||
|
always increments once per 80MHz clock. Changes to a waveform are
|
||||||
|
contiguous and only take effect on the next waveform transition,
|
||||||
|
allowing for smooth transitions.
|
||||||
|
|
||||||
|
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||||
|
|
||||||
|
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
||||||
|
clock cycle count, or an interval measured in CPU clock cycles, but not TIMER1
|
||||||
|
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||||
|
----------
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
This library is free software; you can redistribute it and/or
|
||||||
modify it under the terms of the GNU Lesser General Public
|
modify it under the terms of the GNU Lesser General Public
|
||||||
@ -47,20 +72,41 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Call this function in your setup() to cause the phase locked version of the generator to
|
||||||
|
// be linked in automatically. Otherwise, the default PWM locked version will be used.
|
||||||
|
void enablePhaseLockedWaveform(void);
|
||||||
|
|
||||||
|
|
||||||
// Start or change a waveform of the specified high and low times on specific pin.
|
// Start or change a waveform of the specified high and low times on specific pin.
|
||||||
// If runtimeUS > 0 then automatically stop it after that many usecs.
|
// If runtimeUS > 0 then automatically stop it after that many usecs, relative to the next
|
||||||
|
// full period.
|
||||||
|
// If waveform is not yet started on pin, and on pin == alignPhase a waveform is running,
|
||||||
|
// the new waveform is started at phaseOffsetUS phase offset, in microseconds, to that.
|
||||||
|
// Setting autoPwm to true allows the wave generator to maintain PWM duty to idle cycle ratio
|
||||||
|
// under load, for applications where frequency or duty cycle must not change, leave false.
|
||||||
// Returns true or false on success or failure.
|
// Returns true or false on success or failure.
|
||||||
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS);
|
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS = 0,
|
||||||
|
// Following parameters are ignored unless in PhaseLocked mode
|
||||||
|
int8_t alignPhase = -1, uint32_t phaseOffsetUS = 0, bool autoPwm = false);
|
||||||
|
|
||||||
// Start or change a waveform of the specified high and low CPU clock cycles on specific pin.
|
// 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.
|
// 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.
|
// Returns true or false on success or failure.
|
||||||
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles);
|
int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCcys, uint32_t timeLowCcys, uint32_t runTimeCcys = 0,
|
||||||
|
// Following parameters are ignored unless in PhaseLocked mode
|
||||||
|
int8_t alignPhase = -1, uint32_t phaseOffsetCcys = 0, bool autoPwm = false);
|
||||||
|
|
||||||
// Stop a waveform, if any, on the specified pin.
|
// Stop a waveform, if any, on the specified pin.
|
||||||
// Returns true or false on success or failure.
|
// Returns true or false on success or failure.
|
||||||
int stopWaveform(uint8_t pin);
|
int stopWaveform(uint8_t pin);
|
||||||
|
|
||||||
// Add a callback function to be called on *EVERY* timer1 trigger. The
|
// Add a callback function to be called on *EVERY* timer1 trigger. The
|
||||||
// callback returns the number of microseconds until the next desired call.
|
// callback must return the number of CPU clock cycles until the next desired call.
|
||||||
// However, since it is called every timer1 interrupt, it may be called
|
// However, since it is called every timer1 interrupt, it may be called
|
||||||
// again before this period. It should therefore use the ESP Cycle Counter
|
// again before this period. It should therefore use the ESP Cycle Counter
|
||||||
// to determine whether or not to perform an operation.
|
// to determine whether or not to perform an operation.
|
||||||
@ -69,6 +115,12 @@ int stopWaveform(uint8_t pin);
|
|||||||
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
|
// Make sure the CB function has the ICACHE_RAM_ATTR decorator.
|
||||||
void setTimer1Callback(uint32_t (*fn)());
|
void setTimer1Callback(uint32_t (*fn)());
|
||||||
|
|
||||||
|
|
||||||
|
// Internal-only calls, not for applications
|
||||||
|
extern void _setPWMFreq(uint32_t freq);
|
||||||
|
extern bool _stopPWM(uint8_t pin);
|
||||||
|
extern bool _setPWM(int pin, uint32_t val, uint32_t range);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
444
cores/esp8266/core_esp8266_waveform_phase.cpp
Normal file
444
cores/esp8266/core_esp8266_waveform_phase.cpp
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
/*
|
||||||
|
esp8266_waveform - General purpose waveform generation and control,
|
||||||
|
supporting outputs on all pins in parallel.
|
||||||
|
|
||||||
|
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||||
|
Copyright (c) 2020 Dirk O. Kaar.
|
||||||
|
|
||||||
|
The core idea is to have a programmable waveform generator with a unique
|
||||||
|
high and low period (defined in microseconds or CPU clock cycles). TIMER1 is
|
||||||
|
set to 1-shot mode and is always loaded with the time until the next edge
|
||||||
|
of any live waveforms.
|
||||||
|
|
||||||
|
Up to one waveform generator per pin supported.
|
||||||
|
|
||||||
|
Each waveform generator is synchronized to the ESP clock cycle counter, not the
|
||||||
|
timer. This allows for removing interrupt jitter and delay as the counter
|
||||||
|
always increments once per 80MHz clock. Changes to a waveform are
|
||||||
|
contiguous and only take effect on the next waveform transition,
|
||||||
|
allowing for smooth transitions.
|
||||||
|
|
||||||
|
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||||
|
|
||||||
|
Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount()
|
||||||
|
clock cycle time, or an interval measured in clock cycles, but not TIMER1
|
||||||
|
cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "core_esp8266_waveform.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "debug.h"
|
||||||
|
#include "ets_sys.h"
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" void enablePhaseLockedWaveform (void)
|
||||||
|
{
|
||||||
|
// Does nothing, added to app to enable linking these versions
|
||||||
|
// of the waveform functions instead of the default.
|
||||||
|
DEBUGV("Enabling phase locked waveform generator\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// No-op calls to override the PWM implementation
|
||||||
|
extern "C" void _setPWMFreq_weak(uint32_t freq) { (void) freq; }
|
||||||
|
extern "C" bool _stopPWM_weak(int pin) { (void) pin; return false; }
|
||||||
|
extern "C" bool _setPWM_weak(int pin, uint32_t val, uint32_t range) { (void) pin; (void) val; (void) range; return false; }
|
||||||
|
|
||||||
|
|
||||||
|
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
||||||
|
constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160;
|
||||||
|
// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz
|
||||||
|
constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000);
|
||||||
|
// Maximum servicing time for any single IRQ
|
||||||
|
constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18);
|
||||||
|
// The latency between in-ISR rearming of the timer and the earliest firing
|
||||||
|
constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2);
|
||||||
|
// The SDK and hardware take some time to actually get to our NMI code
|
||||||
|
constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ?
|
||||||
|
microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2);
|
||||||
|
|
||||||
|
// for INFINITE, the NMI proceeds on the waveform without expiry deadline.
|
||||||
|
// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy.
|
||||||
|
// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES.
|
||||||
|
// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY.
|
||||||
|
enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3};
|
||||||
|
|
||||||
|
// Waveform generator can create tones, PWM, and servos
|
||||||
|
typedef struct {
|
||||||
|
uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count
|
||||||
|
uint32_t endDutyCcy; // ESP clock cycle when going from duty to off
|
||||||
|
int32_t dutyCcys; // Set next off cycle at low->high to maintain phase
|
||||||
|
int32_t adjDutyCcys; // Temporary correction for next period
|
||||||
|
int32_t periodCcys; // Set next phase cycle at low->high to maintain phase
|
||||||
|
uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count
|
||||||
|
WaveformMode mode;
|
||||||
|
int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin
|
||||||
|
bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings
|
||||||
|
} Waveform;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
Waveform pins[17]; // State of all possible pins
|
||||||
|
uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||||
|
uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||||
|
|
||||||
|
// Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine
|
||||||
|
int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform
|
||||||
|
int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation
|
||||||
|
|
||||||
|
uint32_t(*timer1CB)() = nullptr;
|
||||||
|
|
||||||
|
bool timer1Running = false;
|
||||||
|
|
||||||
|
uint32_t nextEventCcy;
|
||||||
|
} waveform;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interrupt on/off control
|
||||||
|
static ICACHE_RAM_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 ICACHE_RAM_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
|
||||||
|
ICACHE_RAM_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 ICACHE_RAM_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 ICACHE_RAM_ATTR void timer1Interrupt() {
|
||||||
|
const uint32_t isrStartCcy = ESP.getCycleCount();
|
||||||
|
int32_t clockDrift = isrStartCcy - waveform.nextEventCcy;
|
||||||
|
const bool isCPU2X = CPU2X & 1;
|
||||||
|
if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) {
|
||||||
|
// Handle enable/disable requests from main app.
|
||||||
|
waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off
|
||||||
|
// Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t)
|
||||||
|
waveform.toDisableBits = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waveform.toSetBits) {
|
||||||
|
const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1;
|
||||||
|
Waveform& wave = waveform.pins[toSetPin];
|
||||||
|
switch (wave.mode) {
|
||||||
|
case WaveformMode::INIT:
|
||||||
|
waveform.states &= ~waveform.toSetBits; // Clear the state of any just started
|
||||||
|
if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) {
|
||||||
|
wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wave.nextPeriodCcy = waveform.nextEventCcy;
|
||||||
|
}
|
||||||
|
if (!wave.expiryCcy) {
|
||||||
|
wave.mode = WaveformMode::INFINITE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// fall through
|
||||||
|
case WaveformMode::UPDATEEXPIRY:
|
||||||
|
// in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count
|
||||||
|
wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X);
|
||||||
|
wave.mode = WaveformMode::EXPIRES;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
waveform.toSetBits = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit the loop if the next event, if any, is sufficiently distant.
|
||||||
|
const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS;
|
||||||
|
uint32_t busyPins = waveform.enabled;
|
||||||
|
waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS;
|
||||||
|
|
||||||
|
uint32_t now = ESP.getCycleCount();
|
||||||
|
uint32_t isrNextEventCcy = now;
|
||||||
|
while (busyPins) {
|
||||||
|
if (static_cast<int32_t>(isrNextEventCcy - now) > IRQLATENCYCCYS) {
|
||||||
|
waveform.nextEventCcy = isrNextEventCcy;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
isrNextEventCcy = waveform.nextEventCcy;
|
||||||
|
uint32_t loopPins = busyPins;
|
||||||
|
while (loopPins) {
|
||||||
|
const int pin = __builtin_ffsl(loopPins) - 1;
|
||||||
|
const uint32_t pinBit = 1UL << pin;
|
||||||
|
loopPins ^= pinBit;
|
||||||
|
|
||||||
|
Waveform& wave = waveform.pins[pin];
|
||||||
|
|
||||||
|
if (clockDrift) {
|
||||||
|
wave.endDutyCcy += clockDrift;
|
||||||
|
wave.nextPeriodCcy += clockDrift;
|
||||||
|
wave.expiryCcy += clockDrift;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy;
|
||||||
|
if (WaveformMode::EXPIRES == wave.mode &&
|
||||||
|
static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) >= 0 &&
|
||||||
|
static_cast<int32_t>(now - wave.expiryCcy) >= 0) {
|
||||||
|
// Disable any waveforms that are done
|
||||||
|
waveform.enabled ^= pinBit;
|
||||||
|
busyPins ^= pinBit;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const int32_t overshootCcys = now - waveNextEventCcy;
|
||||||
|
if (overshootCcys >= 0) {
|
||||||
|
const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X);
|
||||||
|
if (waveform.states & pinBit) {
|
||||||
|
// active configuration and forward are 100% duty
|
||||||
|
if (wave.periodCcys == wave.dutyCcys) {
|
||||||
|
wave.nextPeriodCcy += periodCcys;
|
||||||
|
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (wave.autoPwm) {
|
||||||
|
wave.adjDutyCcys += overshootCcys;
|
||||||
|
}
|
||||||
|
waveform.states ^= pinBit;
|
||||||
|
if (16 == pin) {
|
||||||
|
GP16O = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GPOC = pinBit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
waveNextEventCcy = wave.nextPeriodCcy;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wave.nextPeriodCcy += periodCcys;
|
||||||
|
if (!wave.dutyCcys) {
|
||||||
|
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X);
|
||||||
|
if (dutyCcys <= wave.adjDutyCcys) {
|
||||||
|
dutyCcys >>= 1;
|
||||||
|
wave.adjDutyCcys -= dutyCcys;
|
||||||
|
}
|
||||||
|
else if (wave.adjDutyCcys) {
|
||||||
|
dutyCcys -= wave.adjDutyCcys;
|
||||||
|
wave.adjDutyCcys = 0;
|
||||||
|
}
|
||||||
|
wave.endDutyCcy = now + dutyCcys;
|
||||||
|
if (static_cast<int32_t>(wave.endDutyCcy - wave.nextPeriodCcy) > 0) {
|
||||||
|
wave.endDutyCcy = wave.nextPeriodCcy;
|
||||||
|
}
|
||||||
|
waveform.states |= pinBit;
|
||||||
|
if (16 == pin) {
|
||||||
|
GP16O = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GPOS = pinBit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
waveNextEventCcy = wave.endDutyCcy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WaveformMode::EXPIRES == wave.mode && static_cast<int32_t>(waveNextEventCcy - wave.expiryCcy) > 0) {
|
||||||
|
waveNextEventCcy = wave.expiryCcy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static_cast<int32_t>(waveNextEventCcy - isrTimeoutCcy) >= 0) {
|
||||||
|
busyPins ^= pinBit;
|
||||||
|
if (static_cast<int32_t>(waveform.nextEventCcy - waveNextEventCcy) > 0) {
|
||||||
|
waveform.nextEventCcy = waveNextEventCcy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (static_cast<int32_t>(isrNextEventCcy - waveNextEventCcy) > 0) {
|
||||||
|
isrNextEventCcy = waveNextEventCcy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
now = ESP.getCycleCount();
|
||||||
|
}
|
||||||
|
clockDrift = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t callbackCcys = 0;
|
||||||
|
if (waveform.timer1CB) {
|
||||||
|
callbackCcys = scaleCcys(waveform.timer1CB(), isCPU2X);
|
||||||
|
}
|
||||||
|
now = ESP.getCycleCount();
|
||||||
|
int32_t nextEventCcys = waveform.nextEventCcy - now;
|
||||||
|
// Account for unknown duration of timer1CB().
|
||||||
|
if (waveform.timer1CB && nextEventCcys > callbackCcys) {
|
||||||
|
waveform.nextEventCcy = now + callbackCcys;
|
||||||
|
nextEventCcys = callbackCcys;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer is 80MHz fixed. 160MHz CPU frequency need scaling.
|
||||||
|
int32_t deltaIrqCcys = DELTAIRQCCYS;
|
||||||
|
int32_t irqLatencyCcys = IRQLATENCYCCYS;
|
||||||
|
if (isCPU2X) {
|
||||||
|
nextEventCcys >>= 1;
|
||||||
|
deltaIrqCcys >>= 1;
|
||||||
|
irqLatencyCcys >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Firing timer too soon, the NMI occurs before ISR has returned.
|
||||||
|
if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) {
|
||||||
|
waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS;
|
||||||
|
nextEventCcys = irqLatencyCcys;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nextEventCcys -= deltaIrqCcys;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register access is fast and edge IRQ was configured before.
|
||||||
|
T1L = nextEventCcys;
|
||||||
|
}
|
666
cores/esp8266/core_esp8266_waveform_pwm.cpp
Normal file
666
cores/esp8266/core_esp8266_waveform_pwm.cpp
Normal file
@ -0,0 +1,666 @@
|
|||||||
|
/*
|
||||||
|
esp8266_waveform - General purpose waveform generation and control,
|
||||||
|
supporting outputs on all pins in parallel.
|
||||||
|
|
||||||
|
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
|
||||||
|
|
||||||
|
The core idea is to have a programmable waveform generator with a unique
|
||||||
|
high and low period (defined in microseconds or CPU clock cycles). TIMER1
|
||||||
|
is set to 1-shot mode and is always loaded with the time until the next
|
||||||
|
edge of any live waveforms.
|
||||||
|
|
||||||
|
Up to one waveform generator per pin supported.
|
||||||
|
|
||||||
|
Each waveform generator is synchronized to the ESP clock cycle counter, not
|
||||||
|
the timer. This allows for removing interrupt jitter and delay as the
|
||||||
|
counter always increments once per 80MHz clock. Changes to a waveform are
|
||||||
|
contiguous and only take effect on the next waveform transition,
|
||||||
|
allowing for smooth transitions.
|
||||||
|
|
||||||
|
This replaces older tone(), analogWrite(), and the Servo classes.
|
||||||
|
|
||||||
|
Everywhere in the code where "cycles" is used, it means ESP.getCycleCount()
|
||||||
|
clock cycle count, or an interval measured in CPU clock cycles, but not
|
||||||
|
TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz).
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "ets_sys.h"
|
||||||
|
#include "core_esp8266_waveform.h"
|
||||||
|
#include "user_interface.h"
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
// Maximum delay between IRQs
|
||||||
|
#define MAXIRQUS (10000)
|
||||||
|
|
||||||
|
// Waveform generator can create tones, PWM, and servos
|
||||||
|
typedef struct {
|
||||||
|
uint32_t nextServiceCycle; // ESP cycle timer when a transition required
|
||||||
|
uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop
|
||||||
|
uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles)
|
||||||
|
uint32_t timeLowCycles; //
|
||||||
|
uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal
|
||||||
|
uint32_t desiredLowCycles; //
|
||||||
|
uint32_t lastEdge; // Cycle when this generator last changed
|
||||||
|
} Waveform;
|
||||||
|
|
||||||
|
class WVFState {
|
||||||
|
public:
|
||||||
|
Waveform waveform[17]; // State of all possible pins
|
||||||
|
uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code
|
||||||
|
uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code
|
||||||
|
|
||||||
|
// Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine
|
||||||
|
uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin
|
||||||
|
uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation
|
||||||
|
|
||||||
|
uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI
|
||||||
|
uint32_t waveformNewHigh = 0;
|
||||||
|
uint32_t waveformNewLow = 0;
|
||||||
|
|
||||||
|
uint32_t (*timer1CB)() = NULL;
|
||||||
|
|
||||||
|
// Optimize the NMI inner loop by keeping track of the min and max GPIO that we
|
||||||
|
// are generating. In the common case (1 PWM) these may be the same pin and
|
||||||
|
// we can avoid looking at the other pins.
|
||||||
|
uint16_t startPin = 0;
|
||||||
|
uint16_t endPin = 0;
|
||||||
|
};
|
||||||
|
static WVFState wvfState;
|
||||||
|
|
||||||
|
|
||||||
|
// Ensure everything is read/written to RAM
|
||||||
|
#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); }
|
||||||
|
|
||||||
|
// Non-speed critical bits
|
||||||
|
#pragma GCC optimize ("Os")
|
||||||
|
|
||||||
|
// Interrupt on/off control
|
||||||
|
static ICACHE_RAM_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 ICACHE_RAM_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 ICACHE_RAM_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 ICACHE_RAM_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));
|
||||||
|
ICACHE_RAM_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));
|
||||||
|
ICACHE_RAM_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")));
|
||||||
|
ICACHE_RAM_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 ICACHE_RAM_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 ICACHE_RAM_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 ICACHE_RAM_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
@ -39,4 +39,16 @@ extern int __analogRead(uint8_t pin)
|
|||||||
|
|
||||||
extern int analogRead(uint8_t pin) __attribute__ ((weak, alias("__analogRead")));
|
extern int analogRead(uint8_t pin) __attribute__ ((weak, alias("__analogRead")));
|
||||||
|
|
||||||
|
|
||||||
|
void __analogReference(uint8_t mode)
|
||||||
|
{
|
||||||
|
// Only DEFAULT is supported on the ESP8266
|
||||||
|
if (mode != DEFAULT) {
|
||||||
|
DEBUGV("analogReference called with illegal mode");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern void analogReference(uint8_t mode) __attribute__ ((weak, alias("__analogReference")));
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -82,7 +82,8 @@ extern void __pinMode(uint8_t pin, uint8_t mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
|
extern void ICACHE_RAM_ATTR __digitalWrite(uint8_t pin, uint8_t val) {
|
||||||
stopWaveform(pin);
|
stopWaveform(pin); // Disable any Tone or startWaveform on this pin
|
||||||
|
_stopPWM(pin); // and any analogWrites (PWM)
|
||||||
if(pin < 16){
|
if(pin < 16){
|
||||||
if(val) GPOS = (1 << pin);
|
if(val) GPOS = (1 << pin);
|
||||||
else GPOC = (1 << pin);
|
else GPOC = (1 << pin);
|
||||||
@ -130,8 +131,10 @@ typedef struct {
|
|||||||
static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, };
|
static interrupt_handler_t interrupt_handlers[16] = { {0, 0, 0, 0}, };
|
||||||
static uint32_t interrupt_reg = 0;
|
static uint32_t interrupt_reg = 0;
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR interrupt_handler(void*)
|
void ICACHE_RAM_ATTR interrupt_handler(void *arg, void *frame)
|
||||||
{
|
{
|
||||||
|
(void) arg;
|
||||||
|
(void) frame;
|
||||||
uint32_t status = GPIE;
|
uint32_t status = GPIE;
|
||||||
GPIEC = status;//clear them interrupts
|
GPIEC = status;//clear them interrupts
|
||||||
uint32_t levels = GPI;
|
uint32_t levels = GPI;
|
||||||
|
@ -26,27 +26,28 @@
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
static uint32_t analogMap = 0;
|
static int32_t analogScale = 255; // Match upstream default, breaking change from 2.x.x
|
||||||
static int32_t analogScale = PWMRANGE;
|
|
||||||
static uint16_t analogFreq = 1000;
|
|
||||||
|
|
||||||
extern void __analogWriteRange(uint32_t range) {
|
|
||||||
if (range > 0) {
|
static uint32_t analogMap = 0;
|
||||||
analogScale = range;
|
static uint16_t analogFreq = 1000;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void __analogWriteFreq(uint32_t freq) {
|
extern void __analogWriteFreq(uint32_t freq) {
|
||||||
if (freq < 100) {
|
if (freq < 100) {
|
||||||
analogFreq = 100;
|
analogFreq = 100;
|
||||||
} else if (freq > 40000) {
|
} else if (freq > 60000) {
|
||||||
analogFreq = 40000;
|
analogFreq = 60000;
|
||||||
} else {
|
} else {
|
||||||
analogFreq = freq;
|
analogFreq = freq;
|
||||||
}
|
}
|
||||||
|
_setPWMFreq(freq);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void __analogWrite(uint8_t pin, int val) {
|
extern void __analogWrite(uint8_t pin, int val) {
|
||||||
|
analogWriteMode(pin, val, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void __analogWriteMode(uint8_t pin, int val, bool openDrain) {
|
||||||
if (pin > 16) {
|
if (pin > 16) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -57,23 +58,47 @@ extern void __analogWrite(uint8_t pin, int val) {
|
|||||||
val = analogScale;
|
val = analogScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
analogMap &= ~(1 << pin);
|
if (analogMap & 1UL << pin) {
|
||||||
|
// Per the Arduino docs at https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/
|
||||||
|
// val: the duty cycle: between 0 (always off) and 255 (always on).
|
||||||
|
// So if val = 0 we have digitalWrite(LOW), if we have val==range we have digitalWrite(HIGH)
|
||||||
|
|
||||||
|
analogMap &= ~(1 << pin);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(openDrain) {
|
||||||
|
pinMode(pin, OUTPUT_OPEN_DRAIN);
|
||||||
|
} else {
|
||||||
|
pinMode(pin, OUTPUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
uint32_t high = (analogPeriod * val) / analogScale;
|
uint32_t high = (analogPeriod * val) / analogScale;
|
||||||
uint32_t low = analogPeriod - high;
|
uint32_t low = analogPeriod - high;
|
||||||
pinMode(pin, OUTPUT);
|
// 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)
|
||||||
if (low == 0) {
|
int phaseReference = __builtin_ffs(analogMap) - 1;
|
||||||
digitalWrite(pin, HIGH);
|
if (_setPWM(pin, val, analogScale)) {
|
||||||
} else if (high == 0) {
|
analogMap |= (1 << pin);
|
||||||
digitalWrite(pin, LOW);
|
} else if (startWaveformClockCycles(pin, high, low, 0, phaseReference, 0, true)) {
|
||||||
} else {
|
analogMap |= (1 << pin);
|
||||||
if (startWaveformClockCycles(pin, high, low, 0)) {
|
}
|
||||||
analogMap |= (1 << pin);
|
}
|
||||||
}
|
|
||||||
|
extern void __analogWriteRange(uint32_t range) {
|
||||||
|
if ((range >= 15) && (range <= 65535)) {
|
||||||
|
analogScale = range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void __analogWriteResolution(int res) {
|
||||||
|
if ((res >= 4) && (res <= 16)) {
|
||||||
|
analogScale = (1 << res) - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite")));
|
extern void analogWrite(uint8_t pin, int val) __attribute__((weak, alias("__analogWrite")));
|
||||||
|
extern void analogWriteMode(uint8_t pin, int val, bool openDrain) __attribute__((weak, alias("__analogWriteMode")));
|
||||||
extern void analogWriteFreq(uint32_t freq) __attribute__((weak, alias("__analogWriteFreq")));
|
extern void analogWriteFreq(uint32_t freq) __attribute__((weak, alias("__analogWriteFreq")));
|
||||||
extern void analogWriteRange(uint32_t range) __attribute__((weak, alias("__analogWriteRange")));
|
extern void analogWriteRange(uint32_t range) __attribute__((weak, alias("__analogWriteRange")));
|
||||||
|
extern void analogWriteResolution(int res) __attribute__((weak, alias("__analogWriteResolution")));
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -12,9 +12,6 @@ extern "C" {
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <cont.h> // g_pcont declaration
|
#include <cont.h> // g_pcont declaration
|
||||||
|
|
||||||
extern bool timeshift64_is_set;
|
|
||||||
extern uint32_t sntp_real_timestamp;
|
|
||||||
|
|
||||||
bool can_yield();
|
bool can_yield();
|
||||||
void esp_yield();
|
void esp_yield();
|
||||||
void esp_schedule();
|
void esp_schedule();
|
||||||
@ -31,9 +28,10 @@ uint32_t crc32 (const void* data, size_t length, uint32_t crc = 0xffffffff);
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
using BoolCB = std::function<void(bool)>;
|
||||||
using TrivialCB = std::function<void()>;
|
using TrivialCB = std::function<void()>;
|
||||||
|
|
||||||
void settimeofday_cb (TrivialCB&& cb);
|
void settimeofday_cb (const BoolCB& cb);
|
||||||
void settimeofday_cb (const TrivialCB& cb);
|
void settimeofday_cb (const TrivialCB& cb);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,36 +1,53 @@
|
|||||||
/*
|
/*
|
||||||
debug.cpp - debug helper functions
|
debug.cpp - debug helper functions
|
||||||
Copyright (c) 2015 Markus Sattler. All rights reserved.
|
Copyright (c) 2015 Markus Sattler. All rights reserved.
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
This library is free software; you can redistribute it and/or
|
||||||
modify it under the terms of the GNU Lesser General Public
|
modify it under the terms of the GNU Lesser General Public
|
||||||
License as published by the Free Software Foundation; either
|
License as published by the Free Software Foundation; either
|
||||||
version 2.1 of the License, or (at your option) any later version.
|
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,
|
This library is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
Lesser General Public License for more details.
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this library; if not, write to the Free Software
|
License along with this library; if not, write to the Free Software
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "osapi.h"
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR hexdump(const void *mem, uint32_t len, uint8_t cols) {
|
IRAM_ATTR
|
||||||
const uint8_t* src = (const uint8_t*) mem;
|
void hexdump(const void *mem, uint32_t len, uint8_t cols)
|
||||||
os_printf("\n[HEXDUMP] Address: 0x%08X len: 0x%X (%d)", (ptrdiff_t)src, len, len);
|
{
|
||||||
for(uint32_t i = 0; i < len; i++) {
|
const char* src = (const char*)mem;
|
||||||
if(i % cols == 0) {
|
os_printf("\n[HEXDUMP] Address: %p len: 0x%X (%d)", src, len, len);
|
||||||
os_printf("\n[0x%08X] 0x%08X: ", (ptrdiff_t)src, i);
|
while (len > 0)
|
||||||
yield();
|
{
|
||||||
|
uint32_t linesize = cols > len ? len : cols;
|
||||||
|
os_printf("\n[%p] 0x%04x: ", src, (int)(src - (const char*)mem));
|
||||||
|
for (uint32_t i = 0; i < linesize; i++)
|
||||||
|
{
|
||||||
|
os_printf("%02x ", *(src + i));
|
||||||
}
|
}
|
||||||
os_printf("%02X ", *src);
|
os_printf(" ");
|
||||||
src++;
|
for (uint32_t i = linesize; i < cols; i++)
|
||||||
|
{
|
||||||
|
os_printf(" ");
|
||||||
|
}
|
||||||
|
for (uint32_t i = 0; i < linesize; i++)
|
||||||
|
{
|
||||||
|
unsigned char c = *(src + i);
|
||||||
|
os_putc(isprint(c) ? c : '.');
|
||||||
|
}
|
||||||
|
src += linesize;
|
||||||
|
len -= linesize;
|
||||||
|
yield();
|
||||||
}
|
}
|
||||||
os_printf("\n");
|
os_printf("\n");
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
void hexdump(const void *mem, uint32_t len, uint8_t cols = 16);
|
extern "C" void hexdump(const void *mem, uint32_t len, uint8_t cols = 16);
|
||||||
#else
|
#else
|
||||||
void hexdump(const void *mem, uint32_t len, uint8_t cols);
|
void hexdump(const void *mem, uint32_t len, uint8_t cols);
|
||||||
#endif
|
#endif
|
||||||
@ -22,6 +22,7 @@ void hexdump(const void *mem, uint32_t len, uint8_t cols);
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void __unhandled_exception(const char *str) __attribute__((noreturn));
|
||||||
void __panic_func(const char* file, int line, const char* func) __attribute__((noreturn));
|
void __panic_func(const char* file, int line, const char* func) __attribute__((noreturn));
|
||||||
#define panic() __panic_func(PSTR(__FILE__), __LINE__, __func__)
|
#define panic() __panic_func(PSTR(__FILE__), __LINE__, __func__)
|
||||||
|
|
||||||
|
@ -21,6 +21,9 @@
|
|||||||
#ifndef ESP8266_PERI_H_INCLUDED
|
#ifndef ESP8266_PERI_H_INCLUDED
|
||||||
#define ESP8266_PERI_H_INCLUDED
|
#define ESP8266_PERI_H_INCLUDED
|
||||||
|
|
||||||
|
// we expect mocking framework to provide these
|
||||||
|
#ifndef CORE_MOCK
|
||||||
|
|
||||||
#include "c_types.h"
|
#include "c_types.h"
|
||||||
#include "esp8266_undocumented.h"
|
#include "esp8266_undocumented.h"
|
||||||
|
|
||||||
@ -847,4 +850,6 @@ extern volatile uint32_t* const esp8266_gpioToFn[16];
|
|||||||
**/
|
**/
|
||||||
#define RANDOM_REG32 ESP8266_DREG(0x20E44)
|
#define RANDOM_REG32 ESP8266_DREG(0x20E44)
|
||||||
|
|
||||||
|
#endif // ifndef CORE_MOCK
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,8 +1,31 @@
|
|||||||
// ROM and blob calls without official headers available
|
// ROM and blob calls without official headers available
|
||||||
|
|
||||||
|
#if !defined(__ESP8266_UNDOCUMENTED_H) && !(defined(_ASMLANGUAGE) || defined(__ASSEMBLER__))
|
||||||
|
#define __ESP8266_UNDOCUMENTED_H
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <eagle_soc.h>
|
||||||
|
#include <spi_flash.h>
|
||||||
|
|
||||||
|
#define PERIPHS_DPORT_18 (PERIPHS_DPORT_BASEADDR + 0x018)
|
||||||
|
#define PERIPHS_DPORT_ICACHE_ENABLE (PERIPHS_DPORT_BASEADDR + 0x024)
|
||||||
|
/* When enabled 16K IRAM starting at 0x4010C000 is unmapped */
|
||||||
|
#define ICACHE_ENABLE_FIRST_16K BIT3
|
||||||
|
/* When enabled 16K IRAM starting at 0x40108000 is unmapped */
|
||||||
|
#define ICACHE_ENABLE_SECOND_16K BIT4
|
||||||
|
#define PERIPHS_HW_WDT (0x60000900)
|
||||||
|
#define PERIPHS_I2C_48 (0x60000a00 + 0x348)
|
||||||
|
|
||||||
|
|
||||||
|
extern void (*user_start_fptr)();
|
||||||
|
|
||||||
|
#ifndef XCHAL_EXCCAUSE_NUM
|
||||||
|
// from tools/xtensa-lx106-elf/include/xtensa/config/core.h:629:#define XCHAL_EXCCAUSE_NUM 64
|
||||||
|
#define XCHAL_EXCCAUSE_NUM 64
|
||||||
|
#endif
|
||||||
|
|
||||||
// ROM
|
// ROM
|
||||||
|
|
||||||
@ -11,6 +34,12 @@ extern int rom_i2c_readReg_Mask(int, int, int, int, int);
|
|||||||
|
|
||||||
extern int uart_baudrate_detect(int, int);
|
extern int uart_baudrate_detect(int, int);
|
||||||
|
|
||||||
|
/* SDK/Flash contains also an implementation of this function
|
||||||
|
* but for reboot into UART download mode the version from ROM
|
||||||
|
* has to be used because flash is not accessible.
|
||||||
|
*/
|
||||||
|
extern void rom_uart_div_modify(uint8 uart_no, uint32 DivLatchValue);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
ROM function, uart_buff_switch(), is used to switch printing between UART0 and
|
ROM function, uart_buff_switch(), is used to switch printing between UART0 and
|
||||||
UART1. It updates a structure that only controls a select group of print
|
UART1. It updates a structure that only controls a select group of print
|
||||||
@ -32,8 +61,254 @@ extern void uart_buff_switch(uint8_t);
|
|||||||
*/
|
*/
|
||||||
extern int ets_uart_printf(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
|
extern int ets_uart_printf(const char *format, ...) __attribute__ ((format (printf, 1, 2)));
|
||||||
|
|
||||||
|
extern void user_uart_wait_tx_fifo_empty(uint32_t ch, uint32_t arg2);
|
||||||
|
extern void uartAttach();
|
||||||
|
extern void Uart_Init(uint32_t uart_no);
|
||||||
|
|
||||||
extern void ets_delay_us(uint32_t us);
|
extern void ets_delay_us(uint32_t us);
|
||||||
|
|
||||||
|
#ifndef GDBSTUB_H
|
||||||
|
/*
|
||||||
|
GDBSTUB duplicates these with some variances that are not compatible with our
|
||||||
|
references (offsets), which are synced with those used by the BootROM.
|
||||||
|
Specifically, the BootROM does not have register "a1" in the structure where
|
||||||
|
GDBSTUB does.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This structure is used in the argument list of "C" callable exception handlers.
|
||||||
|
See `_xtos_set_exception_handler` details below.
|
||||||
|
*/
|
||||||
|
struct __exception_frame
|
||||||
|
{
|
||||||
|
uint32_t epc;
|
||||||
|
uint32_t ps;
|
||||||
|
uint32_t sar;
|
||||||
|
uint32_t unused;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
uint32_t a0;
|
||||||
|
// note: no a1 here!
|
||||||
|
uint32_t a2;
|
||||||
|
uint32_t a3;
|
||||||
|
uint32_t a4;
|
||||||
|
uint32_t a5;
|
||||||
|
uint32_t a6;
|
||||||
|
uint32_t a7;
|
||||||
|
uint32_t a8;
|
||||||
|
uint32_t a9;
|
||||||
|
uint32_t a10;
|
||||||
|
uint32_t a11;
|
||||||
|
uint32_t a12;
|
||||||
|
uint32_t a13;
|
||||||
|
uint32_t a14;
|
||||||
|
uint32_t a15;
|
||||||
|
};
|
||||||
|
uint32_t a_reg[15];
|
||||||
|
};
|
||||||
|
uint32_t cause;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
Most of the comments here are gleamed from the xtensa files found at the site
|
||||||
|
listed below and are mostly unverified:
|
||||||
|
https://github.com/qca/open-ath9k-htc-firmware/tree/master/sboot/magpie_1_1/sboot/athos/src/xtos
|
||||||
|
* exc-c-wrapper-handler.S
|
||||||
|
* exc-sethandler.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
The Boot ROM sets up a table of dispatch handlers at 0x3FFFC000. This table
|
||||||
|
has an entry for each of the EXCCAUSE values, 0 through 63. The exception
|
||||||
|
handler at the `User Exception Vector` uses EXCCAUSE with the base address
|
||||||
|
0x3FFFC000 to build a jump address to the respective cause handler. Of the
|
||||||
|
cause handle functions, `_xtos_c_wrapper_handler` and `_xtos_unhandled_exception`
|
||||||
|
are of interest.
|
||||||
|
|
||||||
|
Exception handler entries that do not have a specific handler are set to
|
||||||
|
`_xtos_unhandled_exception`. This handler will execute a `break 1, 1`
|
||||||
|
(0x4000DC4Bu) before doing a `rfe` (return from exception). Since the PC has
|
||||||
|
not changed, the event that caused the 1st exception will likely keep
|
||||||
|
repeating until the HWDT kicks in.
|
||||||
|
|
||||||
|
These exception handling functions are in assembly, and do not conform to the
|
||||||
|
typical "C" function conventions. However, some form of prototype/typedef is
|
||||||
|
needed to reference these function addresses in "C" code. In
|
||||||
|
`RTOS_SDK/components/esp8266/include/xtensa/xtruntime.h`, it uses a compounded
|
||||||
|
definition that equates to `void (*)(...)` for .cpp modules to use. I have
|
||||||
|
noticed this creates sufficient confusion at compilation to get your attention
|
||||||
|
when used in the wrong place. I have copied that definition here.
|
||||||
|
|
||||||
|
Added to eagle.rom.addr.v6.ld:
|
||||||
|
PROVIDE ( _xtos_exc_handler_table = 0x3fffc000 );
|
||||||
|
PROVIDE ( _xtos_c_handler_table = 0x3fffc100 );
|
||||||
|
*/
|
||||||
|
#ifndef XTRUNTIME_H
|
||||||
|
// This is copy/paste from RTOS_SDK/components/esp8266/include/xtensa/xtruntime.h
|
||||||
|
#ifdef __cplusplus
|
||||||
|
typedef void (_xtos_handler_func)(...);
|
||||||
|
#else
|
||||||
|
typedef void (_xtos_handler_func)();
|
||||||
|
#endif
|
||||||
|
typedef _xtos_handler_func *_xtos_handler;
|
||||||
|
|
||||||
|
extern _xtos_handler _xtos_exc_handler_table[XCHAL_EXCCAUSE_NUM];
|
||||||
|
|
||||||
|
/*
|
||||||
|
Assembly-level handler, used in the _xtos_exc_handler_table[]. It is a wrapper
|
||||||
|
for calling registered "C" exception handlers.
|
||||||
|
*/
|
||||||
|
_xtos_handler_func _xtos_c_wrapper_handler;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Assembly-level handler, used in the _xtos_exc_handler_table[]. It is the
|
||||||
|
default handler, for exceptions without a registered handler.
|
||||||
|
*/
|
||||||
|
_xtos_handler_func _xtos_unhandled_exception;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
// For these definitions, try to be more precise for .cpp module usage.
|
||||||
|
|
||||||
|
/*
|
||||||
|
A detailed typdef for the "C" callable functions found in
|
||||||
|
`_xtos_c_handler_table[]` More details in `_xtos_set_exception_handler`
|
||||||
|
comments below.
|
||||||
|
*/
|
||||||
|
typedef void (*fn_c_exception_handler_t)(struct __exception_frame *ef, int cause);
|
||||||
|
|
||||||
|
/*
|
||||||
|
TMI maybe? However, it may be useful for a deep debugging session.
|
||||||
|
`_xtos_p_none` is the default "C" exception handler that fills the
|
||||||
|
_xtos_c_handler_table[]. It is present when an exception handler has not been
|
||||||
|
registered. It simply consist of a single instruction, `ret`.
|
||||||
|
It is also internally used by `_xtos_set_exception_handler(cause, NULL)` to
|
||||||
|
reset a "C" exception handler back to the unhandled state. The coresponding
|
||||||
|
`_xtos_exc_handler_table` entry will be set to `_xtos_unhandled_exception`.
|
||||||
|
Note, if nesting handlers is desired this must be implemented in the new "C"
|
||||||
|
exception handler(s) being registered.
|
||||||
|
*/
|
||||||
|
extern void _xtos_p_none(struct __exception_frame *ef, int cause);
|
||||||
|
|
||||||
|
/*
|
||||||
|
TMI maybe?
|
||||||
|
For `extern _xtos_handler _xtos_c_handler_table[XCHAL_EXCCAUSE_NUM];`, defined
|
||||||
|
in in `xtensa/xtos/exc-sethandler.c`. _xtos_handler is a generalized
|
||||||
|
definition that doesn't match the actual function definition of those
|
||||||
|
assigned to `_xtos_c_handler_table` entries.
|
||||||
|
|
||||||
|
At this time we do not require direct access to this table. We perform updates
|
||||||
|
by calling the ROM function `_xtos_set_exception_handler`.
|
||||||
|
|
||||||
|
A corrected version for .cpp would look like this:
|
||||||
|
*/
|
||||||
|
extern fn_c_exception_handler_t _xtos_c_handler_table[XCHAL_EXCCAUSE_NUM];
|
||||||
|
|
||||||
|
/*
|
||||||
|
ROM API function `_xtos_set_exception_handler` registers a "C" callable
|
||||||
|
exception handler for a specified general exception, (EXCCAUSE value). (source
|
||||||
|
in xtensa/xtos/exc-sethandler.c)
|
||||||
|
* If `cause`/reason (EXCCAUSE) is out of range, >=64, it returns NULL.
|
||||||
|
* If the new exception handler is installed, it returns the previous handler.
|
||||||
|
* If the previous handler was `_xtos_unhandled_exception`/`_xtos_p_none`, it
|
||||||
|
returns NULL.
|
||||||
|
|
||||||
|
Note, the installed "C" exception handler is noramlly called from the ROM
|
||||||
|
function _xtos_c_wrapper_handler with IRQs enabled. This build now includes a
|
||||||
|
replacement wrapper that is used with the "C" exception handler for
|
||||||
|
EXCCAUSE_LOAD_STORE_ERROR (3), Non 32-bit read/write error.
|
||||||
|
|
||||||
|
This prototype has been corrected (changed from a generalized to specific
|
||||||
|
argument list) for the .cpp files in this projects; however, it does not match
|
||||||
|
the over generalized version in some Xtensa .h files (not currently part of
|
||||||
|
this project)
|
||||||
|
|
||||||
|
To aid against future conflicts, keep these new defines limited to .cpp with
|
||||||
|
`#ifdef __cplusplus`.
|
||||||
|
*/
|
||||||
|
extern fn_c_exception_handler_t _xtos_set_exception_handler(int cause, fn_c_exception_handler_t fn);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern uint32_t Wait_SPI_Idle(SpiFlashChip *fc);
|
||||||
|
extern void Cache_Read_Disable();
|
||||||
|
extern int32_t system_func1(uint32_t);
|
||||||
|
extern void clockgate_watchdog(uint32_t);
|
||||||
|
extern void pm_open_rf();
|
||||||
|
extern void ets_install_uart_printf(uint32_t uart_no);
|
||||||
|
extern void UartDwnLdProc(uint8_t* ram_addr, uint32_t size, void (**user_start_ptr)());
|
||||||
|
extern int boot_from_flash();
|
||||||
|
extern void ets_run() __attribute__((noreturn));
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(VERIFY_C_ASM_EXCEPTION_FRAME_STRUCTURE) || defined(_ASMLANGUAGE) || defined(__ASSEMBLER__)
|
||||||
|
/*
|
||||||
|
Extracted from information at
|
||||||
|
From https://github.com/fdivitto/ESPWebFramework/blob/master/SDK/xtensa-lx106-elf/xtensa-lx106-elf/lib/libhandlers-null.txt
|
||||||
|
|
||||||
|
The UEXC_... values are create by the macro STRUCT_FIELD in `xtruntime-frames.h`
|
||||||
|
|
||||||
|
These VERIFY_... values are used to confirm that the "C" structure offsets
|
||||||
|
match those generated in exc-c-wrapper-handler.S.
|
||||||
|
*/
|
||||||
|
#define VERIFY_UEXC_pc 0x0000
|
||||||
|
#define VERIFY_UEXC_ps 0x0004
|
||||||
|
#define VERIFY_UEXC_sar 0x0008
|
||||||
|
#define VERIFY_UEXC_vpri 0x000c
|
||||||
|
#define VERIFY_UEXC_a0 0x0010
|
||||||
|
#define VERIFY_UEXC_a2 0x0014
|
||||||
|
#define VERIFY_UEXC_a3 0x0018
|
||||||
|
#define VERIFY_UEXC_a4 0x001c
|
||||||
|
#define VERIFY_UEXC_a5 0x0020
|
||||||
|
#define VERIFY_UEXC_a6 0x0024
|
||||||
|
#define VERIFY_UEXC_a7 0x0028
|
||||||
|
#define VERIFY_UEXC_a8 0x002c
|
||||||
|
#define VERIFY_UEXC_a9 0x0030
|
||||||
|
#define VERIFY_UEXC_a10 0x0034
|
||||||
|
#define VERIFY_UEXC_a11 0x0038
|
||||||
|
#define VERIFY_UEXC_a12 0x003c
|
||||||
|
#define VERIFY_UEXC_a13 0x0040
|
||||||
|
#define VERIFY_UEXC_a14 0x0044
|
||||||
|
#define VERIFY_UEXC_a15 0x0048
|
||||||
|
#define VERIFY_UEXC_exccause 0x004c
|
||||||
|
#define VERIFY_UserFrameSize 0x0050
|
||||||
|
#define VERIFY_UserFrameTotalSize 0x0100
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(VERIFY_C_ASM_EXCEPTION_FRAME_STRUCTURE) && !(defined(_ASMLANGUAGE) || defined(__ASSEMBLER__))
|
||||||
|
/*
|
||||||
|
A set of static_asserts test to confirm both "C" and ASM structures match.
|
||||||
|
|
||||||
|
This only needs to be verified once.
|
||||||
|
We use `#define VERIFY_C_ASM_EXCEPTION_FRAME_STRUCTURE` to limit number of
|
||||||
|
times tested in a build. Testing is done from core_esp8266_non32xfer.cpp.
|
||||||
|
|
||||||
|
ASM structure defines are verified in exc-c-wrapper-handler.S
|
||||||
|
*/
|
||||||
|
static_assert(offsetof(struct __exception_frame, epc) == VERIFY_UEXC_pc, "offsetof(struct __exception_frame, epc) != VERIFY_UEXC_pc, expected 0x0000");
|
||||||
|
static_assert(offsetof(struct __exception_frame, ps) == VERIFY_UEXC_ps, "offsetof(struct __exception_frame, ps) != VERIFY_UEXC_ps, expected 0x0004");
|
||||||
|
static_assert(offsetof(struct __exception_frame, sar) == VERIFY_UEXC_sar, "offsetof(struct __exception_frame, sar) != VERIFY_UEXC_sar, expected 0x0008");
|
||||||
|
static_assert(offsetof(struct __exception_frame, unused) == VERIFY_UEXC_vpri, "offsetof(struct __exception_frame, unused) != VERIFY_UEXC_vpri, expected 0x000c");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a0) == VERIFY_UEXC_a0, "offsetof(struct __exception_frame, a0) != VERIFY_UEXC_a0, expected 0x0010");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a2) == VERIFY_UEXC_a2, "offsetof(struct __exception_frame, a2) != VERIFY_UEXC_a2, expected 0x0014");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a3) == VERIFY_UEXC_a3, "offsetof(struct __exception_frame, a3) != VERIFY_UEXC_a3, expected 0x0018");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a4) == VERIFY_UEXC_a4, "offsetof(struct __exception_frame, a4) != VERIFY_UEXC_a4, expected 0x001c");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a5) == VERIFY_UEXC_a5, "offsetof(struct __exception_frame, a5) != VERIFY_UEXC_a5, expected 0x0020");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a6) == VERIFY_UEXC_a6, "offsetof(struct __exception_frame, a6) != VERIFY_UEXC_a6, expected 0x0024");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a7) == VERIFY_UEXC_a7, "offsetof(struct __exception_frame, a7) != VERIFY_UEXC_a7, expected 0x0028");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a8) == VERIFY_UEXC_a8, "offsetof(struct __exception_frame, a8) != VERIFY_UEXC_a8, expected 0x002c");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a9) == VERIFY_UEXC_a9, "offsetof(struct __exception_frame, a9) != VERIFY_UEXC_a9, expected 0x0030");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a10) == VERIFY_UEXC_a10, "offsetof(struct __exception_frame, a10) != VERIFY_UEXC_a10, expected 0x0034");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a11) == VERIFY_UEXC_a11, "offsetof(struct __exception_frame, a11) != VERIFY_UEXC_a11, expected 0x0038");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a12) == VERIFY_UEXC_a12, "offsetof(struct __exception_frame, a12) != VERIFY_UEXC_a12, expected 0x003c");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a13) == VERIFY_UEXC_a13, "offsetof(struct __exception_frame, a13) != VERIFY_UEXC_a13, expected 0x0040");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a14) == VERIFY_UEXC_a14, "offsetof(struct __exception_frame, a14) != VERIFY_UEXC_a14, expected 0x0044");
|
||||||
|
static_assert(offsetof(struct __exception_frame, a15) == VERIFY_UEXC_a15, "offsetof(struct __exception_frame, a15) != VERIFY_UEXC_a15, expected 0x0048");
|
||||||
|
static_assert(offsetof(struct __exception_frame, cause) == VERIFY_UEXC_exccause, "offsetof(struct __exception_frame, cause) != VERIFY_UEXC_exccause, expected 0x004c");
|
||||||
|
#endif
|
||||||
|
213
cores/esp8266/exc-c-wrapper-handler.S
Normal file
213
cores/esp8266/exc-c-wrapper-handler.S
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
// exc-c-wrapper-handler.S, this is a reduced version of the original file at
|
||||||
|
// https://github.com/qca/open-ath9k-htc-firmware/blob/master/sboot/magpie_1_1/sboot/athos/src/xtos/exc-c-wrapper-handler.S#L62-L67
|
||||||
|
//
|
||||||
|
|
||||||
|
// exc-c-wrapper-handler.S - General Exception Handler that Dispatches C Handlers
|
||||||
|
|
||||||
|
// Copyright (c) 2002-2004, 2006-2007, 2010 Tensilica Inc.
|
||||||
|
//
|
||||||
|
// 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 <xtensa/coreasm.h>
|
||||||
|
#include <xtensa/corebits.h>
|
||||||
|
#include <xtensa/config/specreg.h>
|
||||||
|
// #include "xtos-internal.h"
|
||||||
|
// #ifdef SIMULATOR
|
||||||
|
// #include <xtensa/simcall.h>
|
||||||
|
// #endif
|
||||||
|
|
||||||
|
#include "xtruntime-frames.h"
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Verified that the ASM generated UEXC_xxx values match, the corresponding
|
||||||
|
// values in `struct __exception_frame` used in the "C" code.
|
||||||
|
//
|
||||||
|
#include "esp8266_undocumented.h"
|
||||||
|
.if (UEXC_pc != VERIFY_UEXC_pc)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_ps != VERIFY_UEXC_ps)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_sar != VERIFY_UEXC_sar)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_vpri != VERIFY_UEXC_vpri)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a0 != VERIFY_UEXC_a0)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a2 != VERIFY_UEXC_a2)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a3 != VERIFY_UEXC_a3)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a4 != VERIFY_UEXC_a4)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a5 != VERIFY_UEXC_a5)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a6 != VERIFY_UEXC_a6)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a7 != VERIFY_UEXC_a7)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a8 != VERIFY_UEXC_a8)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a9 != VERIFY_UEXC_a9)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a10 != VERIFY_UEXC_a10)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a11 != VERIFY_UEXC_a11)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a12 != VERIFY_UEXC_a12)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a13 != VERIFY_UEXC_a13)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a14 != VERIFY_UEXC_a14)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_a15 != VERIFY_UEXC_a15)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UEXC_exccause != VERIFY_UEXC_exccause)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UserFrameSize != VERIFY_UserFrameSize)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
.if (UserFrameTotalSize != VERIFY_UserFrameTotalSize)
|
||||||
|
.err
|
||||||
|
.endif
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the general exception assembly-level handler that dispatches C handlers.
|
||||||
|
*/
|
||||||
|
.section .iram.text
|
||||||
|
.align 4
|
||||||
|
.literal_position
|
||||||
|
.global _xtos_c_wrapper_handler
|
||||||
|
_xtos_c_wrapper_handler:
|
||||||
|
|
||||||
|
// HERE: a2, a3, a4 have been saved to exception stack frame allocated with a1 (sp).
|
||||||
|
// a2 contains EXCCAUSE.
|
||||||
|
s32i a5, a1, UEXC_a5 // a5 will get clobbered by ENTRY after the pseudo-CALL4
|
||||||
|
// (a4..a15 spilled as needed; save if modified)
|
||||||
|
|
||||||
|
//NOTA: Possible future improvement:
|
||||||
|
// keep interrupts disabled until we get into the handler, such that
|
||||||
|
// we don't have to save other critical state such as EXCVADDR here.
|
||||||
|
// @mhightower83 - This promise was broken by an "rsil a13, 0" below.
|
||||||
|
//rsr a3, EXCVADDR
|
||||||
|
s32i a2, a1, UEXC_exccause
|
||||||
|
//s32i a3, a1, UEXC_excvaddr
|
||||||
|
|
||||||
|
// Set PS fields:
|
||||||
|
// EXCM = 0
|
||||||
|
// WOE = __XTENSA_CALL0_ABI__ ? 0 : 1
|
||||||
|
// UM = 1
|
||||||
|
// INTLEVEL = EXCM_LEVEL = 1
|
||||||
|
// CALLINC = __XTENSA_CALL0_ABI__ ? 0 : 1
|
||||||
|
// OWB = 0 (really, a dont care if !__XTENSA_CALL0_ABI__)
|
||||||
|
|
||||||
|
// movi a2, 0x23 // 0x21, PS_UM|PS_INTLEVEL(XCHAL_EXCM_LEVEL)
|
||||||
|
// @mhightower83 - use INTLEVEL 15 instead of 3 for Arduino like interrupt support??
|
||||||
|
movi a2, 0x2F // 0x21, PS_UM|PS_INTLEVEL(15)
|
||||||
|
rsr a3, EPC_1
|
||||||
|
// @mhightower83 - I assume PS.EXCM was set and now is being cleared, thus
|
||||||
|
// allowing new exceptions and interrupts within PS_INTLEVEL to be possible.
|
||||||
|
// We have set INTLEVEL to 15 to block any possible interrupts.
|
||||||
|
xsr a2, PS
|
||||||
|
|
||||||
|
// HERE: window overflows enabled, but NOT SAFE because we're not quite
|
||||||
|
// in a valid windowed context (haven't restored a1 yet...);
|
||||||
|
// so don't cause any (keep to a0..a3) until we've saved critical state and restored a1:
|
||||||
|
|
||||||
|
// NOTE: MUST SAVE EPC1 before causing any overflows, because overflows corrupt EPC1.
|
||||||
|
s32i a3, a1, UEXC_pc
|
||||||
|
s32i a2, a1, UEXC_ps
|
||||||
|
s32i a0, a1, UEXC_a0 // save the rest of the registers
|
||||||
|
s32i a6, a1, UEXC_a6
|
||||||
|
s32i a7, a1, UEXC_a7
|
||||||
|
s32i a8, a1, UEXC_a8
|
||||||
|
s32i a9, a1, UEXC_a9
|
||||||
|
s32i a10, a1, UEXC_a10
|
||||||
|
s32i a11, a1, UEXC_a11
|
||||||
|
s32i a12, a1, UEXC_a12
|
||||||
|
s32i a13, a1, UEXC_a13
|
||||||
|
s32i a14, a1, UEXC_a14
|
||||||
|
s32i a15, a1, UEXC_a15
|
||||||
|
rsync // wait for WSR to PS to complete
|
||||||
|
rsr a12, SAR
|
||||||
|
|
||||||
|
// @mhightower83 - I think, after the next instruction, we have the potential of
|
||||||
|
// losing UEXC_excvaddr. Which the earlier comment said we need to preserve for
|
||||||
|
// the exception handler. We keep interrupts off when calling the "C" exception
|
||||||
|
// handler. For the use cases that I am looking at, this is a must. If there are
|
||||||
|
// future use cases that need interrupts enabled, those "C" exception handlers
|
||||||
|
// can turn them on.
|
||||||
|
//
|
||||||
|
// rsil a13, 0
|
||||||
|
|
||||||
|
movi a13, _xtos_c_handler_table // &table
|
||||||
|
l32i a15, a1, UEXC_exccause // arg2: exccause
|
||||||
|
s32i a12, a1, UEXC_sar
|
||||||
|
addx4 a12, a15, a13 // a12 = table[exccause]
|
||||||
|
l32i a12, a12, 0 // ...
|
||||||
|
mov a2, a1 // arg1: exception parameters
|
||||||
|
mov a3, a15 // arg2: exccause
|
||||||
|
beqz a12, 1f // null handler => skip call
|
||||||
|
callx0 a12 // call C exception handler for this exception
|
||||||
|
1:
|
||||||
|
// Now exit the handler.
|
||||||
|
|
||||||
|
// Restore special registers
|
||||||
|
l32i a14, a1, UEXC_sar
|
||||||
|
|
||||||
|
// load early - saves two cycles - @mhightower83
|
||||||
|
movi a0, _xtos_return_from_exc
|
||||||
|
|
||||||
|
// @mhightower83 - For compatibility with Arduino interrupt architecture, we
|
||||||
|
// keep interrupts 100% disabled.
|
||||||
|
// /*
|
||||||
|
// * Disable interrupts while returning from the pseudo-CALL setup above,
|
||||||
|
// * for the same reason they were disabled while doing the pseudo-CALL:
|
||||||
|
// * this sequence restores SP such that it doesn't reflect the allocation
|
||||||
|
// * of the exception stack frame, which we still need to return from
|
||||||
|
// * the exception.
|
||||||
|
// */
|
||||||
|
// rsil a12, 1 // XCHAL_EXCM_LEVEL
|
||||||
|
rsil a12, 15 // All levels blocked.
|
||||||
|
wsr a14, SAR
|
||||||
|
jx a0
|
||||||
|
|
||||||
|
/* FIXME: what about _GeneralException ? */
|
||||||
|
.size _xtos_c_wrapper_handler, . - _xtos_c_wrapper_handler
|
113
cores/esp8266/exc-sethandler.cpp
Normal file
113
cores/esp8266/exc-sethandler.cpp
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Adaptation of _xtos_set_exception_handler for Arduino ESP8266 core
|
||||||
|
*
|
||||||
|
* This replacement for the Boot ROM `_xtos_set_exception_handler` is used to
|
||||||
|
* install our replacement `_xtos_c_wrapper_handler`. This change protects the
|
||||||
|
* value of `excvaddr` from corruption.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Details
|
||||||
|
*
|
||||||
|
* The issue, the Boot ROM "C" wrapper for exception handlers,
|
||||||
|
* `_xtos_c_wrapper_handler`, turns interrupts back on. This leaves `excvaddr`
|
||||||
|
* exposed to possible overwrite before it is read. For example, if an interrupt
|
||||||
|
* is taken during the exception handler processing and the ISR handler
|
||||||
|
* generates a new exception, the original value of `excvaddr` is lost. To
|
||||||
|
* address this issue we have a replacement `_xtos_c_wrapper_handler` in file
|
||||||
|
* `exc-c-wrapper-handler.S`.
|
||||||
|
*
|
||||||
|
* An overview, of an exception at entry: New interrupts are blocked by EXCM
|
||||||
|
* being set. Once cleared, interrupts above the current INTLEVEL and exceptions
|
||||||
|
* (w/o creating a DoubleException) can occur.
|
||||||
|
*
|
||||||
|
* Using our replacement for `_xtos_c_wrapper_handler`, INTLEVEL is raised to 15
|
||||||
|
* with EXCM cleared.
|
||||||
|
*
|
||||||
|
* The original Boot ROM `_xtos_c_wrapper_handler` at entry would set INTLEVEL
|
||||||
|
* to 3 with EXCM cleared, save registers, then do a `rsil 0` (interrupts fully
|
||||||
|
* enabled!) just before calling the registered "C" Exception handler. Our
|
||||||
|
* replacement keeps INTLEVEL at 15. This is needed to support the Arduino model
|
||||||
|
* of interrupts disabled while an ISR runs.
|
||||||
|
*
|
||||||
|
* And we also need it for umm_malloc to work safely with an IRAM heap from an
|
||||||
|
* ISR call. While malloc() will supply DRAM for all allocation from an ISR, we
|
||||||
|
* want free() to safely operate from an ISR to avoid a leak potential.
|
||||||
|
*
|
||||||
|
* If an exception handler needs interrupts enabled, it would be done after it
|
||||||
|
* has consumed the value of `excvaddr`. Whether such action is safe is left to
|
||||||
|
* the exception handler writer to determine. However, with our current
|
||||||
|
* architecture, I am not convinced it can be done safely.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(NON32XFER_HANDLER) || defined(MMU_IRAM_HEAP) || defined(NEW_EXC_C_WRAPPER)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The original module source code came from:
|
||||||
|
* https://github.com/qca/open-ath9k-htc-firmware/blob/master/sboot/magpie_1_1/sboot/athos/src/xtos/exc-sethandler.c
|
||||||
|
*
|
||||||
|
* It has been revised to use Arduino ESP8266 core includes, types, and
|
||||||
|
* formating.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* exc-sethandler.c - register an exception handler in XTOS */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) 1999-2006 Tensilica Inc.
|
||||||
|
*
|
||||||
|
* 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 <Arduino.h>
|
||||||
|
#include "esp8266_undocumented.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Register a C handler for the specified general exception
|
||||||
|
* (specified EXCCAUSE value).
|
||||||
|
*/
|
||||||
|
fn_c_exception_handler_t _xtos_set_exception_handler(int cause, fn_c_exception_handler_t fn)
|
||||||
|
{
|
||||||
|
fn_c_exception_handler_t ret;
|
||||||
|
|
||||||
|
if( (unsigned) cause >= XCHAL_EXCCAUSE_NUM )
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if( fn == 0 )
|
||||||
|
fn = &_xtos_p_none;
|
||||||
|
|
||||||
|
ret = _xtos_c_handler_table[cause];
|
||||||
|
|
||||||
|
_xtos_exc_handler_table[cause] = ( (fn == &_xtos_p_none)
|
||||||
|
? &_xtos_unhandled_exception
|
||||||
|
: &_xtos_c_wrapper_handler );
|
||||||
|
|
||||||
|
_xtos_c_handler_table[cause] = fn;
|
||||||
|
|
||||||
|
if( ret == &_xtos_p_none )
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -28,141 +28,27 @@ extern "C" {
|
|||||||
#include "c_types.h"
|
#include "c_types.h"
|
||||||
#include "spi_flash.h"
|
#include "spi_flash.h"
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
spi_flash_read function requires flash address to be aligned on word boundary.
|
|
||||||
We take care of this by reading first and last words separately and memcpy
|
|
||||||
relevant bytes into result buffer.
|
|
||||||
|
|
||||||
alignment: 012301230123012301230123
|
|
||||||
bytes requested: -------***********------
|
|
||||||
read directly: --------xxxxxxxx--------
|
|
||||||
read pre: ----aaaa----------------
|
|
||||||
read post: ----------------bbbb----
|
|
||||||
alignedBegin: ^
|
|
||||||
alignedEnd: ^
|
|
||||||
*/
|
|
||||||
|
|
||||||
int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
|
int32_t flash_hal_read(uint32_t addr, uint32_t size, uint8_t *dst) {
|
||||||
optimistic_yield(10000);
|
optimistic_yield(10000);
|
||||||
|
|
||||||
uint32_t result = FLASH_HAL_OK;
|
// We use flashRead overload that handles proper alignment
|
||||||
uint32_t alignedBegin = (addr + 3) & (~3);
|
if (ESP.flashRead(addr, dst, size)) {
|
||||||
uint32_t alignedEnd = (addr + size) & (~3);
|
return FLASH_HAL_OK;
|
||||||
if (alignedEnd < alignedBegin) {
|
} else {
|
||||||
alignedEnd = alignedBegin;
|
return FLASH_HAL_READ_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addr < alignedBegin) {
|
|
||||||
uint32_t nb = alignedBegin - addr;
|
|
||||||
uint32_t tmp;
|
|
||||||
if (!ESP.flashRead(alignedBegin - 4, &tmp, 4)) {
|
|
||||||
DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
|
|
||||||
__LINE__, addr, size, alignedBegin, alignedEnd);
|
|
||||||
return FLASH_HAL_READ_ERROR;
|
|
||||||
}
|
|
||||||
memcpy(dst, ((uint8_t*) &tmp) + 4 - nb, nb);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alignedEnd != alignedBegin) {
|
|
||||||
if (!ESP.flashRead(alignedBegin, (uint32_t*) (dst + alignedBegin - addr),
|
|
||||||
alignedEnd - alignedBegin)) {
|
|
||||||
DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
|
|
||||||
__LINE__, addr, size, alignedBegin, alignedEnd);
|
|
||||||
return FLASH_HAL_READ_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addr + size > alignedEnd) {
|
|
||||||
uint32_t nb = addr + size - alignedEnd;
|
|
||||||
uint32_t tmp;
|
|
||||||
if (!ESP.flashRead(alignedEnd, &tmp, 4)) {
|
|
||||||
DEBUGV("_spif_read(%d) addr=%x size=%x ab=%x ae=%x\r\n",
|
|
||||||
__LINE__, addr, size, alignedBegin, alignedEnd);
|
|
||||||
return FLASH_HAL_READ_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(dst + size - nb, &tmp, nb);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Like spi_flash_read, spi_flash_write has a requirement for flash address to be
|
|
||||||
aligned. However it also requires RAM address to be aligned as it reads data
|
|
||||||
in 32-bit words. Flash address (mis-)alignment is handled much the same way
|
|
||||||
as for reads, but for RAM alignment we have to copy data into a temporary
|
|
||||||
buffer. The size of this buffer is a tradeoff between number of writes required
|
|
||||||
and amount of stack required. This is chosen to be 512 bytes here, but might
|
|
||||||
be adjusted in the future if there are good reasons to do so.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static const int UNALIGNED_WRITE_BUFFER_SIZE = 512;
|
|
||||||
|
|
||||||
int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src) {
|
int32_t flash_hal_write(uint32_t addr, uint32_t size, const uint8_t *src) {
|
||||||
optimistic_yield(10000);
|
optimistic_yield(10000);
|
||||||
|
|
||||||
uint32_t alignedBegin = (addr + 3) & (~3);
|
// We use flashWrite overload that handles proper alignment
|
||||||
uint32_t alignedEnd = (addr + size) & (~3);
|
if (ESP.flashWrite(addr, src, size)) {
|
||||||
if (alignedEnd < alignedBegin) {
|
return FLASH_HAL_OK;
|
||||||
alignedEnd = alignedBegin;
|
} else {
|
||||||
|
return FLASH_HAL_WRITE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addr < alignedBegin) {
|
|
||||||
uint32_t ofs = alignedBegin - addr;
|
|
||||||
uint32_t nb = (size < ofs) ? size : ofs;
|
|
||||||
uint8_t tmp[4] __attribute__((aligned(4))) = {0xff, 0xff, 0xff, 0xff};
|
|
||||||
memcpy(tmp + 4 - ofs, src, nb);
|
|
||||||
if (!ESP.flashWrite(alignedBegin - 4, (uint32_t*) tmp, 4)) {
|
|
||||||
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
|
|
||||||
__LINE__, addr, size, alignedBegin, alignedEnd);
|
|
||||||
return FLASH_HAL_WRITE_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alignedEnd != alignedBegin) {
|
|
||||||
uint32_t* srcLeftover = (uint32_t*) (src + alignedBegin - addr);
|
|
||||||
uint32_t srcAlign = ((uint32_t) srcLeftover) & 3;
|
|
||||||
if (!srcAlign) {
|
|
||||||
if (!ESP.flashWrite(alignedBegin, (uint32_t*) srcLeftover,
|
|
||||||
alignedEnd - alignedBegin)) {
|
|
||||||
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
|
|
||||||
__LINE__, addr, size, alignedBegin, alignedEnd);
|
|
||||||
return FLASH_HAL_WRITE_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
uint8_t buf[UNALIGNED_WRITE_BUFFER_SIZE];
|
|
||||||
for (uint32_t sizeLeft = alignedEnd - alignedBegin; sizeLeft; ) {
|
|
||||||
size_t willCopy = std::min(sizeLeft, sizeof(buf));
|
|
||||||
memcpy(buf, srcLeftover, willCopy);
|
|
||||||
|
|
||||||
if (!ESP.flashWrite(alignedBegin, (uint32_t*) buf, willCopy)) {
|
|
||||||
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
|
|
||||||
__LINE__, addr, size, alignedBegin, alignedEnd);
|
|
||||||
return FLASH_HAL_WRITE_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
sizeLeft -= willCopy;
|
|
||||||
srcLeftover += willCopy;
|
|
||||||
alignedBegin += willCopy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addr + size > alignedEnd) {
|
|
||||||
uint32_t nb = addr + size - alignedEnd;
|
|
||||||
uint32_t tmp = 0xffffffff;
|
|
||||||
memcpy(&tmp, src + size - nb, nb);
|
|
||||||
|
|
||||||
if (!ESP.flashWrite(alignedEnd, &tmp, 4)) {
|
|
||||||
DEBUGV("_spif_write(%d) addr=%x size=%x ab=%x ae=%x\r\n",
|
|
||||||
__LINE__, addr, size, alignedBegin, alignedEnd);
|
|
||||||
return FLASH_HAL_WRITE_ERROR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FLASH_HAL_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t flash_hal_erase(uint32_t addr, uint32_t size) {
|
int32_t flash_hal_erase(uint32_t addr, uint32_t size) {
|
||||||
|
@ -31,6 +31,11 @@ static bool ICACHE_RAM_ATTR __gdb_no_op()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To save space, don't create a dummy no-op for each GCC, just point to the no-op
|
||||||
|
// Need to turn off GCC's checking of parameter types or we'll get many warnings
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wattribute-alias"
|
||||||
|
#pragma GCC diagnostic ignored "-Wmissing-attributes"
|
||||||
void gdb_init(void) __attribute__ ((weak, alias("__gdb_no_op")));
|
void gdb_init(void) __attribute__ ((weak, alias("__gdb_no_op")));
|
||||||
void gdb_do_break(void) __attribute__ ((weak, alias("__gdb_no_op")));
|
void gdb_do_break(void) __attribute__ ((weak, alias("__gdb_no_op")));
|
||||||
bool gdb_present(void) __attribute__ ((weak, alias("__gdb_no_op")));
|
bool gdb_present(void) __attribute__ ((weak, alias("__gdb_no_op")));
|
||||||
@ -40,5 +45,6 @@ bool gdbstub_has_uart_isr_control(void) __attribute__ ((weak, alias("__gdb_no_op
|
|||||||
void gdbstub_set_uart_isr_callback(void (*func)(void*, uint8_t), void* arg) __attribute__ ((weak, alias("__gdb_no_op")));
|
void gdbstub_set_uart_isr_callback(void (*func)(void*, uint8_t), void* arg) __attribute__ ((weak, alias("__gdb_no_op")));
|
||||||
void gdbstub_write_char(char c) __attribute__ ((weak, alias("__gdb_no_op")));
|
void gdbstub_write_char(char c) __attribute__ ((weak, alias("__gdb_no_op")));
|
||||||
void gdbstub_write(const char* buf, size_t size) __attribute__ ((weak, alias("__gdb_no_op")));
|
void gdbstub_write(const char* buf, size_t size) __attribute__ ((weak, alias("__gdb_no_op")));
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,11 @@
|
|||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "umm_malloc/umm_malloc.h"
|
#include "umm_malloc/umm_malloc.h"
|
||||||
|
|
||||||
|
// Need FORCE_ALWAYS_INLINE to put HeapSelect class constructor/deconstructor in IRAM
|
||||||
|
#define FORCE_ALWAYS_INLINE_HEAP_SELECT
|
||||||
|
#include "umm_malloc/umm_heap_select.h"
|
||||||
|
|
||||||
#include <c_types.h>
|
#include <c_types.h>
|
||||||
#include <sys/reent.h>
|
#include <sys/reent.h>
|
||||||
#include <user_interface.h>
|
#include <user_interface.h>
|
||||||
@ -16,15 +21,17 @@ extern "C" {
|
|||||||
#define UMM_CALLOC(n,s) umm_poison_calloc(n,s)
|
#define UMM_CALLOC(n,s) umm_poison_calloc(n,s)
|
||||||
#define UMM_REALLOC_FL(p,s,f,l) umm_poison_realloc_fl(p,s,f,l)
|
#define UMM_REALLOC_FL(p,s,f,l) umm_poison_realloc_fl(p,s,f,l)
|
||||||
#define UMM_FREE_FL(p,f,l) umm_poison_free_fl(p,f,l)
|
#define UMM_FREE_FL(p,f,l) umm_poison_free_fl(p,f,l)
|
||||||
|
#define STATIC_ALWAYS_INLINE
|
||||||
|
|
||||||
#undef realloc
|
#undef realloc
|
||||||
#undef free
|
#undef free
|
||||||
|
|
||||||
#elif defined(DEBUG_ESP_OOM)
|
#elif defined(DEBUG_ESP_OOM) || defined(UMM_INTEGRITY_CHECK)
|
||||||
#define UMM_MALLOC(s) umm_malloc(s)
|
#define UMM_MALLOC(s) umm_malloc(s)
|
||||||
#define UMM_CALLOC(n,s) umm_calloc(n,s)
|
#define UMM_CALLOC(n,s) umm_calloc(n,s)
|
||||||
#define UMM_REALLOC_FL(p,s,f,l) umm_realloc(p,s)
|
#define UMM_REALLOC_FL(p,s,f,l) umm_realloc(p,s)
|
||||||
#define UMM_FREE_FL(p,f,l) umm_free(p)
|
#define UMM_FREE_FL(p,f,l) umm_free(p)
|
||||||
|
#define STATIC_ALWAYS_INLINE
|
||||||
|
|
||||||
#undef realloc
|
#undef realloc
|
||||||
#undef free
|
#undef free
|
||||||
@ -34,6 +41,10 @@ extern "C" {
|
|||||||
#define UMM_CALLOC(n,s) calloc(n,s)
|
#define UMM_CALLOC(n,s) calloc(n,s)
|
||||||
#define UMM_REALLOC_FL(p,s,f,l) realloc(p,s)
|
#define UMM_REALLOC_FL(p,s,f,l) realloc(p,s)
|
||||||
#define UMM_FREE_FL(p,f,l) free(p)
|
#define UMM_FREE_FL(p,f,l) free(p)
|
||||||
|
|
||||||
|
// STATIC_ALWAYS_INLINE only applys to the non-debug build path,
|
||||||
|
// it must not be enabled on the debug build path.
|
||||||
|
#define STATIC_ALWAYS_INLINE static ALWAYS_INLINE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -164,7 +175,7 @@ void ICACHE_RAM_ATTR print_loc(size_t size, const char* file, int line)
|
|||||||
if (inISR && (uint32_t)file >= 0x40200000) {
|
if (inISR && (uint32_t)file >= 0x40200000) {
|
||||||
DEBUG_HEAP_PRINTF("File: %p", file);
|
DEBUG_HEAP_PRINTF("File: %p", file);
|
||||||
} else if (!inISR && (uint32_t)file >= 0x40200000) {
|
} else if (!inISR && (uint32_t)file >= 0x40200000) {
|
||||||
char buf[ets_strlen(file)] __attribute__ ((aligned(4)));
|
char buf[ets_strlen(file) + 1] __attribute__((aligned(4)));
|
||||||
ets_strcpy(buf, file);
|
ets_strcpy(buf, file);
|
||||||
DEBUG_HEAP_PRINTF(buf);
|
DEBUG_HEAP_PRINTF(buf);
|
||||||
} else {
|
} else {
|
||||||
@ -183,8 +194,8 @@ void ICACHE_RAM_ATTR print_oom_size(size_t size)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define OOM_CHECK__PRINT_OOM(p, s) if (!p) print_oom_size(s)
|
#define OOM_CHECK__PRINT_OOM(p, s) if ((s) && !(p)) print_oom_size(s)
|
||||||
#define OOM_CHECK__PRINT_LOC(p, s, f, l) if (!p) print_loc(s, f, l)
|
#define OOM_CHECK__PRINT_LOC(p, s, f, l) if ((s) && !(p)) print_loc(s, f, l)
|
||||||
|
|
||||||
#else // ! DEBUG_ESP_OOM
|
#else // ! DEBUG_ESP_OOM
|
||||||
|
|
||||||
@ -259,8 +270,8 @@ void ICACHE_RAM_ATTR free(void* p)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
STATIC_ALWAYS_INLINE
|
||||||
void* ICACHE_RAM_ATTR pvPortMalloc(size_t size, const char* file, int line)
|
void* ICACHE_RAM_ATTR heap_pvPortMalloc(size_t size, const char* file, int line)
|
||||||
{
|
{
|
||||||
INTEGRITY_CHECK__PANIC_FL(file, line);
|
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||||
POISON_CHECK__PANIC_FL(file, line);
|
POISON_CHECK__PANIC_FL(file, line);
|
||||||
@ -270,7 +281,8 @@ void* ICACHE_RAM_ATTR pvPortMalloc(size_t size, const char* file, int line)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ICACHE_RAM_ATTR pvPortCalloc(size_t count, size_t size, const char* file, int line)
|
STATIC_ALWAYS_INLINE
|
||||||
|
void* ICACHE_RAM_ATTR heap_pvPortCalloc(size_t count, size_t size, const char* file, int line)
|
||||||
{
|
{
|
||||||
INTEGRITY_CHECK__PANIC_FL(file, line);
|
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||||
POISON_CHECK__PANIC_FL(file, line);
|
POISON_CHECK__PANIC_FL(file, line);
|
||||||
@ -280,7 +292,8 @@ void* ICACHE_RAM_ATTR pvPortCalloc(size_t count, size_t size, const char* file,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ICACHE_RAM_ATTR pvPortRealloc(void *ptr, size_t size, const char* file, int line)
|
STATIC_ALWAYS_INLINE
|
||||||
|
void* ICACHE_RAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char* file, int line)
|
||||||
{
|
{
|
||||||
INTEGRITY_CHECK__PANIC_FL(file, line);
|
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||||
void* ret = UMM_REALLOC_FL(ptr, size, file, line);
|
void* ret = UMM_REALLOC_FL(ptr, size, file, line);
|
||||||
@ -290,7 +303,8 @@ void* ICACHE_RAM_ATTR pvPortRealloc(void *ptr, size_t size, const char* file, in
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ICACHE_RAM_ATTR pvPortZalloc(size_t size, const char* file, int line)
|
STATIC_ALWAYS_INLINE
|
||||||
|
void* ICACHE_RAM_ATTR heap_pvPortZalloc(size_t size, const char* file, int line)
|
||||||
{
|
{
|
||||||
INTEGRITY_CHECK__PANIC_FL(file, line);
|
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||||
POISON_CHECK__PANIC_FL(file, line);
|
POISON_CHECK__PANIC_FL(file, line);
|
||||||
@ -300,7 +314,8 @@ void* ICACHE_RAM_ATTR pvPortZalloc(size_t size, const char* file, int line)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR vPortFree(void *ptr, const char* file, int line)
|
STATIC_ALWAYS_INLINE
|
||||||
|
void ICACHE_RAM_ATTR heap_vPortFree(void *ptr, const char* file, int line)
|
||||||
{
|
{
|
||||||
INTEGRITY_CHECK__PANIC_FL(file, line);
|
INTEGRITY_CHECK__PANIC_FL(file, line);
|
||||||
UMM_FREE_FL(ptr, file, line);
|
UMM_FREE_FL(ptr, file, line);
|
||||||
@ -314,7 +329,47 @@ size_t ICACHE_RAM_ATTR xPortWantedSizeAlign(size_t size)
|
|||||||
|
|
||||||
void system_show_malloc(void)
|
void system_show_malloc(void)
|
||||||
{
|
{
|
||||||
|
HeapSelectDram ephemeral;
|
||||||
umm_info(NULL, true);
|
umm_info(NULL, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NONOS SDK and lwIP do not handle IRAM heap well. Since they also use portable
|
||||||
|
malloc calls pvPortMalloc, ... we can leverage that for this solution.
|
||||||
|
Force pvPortMalloc, ... APIs to serve DRAM only.
|
||||||
|
*/
|
||||||
|
void* ICACHE_RAM_ATTR pvPortMalloc(size_t size, const char* file, int line)
|
||||||
|
{
|
||||||
|
HeapSelectDram ephemeral;
|
||||||
|
return heap_pvPortMalloc(size, file, line);;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ICACHE_RAM_ATTR pvPortCalloc(size_t count, size_t size, const char* file, int line)
|
||||||
|
{
|
||||||
|
HeapSelectDram ephemeral;
|
||||||
|
return heap_pvPortCalloc(count, size, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ICACHE_RAM_ATTR pvPortRealloc(void *ptr, size_t size, const char* file, int line)
|
||||||
|
{
|
||||||
|
HeapSelectDram ephemeral;
|
||||||
|
return heap_pvPortRealloc(ptr, size, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ICACHE_RAM_ATTR pvPortZalloc(size_t size, const char* file, int line)
|
||||||
|
{
|
||||||
|
HeapSelectDram ephemeral;
|
||||||
|
return heap_pvPortZalloc(size, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICACHE_RAM_ATTR vPortFree(void *ptr, const char* file, int line)
|
||||||
|
{
|
||||||
|
#if defined(DEBUG_ESP_OOM) || defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE) || defined(UMM_INTEGRITY_CHECK)
|
||||||
|
// This is only needed for debug checks to ensure they are performed in
|
||||||
|
// correct context. umm_malloc free internally determines the correct heap.
|
||||||
|
HeapSelectDram ephemeral;
|
||||||
|
#endif
|
||||||
|
return heap_vPortFree(ptr, file, line);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -1,73 +1,12 @@
|
|||||||
/*
|
// This include file is a hack to ensure backward compatibility with
|
||||||
i2s.h - Software I2S library for esp8266
|
// pre 3.0.0 versions of the core. There was a *lowercase* "i2s.h"
|
||||||
|
// header which was in this directory, now renamed to "core_esp82i66s.h"
|
||||||
|
// But, the I2S class has a header, "I2S.h" in uppercase. On Linux
|
||||||
|
// the two names are different, but on Windows it's case-insensitive
|
||||||
|
// so the names conflict.
|
||||||
|
//
|
||||||
|
// Avoid the issue by preserving the old i2s.h file and have it redirect
|
||||||
|
// to I2S.h which will give the ESP8266-specific functions as well as
|
||||||
|
// the generic I2S class.
|
||||||
|
|
||||||
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
#include "../../libraries/I2S/src/I2S.h"
|
||||||
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
|
|
||||||
|
|
||||||
/*
|
|
||||||
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_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
|
|
||||||
|
|
||||||
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
|
|
||||||
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(int16_t *frames, uint16_t frame_count);
|
|
||||||
uint16_t i2s_write_buffer_mono_nb(int16_t *frames, uint16_t frame_count);
|
|
||||||
uint16_t i2s_write_buffer(int16_t *frames, uint16_t frame_count);
|
|
||||||
uint16_t i2s_write_buffer_nb(int16_t *frames, uint16_t frame_count);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
@ -127,6 +127,7 @@ void _exit(int status) {
|
|||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int atexit(void (*func)()) __attribute__((weak));
|
||||||
int atexit(void (*func)()) {
|
int atexit(void (*func)()) {
|
||||||
(void) func;
|
(void) func;
|
||||||
return 0;
|
return 0;
|
||||||
|
192
cores/esp8266/mmu_iram.cpp
Normal file
192
cores/esp8266/mmu_iram.cpp
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 M Hightower
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "mmu_iram.h"
|
||||||
|
#include <user_interface.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
#if (MMU_ICACHE_SIZE == 0x4000)
|
||||||
|
#define SOC_CACHE_SIZE 0 // 16KB
|
||||||
|
#pragma message("ICACHE size 16K")
|
||||||
|
#else
|
||||||
|
#define SOC_CACHE_SIZE 1 // 32KB
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (MMU_ICACHE_SIZE == 0x4000)
|
||||||
|
/*
|
||||||
|
* "Cache_Read_Enable" as in Instruction Read Cache enable, ICACHE.
|
||||||
|
*
|
||||||
|
* The Boot ROM "Cache_Read_Enable" API enables virtual execution of code in
|
||||||
|
* flash memory via an instruction cache, ICACHE. The cache size can be set to
|
||||||
|
* 16K or 32K, and the NONOS SDK 2.x will always set ICACHE to 32K during
|
||||||
|
* initialization.
|
||||||
|
*
|
||||||
|
* When you select a 16K vs. a 32K ICACHE size, you get 48K contiguous IRAM to
|
||||||
|
* work with. The NONOS SDK 2.x does not have an option to select 16K/32K. This
|
||||||
|
* is where this Boot ROM wrapper for Cache_Read_Enable comes in.
|
||||||
|
* Note, there is support for 16K/32K cache size in NONOS SDK 3.0; however, I
|
||||||
|
* do not see an option to have it has part of your general IRAM. That SDK adds
|
||||||
|
* it to the heap.
|
||||||
|
*
|
||||||
|
* With this wrapper function, we override the SDK's ICACHE size.
|
||||||
|
* A build-time define MMU_ICACHE_SIZE selects 16K or 32K ICACHE size.
|
||||||
|
*
|
||||||
|
* mmu_status is used to help understand calling behavior. At some point, it
|
||||||
|
* should be trimmed down to the essentials.
|
||||||
|
*
|
||||||
|
* During NONOS SDK init, it will call to enable. Then call later, to process a
|
||||||
|
* spi_flash_get_id request, it will disable/enable around the Boot ROM SPI calls.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Arguments for Cache_Read_Enable
|
||||||
|
*
|
||||||
|
* The first two arguments appear to specify which 1MB block of the flash to
|
||||||
|
* access with the ICACHE.
|
||||||
|
*
|
||||||
|
* The first argument, map, is partly understood. It has three values 0, 1,
|
||||||
|
* and 2+. The value 0 selects the even 1MB block, and 1 selects the odd 1MB
|
||||||
|
* block, in other words, bit20 of the flash address. No guesses for a value
|
||||||
|
* of 2 or greater.
|
||||||
|
*
|
||||||
|
* The second argument, p, bit 21 of the flash address. Or, it may be bits 23,
|
||||||
|
* 22, 21 of the flash address. A three-bit field is cleared in the register
|
||||||
|
* for this argument; however, I have not seen any examples of it being used
|
||||||
|
* that way.
|
||||||
|
*
|
||||||
|
* The third argument, v, holds our center of attention. A value of 0 selects
|
||||||
|
* 16K, and a value of 1 selects a 32K ICACHE. This is the only parameter we
|
||||||
|
* need to modify on Cache_Read_Enable calls.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Clues and Information sources
|
||||||
|
*
|
||||||
|
* "Cache_Read_Enable" is underdocumented. Main sources of information were from
|
||||||
|
* rboot, zboot, https://richard.burtons.org/2015/06/12/esp8266-cache_read_enable/,
|
||||||
|
* and other places. And some additional expermentation.
|
||||||
|
*
|
||||||
|
* Searching through the NONOS SDK shows nothing on this API; however, some
|
||||||
|
* clues on what the NONOS SDK might be doing with ICACHE related calls can be
|
||||||
|
* found in the RTOS SDK.
|
||||||
|
* eg. ESP8266_RTOS_SDK/blob/master/components/spi_flash/src/spi_flash_raw.c
|
||||||
|
* also calls to it in the bootloader.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ROM_Cache_Read_Enable
|
||||||
|
#define ROM_Cache_Read_Enable 0x40004678U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void (*fp_Cache_Read_Enable_t)(uint8_t map, uint8_t p, uint8_t v);
|
||||||
|
#define real_Cache_Read_Enable (reinterpret_cast<fp_Cache_Read_Enable_t>(ROM_Cache_Read_Enable))
|
||||||
|
|
||||||
|
void IRAM_ATTR Cache_Read_Enable(uint8_t map, uint8_t p, uint8_t v) {
|
||||||
|
(void)v;
|
||||||
|
real_Cache_Read_Enable(map, p, SOC_CACHE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEV_DEBUG_PRINT
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
#ifndef ROM_Cache_Read_Disable
|
||||||
|
#define ROM_Cache_Read_Disable 0x400047f0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void (*fp_Cache_Read_Disable_t)(void);
|
||||||
|
#define real_Cache_Read_Disable (reinterpret_cast<fp_Cache_Read_Disable_t>(ROM_Cache_Read_Disable))
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void IRAM_ATTR Cache_Read_Disable(void) {
|
||||||
|
real_Cache_Read_Disable();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Early adjustment for CPU crystal frequency, so debug printing will work.
|
||||||
|
* This should not be left enabled all the time in Cashe_Read..., I am concerned
|
||||||
|
* that there may be unknown interference with the NONOS SDK startup.
|
||||||
|
*
|
||||||
|
* Inspired by:
|
||||||
|
* https://github.com/pvvx/esp8266web/blob/2e25559bc489487747205db2ef171d48326b32d4/app/sdklib/system/app_main.c#L581-L591
|
||||||
|
*/
|
||||||
|
extern "C" uint8_t rom_i2c_readReg(uint8_t block, uint8_t host_id, uint8_t reg_add);
|
||||||
|
extern "C" void rom_i2c_writeReg(uint8_t block, uint8_t host_id, uint8_t reg_add, uint8_t data);
|
||||||
|
|
||||||
|
extern "C" void IRAM_ATTR set_pll(void)
|
||||||
|
{
|
||||||
|
#if !defined(F_CRYSTAL)
|
||||||
|
#define F_CRYSTAL 26000000
|
||||||
|
#endif
|
||||||
|
if (F_CRYSTAL != 40000000) {
|
||||||
|
// At Boot ROM(-BIOS) start, it assumes a 40MHz crystal.
|
||||||
|
// If it is not, we assume a 26MHz crystal.
|
||||||
|
// There is no support for 24MHz crustal at this time.
|
||||||
|
if(rom_i2c_readReg(103,4,1) != 136) { // 8: 40MHz, 136: 26MHz
|
||||||
|
// Assume 26MHz crystal
|
||||||
|
// soc_param0: 0: 40MHz, 1: 26MHz, 2: 24MHz
|
||||||
|
// set 80MHz PLL CPU
|
||||||
|
rom_i2c_writeReg(103,4,1,136);
|
||||||
|
rom_i2c_writeReg(103,4,2,145);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//C This was used to probe at different stages of boot the state of the PLL
|
||||||
|
//C register. I think we can get rid of this one.
|
||||||
|
extern "C" void IRAM_ATTR dbg_set_pll(void)
|
||||||
|
{
|
||||||
|
char r103_4_1 = rom_i2c_readReg(103,4,1);
|
||||||
|
char r103_4_2 = rom_i2c_readReg(103,4,2);
|
||||||
|
set_pll();
|
||||||
|
ets_uart_printf("\nrom_i2c_readReg(103,4,1) == %u\n", r103_4_1);
|
||||||
|
ets_uart_printf( "rom_i2c_readReg(103,4,2) == %u\n", r103_4_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This helps keep the UART enabled at user_init() so we can get a few more
|
||||||
|
messages printed.
|
||||||
|
*/
|
||||||
|
extern struct rst_info resetInfo;
|
||||||
|
extern "C" void __pinMode( uint8_t pin, uint8_t mode );
|
||||||
|
|
||||||
|
inline bool is_gpio_persistent(void) {
|
||||||
|
return REASON_EXCEPTION_RST <= resetInfo.reason &&
|
||||||
|
REASON_SOFT_RESTART >= resetInfo.reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void pinMode( uint8_t pin, uint8_t mode ) {
|
||||||
|
static bool in_initPins = true;
|
||||||
|
if (in_initPins && (1 == pin)) {
|
||||||
|
if (!is_gpio_persistent()) {
|
||||||
|
/* Restore pin to TX after Power-on and EXT_RST */
|
||||||
|
__pinMode(pin, FUNCTION_0);
|
||||||
|
}
|
||||||
|
in_initPins = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
__pinMode( pin, mode );
|
||||||
|
}
|
||||||
|
#endif // #ifdef DEV_DEBUG_PRINT
|
||||||
|
|
||||||
|
#endif // #if (MMU_ICACHE_SIZE == 0x4000)
|
||||||
|
|
||||||
|
};
|
221
cores/esp8266/mmu_iram.h
Normal file
221
cores/esp8266/mmu_iram.h
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 M Hightower
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __MMU_IRAM_H
|
||||||
|
#define __MMU_IRAM_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <c_types.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <esp8266_undocumented.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//C This turns on range checking. Is this the value you want to trigger it?
|
||||||
|
#ifdef DEBUG_ESP_CORE
|
||||||
|
#define DEBUG_ESP_MMU
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(CORE_MOCK)
|
||||||
|
#define ets_uart_printf(...) do {} while(false)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DEV_DEBUG_PRINT:
|
||||||
|
* Debug printing macros for printing before before, during, and after
|
||||||
|
* NONOS SDK initializes. May or maynot be safe during NONOS SDK
|
||||||
|
* initialization. As in printing from functions called on by the SDK
|
||||||
|
* during the SDK initialization.
|
||||||
|
*
|
||||||
|
#define DEV_DEBUG_PRINT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)
|
||||||
|
#include <esp8266_peri.h>
|
||||||
|
|
||||||
|
#define DBG_MMU_FLUSH(a) while((USS(a) >> USTXC) & 0xff) {}
|
||||||
|
|
||||||
|
#if defined(DEV_DEBUG_PRINT)
|
||||||
|
extern void set_pll(void);
|
||||||
|
extern void dbg_set_pll(void);
|
||||||
|
|
||||||
|
#define DBG_MMU_PRINTF(fmt, ...) \
|
||||||
|
set_pll(); \
|
||||||
|
uart_buff_switch(0); \
|
||||||
|
ets_uart_printf(fmt, ##__VA_ARGS__); \
|
||||||
|
DBG_MMU_FLUSH(0)
|
||||||
|
|
||||||
|
#else // ! defined(DEV_DEBUG_PRINT)
|
||||||
|
#define DBG_MMU_PRINTF(fmt, ...) ets_uart_printf(fmt, ##__VA_ARGS__)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else // ! defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)
|
||||||
|
#define DBG_MMU_FLUSH(...) do {} while(false)
|
||||||
|
#define DBG_MMU_PRINTF(...) do {} while(false)
|
||||||
|
#endif // defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
bool mmu_is_iram(const void *addr) {
|
||||||
|
#define IRAM_START 0x40100000UL
|
||||||
|
#ifndef MMU_IRAM_SIZE
|
||||||
|
#if defined(__GNUC__) && !defined(CORE_MOCK)
|
||||||
|
#warning "MMU_IRAM_SIZE was undefined, setting to 0x8000UL!"
|
||||||
|
#endif
|
||||||
|
#define MMU_IRAM_SIZE 0x8000UL
|
||||||
|
#endif
|
||||||
|
#define IRAM_END (IRAM_START + MMU_IRAM_SIZE)
|
||||||
|
|
||||||
|
return (IRAM_START <= (uintptr_t)addr && IRAM_END > (uintptr_t)addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
bool mmu_is_dram(const void *addr) {
|
||||||
|
#define DRAM_START 0x3FF80000UL
|
||||||
|
#define DRAM_END 0x40000000UL
|
||||||
|
|
||||||
|
return (DRAM_START <= (uintptr_t)addr && DRAM_END > (uintptr_t)addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
bool mmu_is_icache(const void *addr) {
|
||||||
|
#define ICACHE_START 0x40200000UL
|
||||||
|
#define ICACHE_END (ICACHE_START + 0x100000UL)
|
||||||
|
|
||||||
|
return (ICACHE_START <= (uintptr_t)addr && ICACHE_END > (uintptr_t)addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG_ESP_MMU
|
||||||
|
#define ASSERT_RANGE_TEST_WRITE(a) \
|
||||||
|
if (mmu_is_iram(a) || mmu_is_dram(a)) { \
|
||||||
|
} else { \
|
||||||
|
DBG_MMU_PRINTF("\nexcvaddr: %p\n", a); \
|
||||||
|
assert(("Outside of Range - Write" && false)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ASSERT_RANGE_TEST_READ(a) \
|
||||||
|
if (mmu_is_iram(a) || mmu_is_dram(a) || mmu_is_icache(a)) { \
|
||||||
|
} else { \
|
||||||
|
DBG_MMU_PRINTF("\nexcvaddr: %p\n", a); \
|
||||||
|
assert(("Outside of Range - Read" && false)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
#define ASSERT_RANGE_TEST_WRITE(a) do {} while(false)
|
||||||
|
#define ASSERT_RANGE_TEST_READ(a) do {} while(false)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some inlines to allow faster random access to non32bit access of iRAM or
|
||||||
|
* iCACHE data elements. These remove the extra time and stack space that would
|
||||||
|
* have occured by relying on exception processing.
|
||||||
|
*/
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
uint8_t mmu_get_uint8(const void *p8) {
|
||||||
|
ASSERT_RANGE_TEST_READ(p8);
|
||||||
|
uint32_t val = (*(uint32_t *)((uintptr_t)p8 & ~0x3));
|
||||||
|
uint32_t pos = ((uintptr_t)p8 & 0x3) * 8;
|
||||||
|
val >>= pos;
|
||||||
|
return (uint8_t)val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
uint16_t mmu_get_uint16(const uint16_t *p16) {
|
||||||
|
ASSERT_RANGE_TEST_READ(p16);
|
||||||
|
uint32_t val = (*(uint32_t *)((uintptr_t)p16 & ~0x3));
|
||||||
|
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||||
|
val >>= pos;
|
||||||
|
return (uint16_t)val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
int16_t mmu_get_int16(const int16_t *p16) {
|
||||||
|
ASSERT_RANGE_TEST_READ(p16);
|
||||||
|
uint32_t val = (*(uint32_t *)((uintptr_t)p16 & ~0x3));
|
||||||
|
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||||
|
val >>= pos;
|
||||||
|
return (int16_t)val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
uint8_t mmu_set_uint8(void *p8, const uint8_t val) {
|
||||||
|
ASSERT_RANGE_TEST_WRITE(p8);
|
||||||
|
uint32_t pos = ((uintptr_t)p8 & 0x3) * 8;
|
||||||
|
uint32_t sval = val << pos;
|
||||||
|
uint32_t valmask = 0x0FF << pos;
|
||||||
|
|
||||||
|
uint32_t *p32 = (uint32_t *)((uintptr_t)p8 & ~0x3);
|
||||||
|
uint32_t ival = *p32;
|
||||||
|
ival &= (~valmask);
|
||||||
|
ival |= sval;
|
||||||
|
*p32 = ival;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
uint16_t mmu_set_uint16(uint16_t *p16, const uint16_t val) {
|
||||||
|
ASSERT_RANGE_TEST_WRITE(p16);
|
||||||
|
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||||
|
uint32_t sval = val << pos;
|
||||||
|
uint32_t valmask = 0x0FFFF << pos;
|
||||||
|
|
||||||
|
uint32_t *p32 = (uint32_t *)((uintptr_t)p16 & ~0x3);
|
||||||
|
uint32_t ival = *p32;
|
||||||
|
ival &= (~valmask);
|
||||||
|
ival |= sval;
|
||||||
|
*p32 = ival;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
int16_t mmu_set_int16(int16_t *p16, const int16_t val) {
|
||||||
|
ASSERT_RANGE_TEST_WRITE(p16);
|
||||||
|
uint32_t sval = (uint16_t)val;
|
||||||
|
uint32_t pos = ((uintptr_t)p16 & 0x3) * 8;
|
||||||
|
sval <<= pos;
|
||||||
|
uint32_t valmask = 0x0FFFF << pos;
|
||||||
|
|
||||||
|
uint32_t *p32 = (uint32_t *)((uintptr_t)p16 & ~0x3);
|
||||||
|
uint32_t ival = *p32;
|
||||||
|
ival &= (~valmask);
|
||||||
|
ival |= sval;
|
||||||
|
*p32 = ival;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (MMU_IRAM_SIZE > 32*1024) && !defined(MMU_SEC_HEAP)
|
||||||
|
extern void _text_end(void);
|
||||||
|
#define MMU_SEC_HEAP mmu_sec_heap()
|
||||||
|
#define MMU_SEC_HEAP_SIZE mmu_sec_heap_size()
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
void *mmu_sec_heap(void) {
|
||||||
|
uint32_t sec_heap = (uint32_t)_text_end + 32;
|
||||||
|
return (void *)(sec_heap &= ~7);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __attribute__((always_inline))
|
||||||
|
size_t mmu_sec_heap_size(void) {
|
||||||
|
return (size_t)0xC000UL - ((size_t)mmu_sec_heap() - 0x40100000UL);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
151
cores/esp8266/reboot_uart_dwnld.cpp
Normal file
151
cores/esp8266/reboot_uart_dwnld.cpp
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
ESP8266-specific implementation of the UART download mode
|
||||||
|
Copyright (c) 2021 Timo Wischer <twischer@freenet.de>
|
||||||
|
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
|
||||||
|
|
||||||
|
This implementation is based on the original implementation of the ROM.
|
||||||
|
It was shortend to reduce the memory usage. The complete version and the
|
||||||
|
development history can be found in:
|
||||||
|
https://github.com/twischer/Arduino/tree/reboot_uart_download_full
|
||||||
|
This might be usefull in case of issues.
|
||||||
|
*/
|
||||||
|
#include "reboot_uart_dwnld.h"
|
||||||
|
#include <stdnoreturn.h>
|
||||||
|
#include <user_interface.h>
|
||||||
|
#include <esp8266_undocumented.h>
|
||||||
|
|
||||||
|
|
||||||
|
static inline uint32_t __rsil_1() {
|
||||||
|
uint32_t program_state;
|
||||||
|
asm volatile("rsil %0, 1" : "=r" (program_state));
|
||||||
|
return program_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void __wsr_intenable(uint32_t interupt_enable) {
|
||||||
|
asm volatile("wsr.intenable %0" :: "r" (interupt_enable));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void __wsr_litbase(uint32_t literal_base) {
|
||||||
|
asm volatile("wsr.litbase %0" :: "r" (literal_base));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void __wsr_ps(uint32_t program_state) {
|
||||||
|
asm volatile("wsr.ps %0" :: "r" (program_state));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void __wsr_vecbase(uint32_t vector_base) {
|
||||||
|
asm volatile("wsr.vecbase %0" :: "r" (vector_base));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void ICACHE_RAM_ATTR esp8266UartDownloadMode()
|
||||||
|
{
|
||||||
|
/* reverse engineered from system_restart_core() */
|
||||||
|
/* Before disabling instruction cache and restoring instruction RAM to a
|
||||||
|
* power-on like state, SPI bus must be idle.
|
||||||
|
*/
|
||||||
|
Wait_SPI_Idle(flashchip);
|
||||||
|
|
||||||
|
Cache_Read_Disable();
|
||||||
|
/* This will disable the 32kB instruction cache and extend the IRAM by 32kB.
|
||||||
|
* Therefore the full 64kB of IRAM will be available for boot.
|
||||||
|
* Cache_Read_Enable() sets those bits but Cache_Read_Disable() does not clear
|
||||||
|
* them. On hardware reset those bits are cleared. Therefore clear them also
|
||||||
|
* for this reboot.
|
||||||
|
*/
|
||||||
|
CLEAR_PERI_REG_MASK(PERIPHS_DPORT_ICACHE_ENABLE,
|
||||||
|
ICACHE_ENABLE_FIRST_16K | ICACHE_ENABLE_SECOND_16K);
|
||||||
|
|
||||||
|
/* reverse engineered from _ResetHandler() */
|
||||||
|
/* disable all level 1 interrupts */
|
||||||
|
__wsr_intenable(0);
|
||||||
|
/* Clear the literal base to use an offset of 0 for
|
||||||
|
* Load 32-bit PC-Relative(L32R) instructions
|
||||||
|
*/
|
||||||
|
__wsr_litbase(0);
|
||||||
|
asm volatile("rsync");
|
||||||
|
|
||||||
|
/* Set interrupt vector base address to system ROM */
|
||||||
|
__wsr_vecbase(0x40000000);
|
||||||
|
/* Set interrupt level to 1. Therefore disable interrupts of level 1.
|
||||||
|
* Above levels like level 2,... might still be active if available
|
||||||
|
* on ESP8266.
|
||||||
|
*/
|
||||||
|
__rsil_1();
|
||||||
|
|
||||||
|
/* reverse engineered from _start() */
|
||||||
|
/* Set stack pointer to upper end of data RAM */
|
||||||
|
const uint32_t stack_pointer = 0x40000000;
|
||||||
|
asm volatile("mov a1, %0" :: "r" (stack_pointer));
|
||||||
|
|
||||||
|
/* Set the program state register
|
||||||
|
* Name Value Description
|
||||||
|
* Interrupt level disable 0 enable all interrupt levels
|
||||||
|
* Exception mode 0 normal operation
|
||||||
|
* User vector mode 1 user vector mode, exceptions need to switch stacks
|
||||||
|
* Privilege level 0 Set to Ring 0
|
||||||
|
*/
|
||||||
|
__wsr_ps(0x20);
|
||||||
|
asm volatile("rsync");
|
||||||
|
|
||||||
|
/* reverse engineered from main() */
|
||||||
|
const uint32_t uart_no = 0;
|
||||||
|
uartAttach();
|
||||||
|
Uart_Init(uart_no);
|
||||||
|
ets_install_uart_printf(uart_no);
|
||||||
|
|
||||||
|
/* reverse engineered from boot_from_something() */
|
||||||
|
const uint16_t divlatch = uart_baudrate_detect(uart_no, 0);
|
||||||
|
rom_uart_div_modify(uart_no, divlatch);
|
||||||
|
UartDwnLdProc((uint8_t*)0x3fffa000, 0x2000, &user_start_fptr);
|
||||||
|
|
||||||
|
/* reverse engineered from main() */
|
||||||
|
if (user_start_fptr == NULL) {
|
||||||
|
if (boot_from_flash() != 0) {
|
||||||
|
ets_printf("boot_from_flash() failed\n");
|
||||||
|
while (true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user_start_fptr) {
|
||||||
|
user_start_fptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
ets_printf("user code done\n");
|
||||||
|
ets_run();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void esp8266RebootIntoUartDownloadMode()
|
||||||
|
{
|
||||||
|
/* reverse engineered from system_restart_local() */
|
||||||
|
if (system_func1(0x4) == -1) {
|
||||||
|
clockgate_watchdog(0);
|
||||||
|
SET_PERI_REG_MASK(PERIPHS_DPORT_18, 0xffff00ff);
|
||||||
|
pm_open_rf();
|
||||||
|
}
|
||||||
|
|
||||||
|
user_uart_wait_tx_fifo_empty(0, 0x7a120);
|
||||||
|
user_uart_wait_tx_fifo_empty(1, 0x7a120);
|
||||||
|
ets_intr_lock();
|
||||||
|
SET_PERI_REG_MASK(PERIPHS_DPORT_18, 0x7500);
|
||||||
|
CLEAR_PERI_REG_MASK(PERIPHS_DPORT_18, 0x7500);
|
||||||
|
SET_PERI_REG_MASK(PERIPHS_I2C_48, 0x2);
|
||||||
|
CLEAR_PERI_REG_MASK(PERIPHS_I2C_48, 0x2);
|
||||||
|
|
||||||
|
esp8266UartDownloadMode();
|
||||||
|
}
|
||||||
|
|
23
cores/esp8266/reboot_uart_dwnld.h
Normal file
23
cores/esp8266/reboot_uart_dwnld.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
ESP8266-specific implementation of the UART download mode
|
||||||
|
Copyright (c) 2021 Timo Wischer <twischer@freenet.de>
|
||||||
|
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 <stdnoreturn.h>
|
||||||
|
|
||||||
|
[[noreturn]] void esp8266RebootIntoUartDownloadMode();
|
@ -1,133 +0,0 @@
|
|||||||
/*
|
|
||||||
* sntp-lwip2.c - ESP8266-specific functions for SNTP and lwIP-v2
|
|
||||||
* Copyright (c) 2015 Espressif (license is tools/sdk/lwip/src/core/sntp.c's)
|
|
||||||
* Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
* are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
* 3. The name of the author may not be used to endorse or promote products
|
|
||||||
* derived from this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
|
||||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
|
||||||
* SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
|
||||||
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
||||||
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
|
|
||||||
* OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* History:
|
|
||||||
* This code is extracted from lwip1.4-espressif's sntp.c
|
|
||||||
* which is a patched version of the original lwip1's sntp.
|
|
||||||
* (check the mix-up in tools/sdk/lwip/src/core/sntp.c)
|
|
||||||
* It is moved here as-is and cleaned for maintainability and
|
|
||||||
* because it does not belong to lwip.
|
|
||||||
*
|
|
||||||
* TODOs:
|
|
||||||
* settimeofday(): handle tv->tv_usec
|
|
||||||
* sntp_mktm_r(): review, fix DST handling (this one is currently untouched from lwip-1.4)
|
|
||||||
* implement adjtime()
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <lwip/init.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <osapi.h>
|
|
||||||
#include <os_type.h>
|
|
||||||
#include "coredecls.h"
|
|
||||||
#include "Schedule.h"
|
|
||||||
|
|
||||||
static TrivialCB _settimeofday_cb;
|
|
||||||
|
|
||||||
void settimeofday_cb (TrivialCB&& cb)
|
|
||||||
{
|
|
||||||
_settimeofday_cb = std::move(cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
void settimeofday_cb (const TrivialCB& cb)
|
|
||||||
{
|
|
||||||
_settimeofday_cb = cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
#if LWIP_VERSION_MAJOR == 1
|
|
||||||
|
|
||||||
#include <pgmspace.h>
|
|
||||||
|
|
||||||
static const char stod14[] PROGMEM = "settimeofday() can't set time!\n";
|
|
||||||
bool sntp_set_timezone(sint8 timezone);
|
|
||||||
bool sntp_set_timezone_in_seconds(int32_t timezone)
|
|
||||||
{
|
|
||||||
return sntp_set_timezone((sint8)(timezone/(60*60))); //TODO: move this to the same file as sntp_set_timezone() in lwip1.4, and implement correctly over there.
|
|
||||||
}
|
|
||||||
|
|
||||||
void sntp_set_daylight(int daylight);
|
|
||||||
|
|
||||||
int settimeofday(const struct timeval* tv, const struct timezone* tz)
|
|
||||||
{
|
|
||||||
if (tz) /*before*/
|
|
||||||
{
|
|
||||||
sntp_set_timezone_in_seconds(tz->tz_minuteswest * 60);
|
|
||||||
// apparently tz->tz_dsttime is a bitfield and should not be further used (cf man)
|
|
||||||
sntp_set_daylight(0);
|
|
||||||
}
|
|
||||||
if (tv) /* after*/
|
|
||||||
{
|
|
||||||
// can't call lwip1.4's static sntp_set_system_time()
|
|
||||||
os_printf(stod14);
|
|
||||||
|
|
||||||
// reset time subsystem
|
|
||||||
timeshift64_is_set = false;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // lwip 1.4 only
|
|
||||||
|
|
||||||
#if LWIP_VERSION_MAJOR == 2
|
|
||||||
|
|
||||||
#include <lwip/apps/sntp.h>
|
|
||||||
|
|
||||||
uint32_t sntp_real_timestamp = 0;
|
|
||||||
LOCAL os_timer_t sntp_timer;
|
|
||||||
|
|
||||||
void ICACHE_RAM_ATTR sntp_time_inc (void)
|
|
||||||
{
|
|
||||||
sntp_real_timestamp++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int settimeofday(const struct timeval* tv, const struct timezone* tz)
|
|
||||||
{
|
|
||||||
if (tz || !tv)
|
|
||||||
// tz is obsolete (cf. man settimeofday)
|
|
||||||
return EINVAL;
|
|
||||||
|
|
||||||
// reset time subsystem
|
|
||||||
tune_timeshift64(tv->tv_sec * 1000000ULL + tv->tv_usec);
|
|
||||||
|
|
||||||
sntp_real_timestamp = tv->tv_sec;
|
|
||||||
os_timer_disarm(&sntp_timer);
|
|
||||||
os_timer_setfn(&sntp_timer, (os_timer_func_t *)sntp_time_inc, NULL);
|
|
||||||
os_timer_arm(&sntp_timer, 1000, 1);
|
|
||||||
|
|
||||||
if (_settimeofday_cb)
|
|
||||||
schedule_recurrent_function_us([](){ _settimeofday_cb(); return false; }, 0);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // lwip2 only
|
|
||||||
|
|
||||||
};
|
|
@ -1,6 +0,0 @@
|
|||||||
#ifndef __sntp_lwip2_h__
|
|
||||||
#define __sntp_lwip2_h__
|
|
||||||
|
|
||||||
#include <coredecls.h>
|
|
||||||
|
|
||||||
#endif
|
|
@ -26,8 +26,6 @@
|
|||||||
*/
|
*/
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include "FS.h"
|
#include "FS.h"
|
||||||
#undef max
|
|
||||||
#undef min
|
|
||||||
#include "FSImpl.h"
|
#include "FSImpl.h"
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "spiffs/spiffs.h"
|
#include "spiffs/spiffs.h"
|
||||||
|
62
cores/esp8266/stdlib_noniso.cpp
Normal file
62
cores/esp8266/stdlib_noniso.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
stdlib_noniso.h - nonstandard (but usefull) conversion functions
|
||||||
|
|
||||||
|
Copyright (c) 2021 David 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "stdlib_noniso.h"
|
||||||
|
|
||||||
|
// ulltoa() is slower than std::to_char() (1.6 times)
|
||||||
|
// but is smaller by ~800B/flash and ~250B/rodata
|
||||||
|
|
||||||
|
// ulltoa fills str backwards and can return a pointer different from str
|
||||||
|
char* ulltoa(unsigned long long val, char* str, int slen, unsigned int radix)
|
||||||
|
{
|
||||||
|
str += --slen;
|
||||||
|
*str = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
auto mod = val % radix;
|
||||||
|
val /= radix;
|
||||||
|
*--str = mod + ((mod > 9) ? ('a' - 10) : '0');
|
||||||
|
} while (--slen && val);
|
||||||
|
return val? nullptr: str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lltoa fills str backwards and can return a pointer different from str
|
||||||
|
char* lltoa (long long val, char* str, int slen, unsigned int radix)
|
||||||
|
{
|
||||||
|
bool neg;
|
||||||
|
if (val < 0)
|
||||||
|
{
|
||||||
|
val = -val;
|
||||||
|
neg = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
neg = false;
|
||||||
|
}
|
||||||
|
char* ret = ulltoa(val, str, slen, radix);
|
||||||
|
if (neg)
|
||||||
|
{
|
||||||
|
if (ret == str || ret == nullptr)
|
||||||
|
return nullptr;
|
||||||
|
*--ret = '-';
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
stdlib_noniso.h - nonstandard (but usefull) conversion functions
|
stdlib_noniso.h - nonstandard (but usefull) conversion functions
|
||||||
|
|
||||||
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
||||||
This file is part of the esp8266 core for Arduino environment.
|
This file is part of the esp8266 core for Arduino environment.
|
||||||
|
|
||||||
This library is free software; you can redistribute it and/or
|
This library is free software; you can redistribute it and/or
|
||||||
modify it under the terms of the GNU Lesser General Public
|
modify it under the terms of the GNU Lesser General Public
|
||||||
License as published by the Free Software Foundation; either
|
License as published by the Free Software Foundation; either
|
||||||
@ -36,14 +36,21 @@ char* itoa (int val, char *s, int radix);
|
|||||||
|
|
||||||
char* ltoa (long val, char *s, int radix);
|
char* ltoa (long val, char *s, int radix);
|
||||||
|
|
||||||
|
char* lltoa (long long val, char* str, int slen, unsigned int radix);
|
||||||
|
|
||||||
char* utoa (unsigned int val, char *s, int radix);
|
char* utoa (unsigned int val, char *s, int radix);
|
||||||
|
|
||||||
char* ultoa (unsigned long val, char *s, int radix);
|
char* ultoa (unsigned long val, char *s, int radix);
|
||||||
|
|
||||||
|
char* ulltoa (unsigned long long val, char* str, int slen, unsigned int radix);
|
||||||
|
|
||||||
char* dtostrf (double val, signed char width, unsigned char prec, char *s);
|
char* dtostrf (double val, signed char width, unsigned char prec, char *s);
|
||||||
|
|
||||||
void reverse(char* begin, char* end);
|
void reverse(char* begin, char* end);
|
||||||
|
|
||||||
|
const char* strrstr(const char*__restrict p_pcString,
|
||||||
|
const char*__restrict p_pcPattern);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
@ -14,19 +14,27 @@
|
|||||||
* License along with this library; if not, write to the Free Software
|
* License along with this library; if not, write to the Free Software
|
||||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
*
|
*
|
||||||
|
* reworked for newlib and lwIP-v2:
|
||||||
|
* time source is SNTP/settimeofday()
|
||||||
|
* system time is micros64() / NONOS-SDK's system_get_time()
|
||||||
|
* synchronisation of the two through timeshift64
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <../include/time.h> // See issue #6714
|
#include <../include/time.h> // See issue #6714
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
extern "C" {
|
||||||
|
#include <sys/_tz_structs.h>
|
||||||
|
};
|
||||||
#include <sys/reent.h>
|
#include <sys/reent.h>
|
||||||
#include "sntp.h"
|
#include <errno.h>
|
||||||
#include "coredecls.h"
|
|
||||||
|
#include <sntp.h> // nonos-sdk
|
||||||
|
#include <coredecls.h>
|
||||||
|
#include <Schedule.h>
|
||||||
|
|
||||||
#include <Arduino.h> // configTime()
|
#include <Arduino.h> // configTime()
|
||||||
|
|
||||||
#include "sntp-lwip2.h"
|
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
#ifndef _TIMEVAL_DEFINED
|
#ifndef _TIMEVAL_DEFINED
|
||||||
@ -42,16 +50,11 @@ extern struct tm* sntp_localtime(const time_t *clock);
|
|||||||
extern uint64_t micros64();
|
extern uint64_t micros64();
|
||||||
extern void sntp_set_daylight(int daylight);
|
extern void sntp_set_daylight(int daylight);
|
||||||
|
|
||||||
// time gap in seconds from 01.01.1900 (NTP time) to 01.01.1970 (UNIX time)
|
|
||||||
#define DIFF1900TO1970 2208988800UL
|
|
||||||
|
|
||||||
bool timeshift64_is_set = false;
|
|
||||||
static uint64_t timeshift64 = 0;
|
static uint64_t timeshift64 = 0;
|
||||||
|
|
||||||
void tune_timeshift64 (uint64_t now_us)
|
void tune_timeshift64 (uint64_t now_us)
|
||||||
{
|
{
|
||||||
timeshift64 = now_us - micros64();
|
timeshift64 = now_us - micros64();
|
||||||
timeshift64_is_set = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setServer(int id, const char* name_or_ip)
|
static void setServer(int id, const char* name_or_ip)
|
||||||
@ -73,14 +76,8 @@ int clock_gettime(clockid_t unused, struct timespec *tp)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if LWIP_VERSION_MAJOR == 1
|
///////////////////////////////////////////
|
||||||
// hack for espressif time management included in patched lwIP-1.4
|
// backport legacy nonos-sdk Espressif api
|
||||||
#define sntp_real_timestamp sntp_get_current_timestamp()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if LWIP_VERSION_MAJOR != 1
|
|
||||||
|
|
||||||
// backport Espressif api
|
|
||||||
|
|
||||||
bool sntp_set_timezone_in_seconds (int32_t timezone_sec)
|
bool sntp_set_timezone_in_seconds (int32_t timezone_sec)
|
||||||
{
|
{
|
||||||
@ -100,18 +97,20 @@ char* sntp_get_real_time(time_t t)
|
|||||||
|
|
||||||
uint32 sntp_get_current_timestamp()
|
uint32 sntp_get_current_timestamp()
|
||||||
{
|
{
|
||||||
return sntp_real_timestamp;
|
return time(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
// backport legacy nonos-sdk Espressif api
|
||||||
|
///////////////////////////////////////////
|
||||||
|
|
||||||
time_t time(time_t * t)
|
time_t time(time_t * t)
|
||||||
{
|
{
|
||||||
|
time_t currentTime_s = (micros64() + timeshift64) / 1000000ULL;
|
||||||
if (t)
|
if (t)
|
||||||
{
|
{
|
||||||
*t = sntp_real_timestamp;
|
*t = currentTime_s;
|
||||||
}
|
}
|
||||||
return sntp_real_timestamp;
|
return currentTime_s;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _gettimeofday_r(struct _reent* unused, struct timeval *tp, void *tzp)
|
int _gettimeofday_r(struct _reent* unused, struct timeval *tp, void *tzp)
|
||||||
@ -120,8 +119,6 @@ int _gettimeofday_r(struct _reent* unused, struct timeval *tp, void *tzp)
|
|||||||
(void) tzp;
|
(void) tzp;
|
||||||
if (tp)
|
if (tp)
|
||||||
{
|
{
|
||||||
if (!timeshift64_is_set)
|
|
||||||
tune_timeshift64(sntp_real_timestamp * 1000000ULL);
|
|
||||||
uint64_t currentTime_us = timeshift64 + micros64();
|
uint64_t currentTime_us = timeshift64 + micros64();
|
||||||
tp->tv_sec = currentTime_us / 1000000ULL;
|
tp->tv_sec = currentTime_us / 1000000ULL;
|
||||||
tp->tv_usec = currentTime_us % 1000000ULL;
|
tp->tv_usec = currentTime_us % 1000000ULL;
|
||||||
@ -133,6 +130,8 @@ int _gettimeofday_r(struct _reent* unused, struct timeval *tp, void *tzp)
|
|||||||
|
|
||||||
void configTime(int timezone_sec, int daylightOffset_sec, const char* server1, const char* server2, const char* server3)
|
void configTime(int timezone_sec, int daylightOffset_sec, const char* server1, const char* server2, const char* server3)
|
||||||
{
|
{
|
||||||
|
sntp_stop();
|
||||||
|
|
||||||
// There is no way to tell when DST starts or stop with this API
|
// There is no way to tell when DST starts or stop with this API
|
||||||
// So DST is always integrated in TZ
|
// So DST is always integrated in TZ
|
||||||
// The other API should be preferred
|
// The other API should be preferred
|
||||||
@ -155,7 +154,7 @@ void configTime(int timezone_sec, int daylightOffset_sec, const char* server1, c
|
|||||||
newlib inspection and internal structure hacking
|
newlib inspection and internal structure hacking
|
||||||
(no sprintf, no sscanf, -7584 flash bytes):
|
(no sprintf, no sscanf, -7584 flash bytes):
|
||||||
|
|
||||||
***/
|
*** hack starts here: ***/
|
||||||
|
|
||||||
static char gmt[] = "GMT";
|
static char gmt[] = "GMT";
|
||||||
|
|
||||||
@ -178,12 +177,14 @@ void configTime(int timezone_sec, int daylightOffset_sec, const char* server1, c
|
|||||||
tzr->offset = -_timezone;
|
tzr->offset = -_timezone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** end of hack ***/
|
||||||
|
|
||||||
// sntp servers
|
// sntp servers
|
||||||
setServer(0, server1);
|
setServer(0, server1);
|
||||||
setServer(1, server2);
|
setServer(1, server2);
|
||||||
setServer(2, server3);
|
setServer(2, server3);
|
||||||
|
|
||||||
/*** end of posix replacement ***/
|
sntp_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setTZ(const char* tz){
|
void setTZ(const char* tz){
|
||||||
@ -206,3 +207,49 @@ void configTime(const char* tz, const char* server1, const char* server2, const
|
|||||||
sntp_init();
|
sntp_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static BoolCB _settimeofday_cb;
|
||||||
|
|
||||||
|
void settimeofday_cb (const TrivialCB& cb)
|
||||||
|
{
|
||||||
|
_settimeofday_cb = [cb](bool sntp) { (void)sntp; cb(); };
|
||||||
|
}
|
||||||
|
|
||||||
|
void settimeofday_cb (const BoolCB& cb)
|
||||||
|
{
|
||||||
|
_settimeofday_cb = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
#include <lwip/apps/sntp.h>
|
||||||
|
|
||||||
|
int settimeofday(const struct timeval* tv, const struct timezone* tz)
|
||||||
|
{
|
||||||
|
bool from_sntp;
|
||||||
|
if (tz == (struct timezone*)0xFeedC0de)
|
||||||
|
{
|
||||||
|
// This special constant is used by lwip2/SNTP calling
|
||||||
|
// settimeofday(sntp-time, 0xfeedc0de), secretly using the
|
||||||
|
// obsolete-but-yet-still-there `tz` field.
|
||||||
|
// It allows to avoid duplicating this function and inform user
|
||||||
|
// about the source time change.
|
||||||
|
tz = nullptr;
|
||||||
|
from_sntp = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
from_sntp = false;
|
||||||
|
|
||||||
|
if (tz || !tv)
|
||||||
|
// tz is obsolete (cf. man settimeofday)
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
// reset time subsystem
|
||||||
|
tune_timeshift64(tv->tv_sec * 1000000ULL + tv->tv_usec);
|
||||||
|
|
||||||
|
if (_settimeofday_cb)
|
||||||
|
schedule_recurrent_function_us([from_sntp](){ _settimeofday_cb(from_sntp); return false; }, 0);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
@ -371,8 +371,9 @@ uart_get_rx_buffer_size(uart_t* uart)
|
|||||||
|
|
||||||
// The default ISR handler called when GDB is not enabled
|
// The default ISR handler called when GDB is not enabled
|
||||||
void ICACHE_RAM_ATTR
|
void ICACHE_RAM_ATTR
|
||||||
uart_isr(void * arg)
|
uart_isr(void * arg, void * frame)
|
||||||
{
|
{
|
||||||
|
(void) frame;
|
||||||
uart_t* uart = (uart_t*)arg;
|
uart_t* uart = (uart_t*)arg;
|
||||||
uint32_t usis = USIS(uart->uart_nr);
|
uint32_t usis = USIS(uart->uart_nr);
|
||||||
|
|
||||||
@ -505,8 +506,10 @@ uart_write(uart_t* uart, const char* buf, size_t size)
|
|||||||
|
|
||||||
size_t ret = size;
|
size_t ret = size;
|
||||||
const int uart_nr = uart->uart_nr;
|
const int uart_nr = uart->uart_nr;
|
||||||
while (size--)
|
while (size--) {
|
||||||
uart_do_write_char(uart_nr, pgm_read_byte(buf++));
|
uart_do_write_char(uart_nr, pgm_read_byte(buf++));
|
||||||
|
optimistic_yield(10000UL);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
102
cores/esp8266/umm_malloc/umm_heap_select.h
Normal file
102
cores/esp8266/umm_malloc/umm_heap_select.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
#ifndef UMM_MALLOC_SELECT_H
|
||||||
|
#define UMM_MALLOC_SELECT_H
|
||||||
|
|
||||||
|
#include <umm_malloc/umm_malloc.h>
|
||||||
|
|
||||||
|
#ifndef ALWAYS_INLINE
|
||||||
|
#define ALWAYS_INLINE inline __attribute__ ((always_inline))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Use FORCE_ALWAYS_INLINE to ensure HeapSelect... construtor/deconstructor
|
||||||
|
// are placed in IRAM
|
||||||
|
#ifdef FORCE_ALWAYS_INLINE_HEAP_SELECT
|
||||||
|
#define MAYBE_ALWAYS_INLINE ALWAYS_INLINE
|
||||||
|
#else
|
||||||
|
#define MAYBE_ALWAYS_INLINE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
This class is modeled after interrupts.h
|
||||||
|
|
||||||
|
HeapSelectIram is used to temporarily select an alternate Heap.
|
||||||
|
|
||||||
|
{
|
||||||
|
{
|
||||||
|
HeapSelectIram lock;
|
||||||
|
// allocate memory here
|
||||||
|
}
|
||||||
|
allocations here are from the old Heap selection
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HeapSelect {
|
||||||
|
public:
|
||||||
|
#if (UMM_NUM_HEAPS == 1)
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
HeapSelect(size_t id) { (void)id; }
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
~HeapSelect() {}
|
||||||
|
#else
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
HeapSelect(size_t id) : _heap_id(umm_get_current_heap_id()) {
|
||||||
|
umm_set_heap_by_id(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
~HeapSelect() {
|
||||||
|
umm_set_heap_by_id(_heap_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t _heap_id;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeapSelectIram {
|
||||||
|
public:
|
||||||
|
#ifdef UMM_HEAP_IRAM
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
HeapSelectIram() : _heap_id(umm_get_current_heap_id()) {
|
||||||
|
umm_set_heap_by_id(UMM_HEAP_IRAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
~HeapSelectIram() {
|
||||||
|
umm_set_heap_by_id(_heap_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t _heap_id;
|
||||||
|
|
||||||
|
#else
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
HeapSelectIram() {}
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
~HeapSelectIram() {}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeapSelectDram {
|
||||||
|
public:
|
||||||
|
#if (UMM_NUM_HEAPS == 1)
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
HeapSelectDram() {}
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
~HeapSelectDram() {}
|
||||||
|
#else
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
HeapSelectDram() : _heap_id(umm_get_current_heap_id()) {
|
||||||
|
umm_set_heap_by_id(UMM_HEAP_DRAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
MAYBE_ALWAYS_INLINE
|
||||||
|
~HeapSelectDram() {
|
||||||
|
umm_set_heap_by_id(_heap_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t _heap_id;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // UMM_MALLOC_SELECT_H
|
@ -23,25 +23,25 @@
|
|||||||
* ----------------------------------------------------------------------------
|
* ----------------------------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
UMM_HEAP_INFO ummHeapInfo;
|
// UMM_HEAP_INFO ummHeapInfo;
|
||||||
|
|
||||||
void *umm_info( void *ptr, bool force ) {
|
void *umm_info( void *ptr, bool force ) {
|
||||||
UMM_CRITICAL_DECL(id_info);
|
UMM_CRITICAL_DECL(id_info);
|
||||||
|
|
||||||
if(umm_heap == NULL) {
|
UMM_INIT_HEAP;
|
||||||
umm_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t blockNo = 0;
|
uint16_t blockNo = 0;
|
||||||
|
|
||||||
/* Protect the critical section... */
|
/* Protect the critical section... */
|
||||||
UMM_CRITICAL_ENTRY(id_info);
|
UMM_CRITICAL_ENTRY(id_info);
|
||||||
|
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Clear out all of the entries in the ummHeapInfo structure before doing
|
* Clear out all of the entries in the ummHeapInfo structure before doing
|
||||||
* any calculations..
|
* any calculations..
|
||||||
*/
|
*/
|
||||||
memset( &ummHeapInfo, 0, sizeof( ummHeapInfo ) );
|
memset( &_context->info, 0, sizeof( _context->info ) );
|
||||||
|
|
||||||
DBGLOG_FORCE( force, "\n" );
|
DBGLOG_FORCE( force, "\n" );
|
||||||
DBGLOG_FORCE( force, "+----------+-------+--------+--------+-------+--------+--------+\n" );
|
DBGLOG_FORCE( force, "+----------+-------+--------+--------+-------+--------+--------+\n" );
|
||||||
@ -65,18 +65,18 @@ void *umm_info( void *ptr, bool force ) {
|
|||||||
while( UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK ) {
|
while( UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK ) {
|
||||||
size_t curBlocks = (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK )-blockNo;
|
size_t curBlocks = (UMM_NBLOCK(blockNo) & UMM_BLOCKNO_MASK )-blockNo;
|
||||||
|
|
||||||
++ummHeapInfo.totalEntries;
|
++_context->info.totalEntries;
|
||||||
ummHeapInfo.totalBlocks += curBlocks;
|
_context->info.totalBlocks += curBlocks;
|
||||||
|
|
||||||
/* Is this a free block? */
|
/* Is this a free block? */
|
||||||
|
|
||||||
if( UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK ) {
|
if( UMM_NBLOCK(blockNo) & UMM_FREELIST_MASK ) {
|
||||||
++ummHeapInfo.freeEntries;
|
++_context->info.freeEntries;
|
||||||
ummHeapInfo.freeBlocks += curBlocks;
|
_context->info.freeBlocks += curBlocks;
|
||||||
ummHeapInfo.freeBlocksSquared += (curBlocks * curBlocks);
|
_context->info.freeBlocksSquared += (curBlocks * curBlocks);
|
||||||
|
|
||||||
if (ummHeapInfo.maxFreeContiguousBlocks < curBlocks) {
|
if (_context->info.maxFreeContiguousBlocks < curBlocks) {
|
||||||
ummHeapInfo.maxFreeContiguousBlocks = curBlocks;
|
_context->info.maxFreeContiguousBlocks = curBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
DBGLOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|NF %5d|PF %5d|\n",
|
DBGLOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|NF %5d|PF %5d|\n",
|
||||||
@ -98,8 +98,8 @@ void *umm_info( void *ptr, bool force ) {
|
|||||||
return( ptr );
|
return( ptr );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
++ummHeapInfo.usedEntries;
|
++_context->info.usedEntries;
|
||||||
ummHeapInfo.usedBlocks += curBlocks;
|
_context->info.usedBlocks += curBlocks;
|
||||||
|
|
||||||
DBGLOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|\n",
|
DBGLOG_FORCE( force, "|0x%08lx|B %5d|NB %5d|PB %5d|Z %5u|\n",
|
||||||
DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)),
|
DBGLOG_32_BIT_PTR(&UMM_BLOCK(blockNo)),
|
||||||
@ -131,35 +131,35 @@ void *umm_info( void *ptr, bool force ) {
|
|||||||
DBGLOG_FORCE( force, "+----------+-------+--------+--------+-------+--------+--------+\n" );
|
DBGLOG_FORCE( force, "+----------+-------+--------+--------+-------+--------+--------+\n" );
|
||||||
|
|
||||||
DBGLOG_FORCE( force, "Total Entries %5d Used Entries %5d Free Entries %5d\n",
|
DBGLOG_FORCE( force, "Total Entries %5d Used Entries %5d Free Entries %5d\n",
|
||||||
ummHeapInfo.totalEntries,
|
_context->info.totalEntries,
|
||||||
ummHeapInfo.usedEntries,
|
_context->info.usedEntries,
|
||||||
ummHeapInfo.freeEntries );
|
_context->info.freeEntries );
|
||||||
|
|
||||||
DBGLOG_FORCE( force, "Total Blocks %5d Used Blocks %5d Free Blocks %5d\n",
|
DBGLOG_FORCE( force, "Total Blocks %5d Used Blocks %5d Free Blocks %5d\n",
|
||||||
ummHeapInfo.totalBlocks,
|
_context->info.totalBlocks,
|
||||||
ummHeapInfo.usedBlocks,
|
_context->info.usedBlocks,
|
||||||
ummHeapInfo.freeBlocks );
|
_context->info.freeBlocks );
|
||||||
|
|
||||||
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
||||||
|
|
||||||
DBGLOG_FORCE( force, "Usage Metric: %5d\n", umm_usage_metric());
|
DBGLOG_FORCE( force, "Usage Metric: %5d\n", umm_usage_metric_core(_context));
|
||||||
DBGLOG_FORCE( force, "Fragmentation Metric: %5d\n", umm_fragmentation_metric());
|
DBGLOG_FORCE( force, "Fragmentation Metric: %5d\n", umm_fragmentation_metric_core(_context));
|
||||||
|
|
||||||
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
||||||
|
|
||||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||||
#if !defined(UMM_INLINE_METRICS)
|
#if !defined(UMM_INLINE_METRICS)
|
||||||
if (ummHeapInfo.freeBlocks == ummStats.free_blocks) {
|
if (_context->info.freeBlocks == _context->stats.free_blocks) {
|
||||||
DBGLOG_FORCE( force, "heap info Free blocks and heap statistics Free blocks match.\n");
|
DBGLOG_FORCE( force, "heap info Free blocks and heap statistics Free blocks match.\n");
|
||||||
} else {
|
} else {
|
||||||
DBGLOG_FORCE( force, "\nheap info Free blocks %5d != heap statistics Free Blocks %5d\n\n",
|
DBGLOG_FORCE( force, "\nheap info Free blocks %5d != heap statistics Free Blocks %5d\n\n",
|
||||||
ummHeapInfo.freeBlocks,
|
_context->info.freeBlocks,
|
||||||
ummStats.free_blocks );
|
_context->stats.free_blocks );
|
||||||
}
|
}
|
||||||
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
print_stats(force);
|
umm_print_stats(force);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Release the critical section... */
|
/* Release the critical section... */
|
||||||
@ -170,20 +170,29 @@ void *umm_info( void *ptr, bool force ) {
|
|||||||
|
|
||||||
/* ------------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
size_t umm_free_heap_size_core( umm_heap_context_t *_context ) {
|
||||||
|
return (size_t)_context->info.freeBlocks * sizeof(umm_block);
|
||||||
|
}
|
||||||
|
|
||||||
size_t umm_free_heap_size( void ) {
|
size_t umm_free_heap_size( void ) {
|
||||||
#ifndef UMM_INLINE_METRICS
|
#ifndef UMM_INLINE_METRICS
|
||||||
umm_info(NULL, false);
|
umm_info(NULL, false);
|
||||||
#endif
|
#endif
|
||||||
return (size_t)ummHeapInfo.freeBlocks * sizeof(umm_block);
|
|
||||||
|
return umm_free_heap_size_core(umm_get_current_heap());
|
||||||
}
|
}
|
||||||
|
|
||||||
//C Breaking change in upstream umm_max_block_size() was changed to
|
//C Breaking change in upstream umm_max_block_size() was changed to
|
||||||
//C umm_max_free_block_size() keeping old function name for (dot) releases.
|
//C umm_max_free_block_size() keeping old function name for (dot) releases.
|
||||||
//C TODO: update at next major release.
|
//C TODO: update at next major release.
|
||||||
//C size_t umm_max_free_block_size( void ) {
|
//C size_t umm_max_free_block_size( void ) {
|
||||||
|
size_t umm_max_block_size_core( umm_heap_context_t *_context ) {
|
||||||
|
return _context->info.maxFreeContiguousBlocks * sizeof(umm_block);
|
||||||
|
}
|
||||||
|
|
||||||
size_t umm_max_block_size( void ) {
|
size_t umm_max_block_size( void ) {
|
||||||
umm_info(NULL, false);
|
umm_info(NULL, false);
|
||||||
return ummHeapInfo.maxFreeContiguousBlocks * sizeof(umm_block);
|
return umm_max_block_size_core(umm_get_current_heap());
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -191,50 +200,62 @@ size_t umm_max_block_size( void ) {
|
|||||||
umm_fragmentation_metric() must to be preceeded by a call to umm_info(NULL, false)
|
umm_fragmentation_metric() must to be preceeded by a call to umm_info(NULL, false)
|
||||||
for updated results.
|
for updated results.
|
||||||
*/
|
*/
|
||||||
int umm_usage_metric( void ) {
|
int umm_usage_metric_core( umm_heap_context_t *_context ) {
|
||||||
#ifndef UMM_INLINE_METRICS
|
//C Note, umm_metrics also appears in the upstrean w/o definition. I suspect it is suppose to be ummHeapInfo.
|
||||||
umm_info(NULL, false);
|
// DBGLOG_DEBUG( "usedBlocks %d totalBlocks %d\n", umm_metrics.usedBlocks, ummHeapInfo.totalBlocks);
|
||||||
#endif
|
DBGLOG_DEBUG( "usedBlocks %d totalBlocks %d\n", _context->info.usedBlocks, _context->info.totalBlocks);
|
||||||
DBGLOG_DEBUG( "usedBlocks %d totalBlocks %d\n", umm_metrics.usedBlocks, ummHeapInfo.totalBlocks);
|
if (_context->info.freeBlocks)
|
||||||
if (ummHeapInfo.freeBlocks)
|
return (int)((_context->info.usedBlocks * 100)/(_context->info.freeBlocks));
|
||||||
return (int)((ummHeapInfo.usedBlocks * 100)/(ummHeapInfo.freeBlocks));
|
|
||||||
|
|
||||||
return -1; // no freeBlocks
|
return -1; // no freeBlocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int umm_usage_metric( void ) {
|
||||||
|
#ifndef UMM_INLINE_METRICS
|
||||||
|
umm_info(NULL, false);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return umm_usage_metric_core(umm_get_current_heap());
|
||||||
|
}
|
||||||
uint32_t sqrt32 (uint32_t n);
|
uint32_t sqrt32 (uint32_t n);
|
||||||
|
|
||||||
|
int umm_fragmentation_metric_core( umm_heap_context_t *_context ) {
|
||||||
|
// DBGLOG_DEBUG( "freeBlocks %d freeBlocksSquared %d\n", umm_metrics.freeBlocks, ummHeapInfo.freeBlocksSquared);
|
||||||
|
DBGLOG_DEBUG( "freeBlocks %d freeBlocksSquared %d\n", _context->info.freeBlocks, _context->info.freeBlocksSquared);
|
||||||
|
if (0 == _context->info.freeBlocks) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
//upstream version: return (100 - (((uint32_t)(sqrtf(ummHeapInfo.freeBlocksSquared)) * 100)/(ummHeapInfo.freeBlocks)));
|
||||||
|
return (100 - (((uint32_t)(sqrt32(_context->info.freeBlocksSquared)) * 100)/(_context->info.freeBlocks)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int umm_fragmentation_metric( void ) {
|
int umm_fragmentation_metric( void ) {
|
||||||
#ifndef UMM_INLINE_METRICS
|
#ifndef UMM_INLINE_METRICS
|
||||||
umm_info(NULL, false);
|
umm_info(NULL, false);
|
||||||
#endif
|
#endif
|
||||||
DBGLOG_DEBUG( "freeBlocks %d freeBlocksSquared %d\n", umm_metrics.freeBlocks, ummHeapInfo.freeBlocksSquared);
|
|
||||||
if (0 == ummHeapInfo.freeBlocks) {
|
return umm_fragmentation_metric_core(umm_get_current_heap());
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
//upstream version: return (100 - (((uint32_t)(sqrtf(ummHeapInfo.freeBlocksSquared)) * 100)/(ummHeapInfo.freeBlocks)));
|
|
||||||
return (100 - (((uint32_t)(sqrt32(ummHeapInfo.freeBlocksSquared)) * 100)/(ummHeapInfo.freeBlocks)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef UMM_INLINE_METRICS
|
#ifdef UMM_INLINE_METRICS
|
||||||
static void umm_fragmentation_metric_init( void ) {
|
static void umm_fragmentation_metric_init( umm_heap_context_t *_context ) {
|
||||||
ummHeapInfo.freeBlocks = UMM_NUMBLOCKS - 2;
|
_context->info.freeBlocks = UMM_NUMBLOCKS - 2;
|
||||||
ummHeapInfo.freeBlocksSquared = ummHeapInfo.freeBlocks * ummHeapInfo.freeBlocks;
|
_context->info.freeBlocksSquared = _context->info.freeBlocks * _context->info.freeBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void umm_fragmentation_metric_add( uint16_t c ) {
|
static void umm_fragmentation_metric_add( umm_heap_context_t *_context, uint16_t c ) {
|
||||||
uint16_t blocks = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) - c;
|
uint16_t blocks = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) - c;
|
||||||
DBGLOG_DEBUG( "Add block %d size %d to free metric\n", c, blocks);
|
DBGLOG_DEBUG( "Add block %d size %d to free metric\n", c, blocks);
|
||||||
ummHeapInfo.freeBlocks += blocks;
|
_context->info.freeBlocks += blocks;
|
||||||
ummHeapInfo.freeBlocksSquared += (blocks * blocks);
|
_context->info.freeBlocksSquared += (blocks * blocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void umm_fragmentation_metric_remove( uint16_t c ) {
|
static void umm_fragmentation_metric_remove( umm_heap_context_t *_context, uint16_t c ) {
|
||||||
uint16_t blocks = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) - c;
|
uint16_t blocks = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) - c;
|
||||||
DBGLOG_DEBUG( "Remove block %d size %d from free metric\n", c, blocks);
|
DBGLOG_DEBUG( "Remove block %d size %d from free metric\n", c, blocks);
|
||||||
ummHeapInfo.freeBlocks -= blocks;
|
_context->info.freeBlocks -= blocks;
|
||||||
ummHeapInfo.freeBlocksSquared -= (blocks * blocks);
|
_context->info.freeBlocksSquared -= (blocks * blocks);
|
||||||
}
|
}
|
||||||
#endif // UMM_INLINE_METRICS
|
#endif // UMM_INLINE_METRICS
|
||||||
|
|
||||||
|
@ -33,13 +33,14 @@ bool umm_integrity_check(void) {
|
|||||||
uint16_t prev;
|
uint16_t prev;
|
||||||
uint16_t cur;
|
uint16_t cur;
|
||||||
|
|
||||||
if (umm_heap == NULL) {
|
UMM_INIT_HEAP;
|
||||||
umm_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Iterate through all free blocks */
|
/* Iterate through all free blocks */
|
||||||
prev = 0;
|
prev = 0;
|
||||||
UMM_CRITICAL_ENTRY(id_integrity);
|
UMM_CRITICAL_ENTRY(id_integrity);
|
||||||
|
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
|
||||||
while(1) {
|
while(1) {
|
||||||
cur = UMM_NFREE(prev);
|
cur = UMM_NFREE(prev);
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ UMM_TIME_STATS time_stats = {
|
|||||||
#ifdef UMM_INFO
|
#ifdef UMM_INFO
|
||||||
{0xFFFFFFFF, 0U, 0U, 0U},
|
{0xFFFFFFFF, 0U, 0U, 0U},
|
||||||
#endif
|
#endif
|
||||||
#ifdef UMM_POISON_CHECK
|
#if defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE)
|
||||||
{0xFFFFFFFF, 0U, 0U, 0U},
|
{0xFFFFFFFF, 0U, 0U, 0U},
|
||||||
#endif
|
#endif
|
||||||
#ifdef UMM_INTEGRITY_CHECK
|
#ifdef UMM_INTEGRITY_CHECK
|
||||||
@ -42,7 +42,7 @@ bool ICACHE_FLASH_ATTR get_umm_get_perf_data(UMM_TIME_STATS *p, size_t size)
|
|||||||
#if defined(UMM_POISON_CHECK_LITE)
|
#if defined(UMM_POISON_CHECK_LITE)
|
||||||
// We skip this when doing the full poison check.
|
// We skip this when doing the full poison check.
|
||||||
|
|
||||||
static bool check_poison_neighbors( uint16_t cur ) {
|
static bool check_poison_neighbors( umm_heap_context_t *_context, uint16_t cur ) {
|
||||||
uint16_t c;
|
uint16_t c;
|
||||||
|
|
||||||
if ( 0 == cur )
|
if ( 0 == cur )
|
||||||
@ -96,12 +96,16 @@ static void *get_unpoisoned_check_neighbors( void *vptr, const char* file, int l
|
|||||||
UMM_CRITICAL_DECL(id_poison);
|
UMM_CRITICAL_DECL(id_poison);
|
||||||
uint16_t c;
|
uint16_t c;
|
||||||
bool poison = false;
|
bool poison = false;
|
||||||
|
umm_heap_context_t *_context = umm_get_ptr_context( vptr );
|
||||||
|
if (NULL == _context) {
|
||||||
|
panic();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
/* Figure out which block we're in. Note the use of truncated division... */
|
/* Figure out which block we're in. Note the use of truncated division... */
|
||||||
c = (ptr - (uintptr_t)(&(umm_heap[0])))/sizeof(umm_block);
|
c = (ptr - (uintptr_t)(&(_context->heap[0])))/sizeof(umm_block);
|
||||||
|
|
||||||
UMM_CRITICAL_ENTRY(id_poison);
|
UMM_CRITICAL_ENTRY(id_poison);
|
||||||
poison = check_poison_block(&UMM_BLOCK(c)) && check_poison_neighbors(c);
|
poison = check_poison_block(&UMM_BLOCK(c)) && check_poison_neighbors(_context, c);
|
||||||
UMM_CRITICAL_EXIT(id_poison);
|
UMM_CRITICAL_EXIT(id_poison);
|
||||||
|
|
||||||
if (!poison) {
|
if (!poison) {
|
||||||
@ -157,17 +161,13 @@ size_t umm_block_size( void ) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if (!defined(UMM_INLINE_METRICS) && defined(UMM_STATS)) || defined(UMM_STATS_FULL)
|
|
||||||
UMM_STATISTICS ummStats;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||||
// Keep complete call path in IRAM
|
// Keep complete call path in IRAM
|
||||||
size_t umm_free_heap_size_lw( void ) {
|
size_t umm_free_heap_size_lw( void ) {
|
||||||
if (umm_heap == NULL) {
|
UMM_INIT_HEAP;
|
||||||
umm_init();
|
|
||||||
}
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
return (size_t)UMM_FREE_BLOCKS * sizeof(umm_block);
|
return (size_t)_context->UMM_FREE_BLOCKS * sizeof(umm_block);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -186,14 +186,17 @@ size_t xPortGetFreeHeapSize(void) __attribute__ ((alias("umm_free_heap_size")));
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||||
void print_stats(int force) {
|
void umm_print_stats(int force) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
|
||||||
DBGLOG_FORCE( force, "umm heap statistics:\n");
|
DBGLOG_FORCE( force, "umm heap statistics:\n");
|
||||||
DBGLOG_FORCE( force, " Raw Free Space %5u\n", UMM_FREE_BLOCKS * sizeof(umm_block));
|
DBGLOG_FORCE( force, " Heap ID %5u\n", _context->id);
|
||||||
DBGLOG_FORCE( force, " OOM Count %5u\n", UMM_OOM_COUNT);
|
DBGLOG_FORCE( force, " Free Space %5u\n", _context->UMM_FREE_BLOCKS * sizeof(umm_block));
|
||||||
|
DBGLOG_FORCE( force, " OOM Count %5u\n", _context->UMM_OOM_COUNT);
|
||||||
#if defined(UMM_STATS_FULL)
|
#if defined(UMM_STATS_FULL)
|
||||||
DBGLOG_FORCE( force, " Low Watermark %5u\n", ummStats.free_blocks_min * sizeof(umm_block));
|
DBGLOG_FORCE( force, " Low Watermark %5u\n", _context->stats.free_blocks_min * sizeof(umm_block));
|
||||||
DBGLOG_FORCE( force, " Low Watermark ISR %5u\n", ummStats.free_blocks_isr_min * sizeof(umm_block));
|
DBGLOG_FORCE( force, " Low Watermark ISR %5u\n", _context->stats.free_blocks_isr_min * sizeof(umm_block));
|
||||||
DBGLOG_FORCE( force, " MAX Alloc Request %5u\n", ummStats.alloc_max_size);
|
DBGLOG_FORCE( force, " MAX Alloc Request %5u\n", _context->stats.alloc_max_size);
|
||||||
#endif
|
#endif
|
||||||
DBGLOG_FORCE( force, " Size of umm_block %5u\n", sizeof(umm_block));
|
DBGLOG_FORCE( force, " Size of umm_block %5u\n", sizeof(umm_block));
|
||||||
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
DBGLOG_FORCE( force, "+--------------------------------------------------------------+\n" );
|
||||||
@ -206,7 +209,7 @@ int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) {
|
|||||||
the PROGMEM address must be word (4 bytes) aligned. The destination
|
the PROGMEM address must be word (4 bytes) aligned. The destination
|
||||||
address for ets_memcpy must also be word-aligned.
|
address for ets_memcpy must also be word-aligned.
|
||||||
*/
|
*/
|
||||||
char ram_buf[ets_strlen(fmt)] __attribute__ ((aligned(4)));
|
char ram_buf[ets_strlen(fmt) + 1] __attribute__((aligned(4)));
|
||||||
ets_strcpy(ram_buf, fmt);
|
ets_strcpy(ram_buf, fmt);
|
||||||
va_list argPtr;
|
va_list argPtr;
|
||||||
va_start(argPtr, fmt);
|
va_start(argPtr, fmt);
|
||||||
@ -215,4 +218,85 @@ int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_get_oom_count( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->UMM_OOM_COUNT;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef UMM_STATS_FULL
|
||||||
|
// TODO - Did I mix something up
|
||||||
|
//
|
||||||
|
// umm_free_heap_size_min is the same code as
|
||||||
|
// umm_free_heap_size_lw_min
|
||||||
|
//
|
||||||
|
// If this is correct use alias.
|
||||||
|
//
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_free_heap_size_lw_min( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->stats.free_blocks_min * umm_block_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_free_heap_size_min_reset( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
_context->stats.free_blocks_min = _context->UMM_FREE_BLOCKS;
|
||||||
|
return _context->stats.free_blocks_min * umm_block_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0 // TODO - Don't understand this why do both umm_free_heap_size_(lw_)min exist
|
||||||
|
size_t umm_free_heap_size_min(void) __attribute__ ((alias("umm_free_heap_size_lw_min")));
|
||||||
|
#else
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_free_heap_size_min( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->stats.free_blocks_min * umm_block_size();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_free_heap_size_isr_min( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->stats.free_blocks_isr_min * umm_block_size();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_get_max_alloc_size( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->stats.alloc_max_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_get_last_alloc_size( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->stats.last_alloc_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_get_malloc_count( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->stats.id_malloc_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_get_malloc_zero_count( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->stats.id_malloc_zero_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_get_realloc_count( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->stats.id_realloc_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_get_realloc_zero_count( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->stats.id_realloc_zero_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_get_free_count( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->stats.id_free_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ICACHE_FLASH_ATTR umm_get_free_null_count( void ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
return _context->stats.id_free_null_count;
|
||||||
|
}
|
||||||
|
#endif // UMM_STATS_FULL
|
||||||
|
|
||||||
#endif // BUILD_UMM_MALLOC_C
|
#endif // BUILD_UMM_MALLOC_C
|
||||||
|
@ -37,12 +37,12 @@
|
|||||||
|
|
||||||
|
|
||||||
#if defined(UMM_POISON_CHECK_LITE)
|
#if defined(UMM_POISON_CHECK_LITE)
|
||||||
static bool check_poison_neighbors( uint16_t cur );
|
static bool check_poison_neighbors( umm_heap_context_t *_context, uint16_t cur );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||||
void ICACHE_FLASH_ATTR print_stats(int force);
|
void ICACHE_FLASH_ATTR umm_print_stats(int force);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -51,4 +51,21 @@ int ICACHE_FLASH_ATTR umm_info_safe_printf_P(const char *fmt, ...) __attribute__
|
|||||||
#define UMM_INFO_PRINTF(fmt, ...) umm_info_safe_printf_P(PSTR4(fmt), ##__VA_ARGS__)
|
#define UMM_INFO_PRINTF(fmt, ...) umm_info_safe_printf_P(PSTR4(fmt), ##__VA_ARGS__)
|
||||||
// use PSTR4() instead of PSTR() to ensure 4-bytes alignment in Flash, whatever the default alignment of PSTR_ALIGN
|
// use PSTR4() instead of PSTR() to ensure 4-bytes alignment in Flash, whatever the default alignment of PSTR_ALIGN
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct umm_block_t umm_block;
|
||||||
|
|
||||||
|
struct UMM_HEAP_CONTEXT {
|
||||||
|
umm_block *heap;
|
||||||
|
void *heap_end;
|
||||||
|
#if (!defined(UMM_INLINE_METRICS) && defined(UMM_STATS)) || defined(UMM_STATS_FULL)
|
||||||
|
UMM_STATISTICS stats;
|
||||||
|
#endif
|
||||||
|
#ifdef UMM_INFO
|
||||||
|
UMM_HEAP_INFO info;
|
||||||
|
#endif
|
||||||
|
unsigned short int numblocks;
|
||||||
|
unsigned char id;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -63,6 +63,11 @@ extern "C" {
|
|||||||
#define DBGLOG_LEVEL 0
|
#define DBGLOG_LEVEL 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Save 104 bytes by calling umm_init() early once from app_entry()
|
||||||
|
// Some minor UMM_CRITICAL_METRICS counts will be lost through CRT0 init.
|
||||||
|
// #define UMM_INIT_HEAP if (!umm_heap) { umm_init(); }
|
||||||
|
#define UMM_INIT_HEAP (void)0
|
||||||
|
|
||||||
#include "dbglog/dbglog.h"
|
#include "dbglog/dbglog.h"
|
||||||
|
|
||||||
//C This change is new in upstream umm_malloc.I think this would have created a
|
//C This change is new in upstream umm_malloc.I think this would have created a
|
||||||
@ -101,24 +106,146 @@ UMM_H_ATTPACKPRE typedef struct umm_block_t {
|
|||||||
#define UMM_BLOCKNO_MASK ((uint16_t)(0x7FFF))
|
#define UMM_BLOCKNO_MASK ((uint16_t)(0x7FFF))
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------- */
|
||||||
|
umm_heap_context_t heap_context[UMM_NUM_HEAPS] __attribute__((section(".noinit")));
|
||||||
|
// void *umm_heap = NULL;
|
||||||
|
|
||||||
umm_block *umm_heap = NULL;
|
/* A stack allowing push/popping of heaps for library use */
|
||||||
uint16_t umm_numblocks = 0;
|
#if (UMM_NUM_HEAPS == 1)
|
||||||
|
|
||||||
#define UMM_NUMBLOCKS (umm_numblocks)
|
#else
|
||||||
|
static size_t umm_heap_cur = UMM_HEAP_DRAM;
|
||||||
|
static int umm_heap_stack_ptr = 0;
|
||||||
|
static unsigned char umm_heap_stack[UMM_HEAP_STACK_DEPTH];
|
||||||
|
#endif
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/*
|
||||||
|
* Methods to get heap id or context
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if (UMM_NUM_HEAPS == 1)
|
||||||
|
size_t umm_get_current_heap_id(void) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
umm_heap_context_t *umm_get_current_heap(void) {
|
||||||
|
return &heap_context[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static umm_heap_context_t *umm_get_heap_by_id( size_t which ) {
|
||||||
|
(void)which;
|
||||||
|
return &heap_context[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
umm_heap_context_t *umm_set_heap_by_id( size_t which ) {
|
||||||
|
(void)which;
|
||||||
|
return &heap_context[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
size_t umm_get_current_heap_id(void) {
|
||||||
|
return umm_heap_cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
umm_heap_context_t *umm_get_current_heap(void) {
|
||||||
|
return &heap_context[umm_heap_cur];
|
||||||
|
}
|
||||||
|
|
||||||
|
static umm_heap_context_t *umm_get_heap_by_id( size_t which ) {
|
||||||
|
if (which < UMM_NUM_HEAPS) {
|
||||||
|
return &heap_context[which];
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
umm_heap_context_t *umm_set_heap_by_id( size_t which ) {
|
||||||
|
umm_heap_context_t *_context = umm_get_heap_by_id(which);
|
||||||
|
if (_context && _context->heap) {
|
||||||
|
umm_heap_cur = which;
|
||||||
|
return _context;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (UMM_NUM_HEAPS == 1)
|
||||||
|
umm_heap_context_t *umm_push_heap( size_t which ) {
|
||||||
|
(void)which;
|
||||||
|
return &heap_context[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
umm_heap_context_t *umm_pop_heap( void ) {
|
||||||
|
return &heap_context[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
int umm_get_heap_stack_index( void ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
umm_heap_context_t *umm_push_heap( size_t which ) {
|
||||||
|
if (umm_heap_stack_ptr < UMM_HEAP_STACK_DEPTH) {
|
||||||
|
umm_heap_stack[umm_heap_stack_ptr++] = umm_heap_cur;
|
||||||
|
return umm_set_heap_by_id( which );
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
umm_heap_context_t *umm_pop_heap( void ) {
|
||||||
|
if (umm_heap_stack_ptr > 0 ) {
|
||||||
|
return umm_set_heap_by_id(umm_heap_stack[--umm_heap_stack_ptr]);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intended for diagnosic use
|
||||||
|
int umm_get_heap_stack_index( void ) {
|
||||||
|
return umm_heap_stack_ptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
/*
|
||||||
|
* Returns the correct heap context for a given pointer. Useful for
|
||||||
|
* realloc or free since you may not be in the right heap to handle it.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static bool test_ptr_context( size_t which, void *ptr ) {
|
||||||
|
return
|
||||||
|
heap_context[which].heap &&
|
||||||
|
ptr >= (void *)heap_context[which].heap &&
|
||||||
|
ptr < heap_context[which].heap_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
static umm_heap_context_t *umm_get_ptr_context(void *ptr) {
|
||||||
|
for (size_t i = 0; i < UMM_NUM_HEAPS; i++) {
|
||||||
|
if (test_ptr_context( i, ptr ) ) {
|
||||||
|
return umm_get_heap_by_id( i );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define UMM_NUMBLOCKS (_context->numblocks)
|
||||||
#define UMM_BLOCK_LAST (UMM_NUMBLOCKS - 1)
|
#define UMM_BLOCK_LAST (UMM_NUMBLOCKS - 1)
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------
|
/* -------------------------------------------------------------------------
|
||||||
* These macros evaluate to the address of the block and data respectively
|
* These macros evaluate to the address of the block and data respectively
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define UMM_BLOCK(b) (umm_heap[b])
|
#define UMM_BLOCK(b) (_context->heap[b])
|
||||||
#define UMM_DATA(b) (UMM_BLOCK(b).body.data)
|
#define UMM_DATA(b) (UMM_BLOCK(b).body.data)
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------
|
/* -------------------------------------------------------------------------
|
||||||
* These macros evaluate to the index of the block - NOT the address!!!
|
* These macros evaluate to the index of the block - NOT the address!!!
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
#define UMM_NBLOCK(b) (UMM_BLOCK(b).header.used.next)
|
#define UMM_NBLOCK(b) (UMM_BLOCK(b).header.used.next)
|
||||||
#define UMM_PBLOCK(b) (UMM_BLOCK(b).header.used.prev)
|
#define UMM_PBLOCK(b) (UMM_BLOCK(b).header.used.prev)
|
||||||
#define UMM_NFREE(b) (UMM_BLOCK(b).body.free.next)
|
#define UMM_NFREE(b) (UMM_BLOCK(b).body.free.next)
|
||||||
@ -172,7 +299,9 @@ static uint16_t umm_blocks( size_t size ) {
|
|||||||
*
|
*
|
||||||
* Note that free pointers are NOT modified by this function.
|
* Note that free pointers are NOT modified by this function.
|
||||||
*/
|
*/
|
||||||
static void umm_split_block( uint16_t c,
|
static void umm_split_block(
|
||||||
|
umm_heap_context_t *_context,
|
||||||
|
uint16_t c,
|
||||||
uint16_t blocks,
|
uint16_t blocks,
|
||||||
uint16_t new_freemask ) {
|
uint16_t new_freemask ) {
|
||||||
|
|
||||||
@ -185,7 +314,7 @@ static void umm_split_block( uint16_t c,
|
|||||||
|
|
||||||
/* ------------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------------ */
|
||||||
|
|
||||||
static void umm_disconnect_from_free_list( uint16_t c ) {
|
static void umm_disconnect_from_free_list( umm_heap_context_t *_context, uint16_t c ) {
|
||||||
/* Disconnect this block from the FREE list */
|
/* Disconnect this block from the FREE list */
|
||||||
|
|
||||||
UMM_NFREE(UMM_PFREE(c)) = UMM_NFREE(c);
|
UMM_NFREE(UMM_PFREE(c)) = UMM_NFREE(c);
|
||||||
@ -202,7 +331,7 @@ static void umm_disconnect_from_free_list( uint16_t c ) {
|
|||||||
* next block is free.
|
* next block is free.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static void umm_assimilate_up( uint16_t c ) {
|
static void umm_assimilate_up( umm_heap_context_t *_context, uint16_t c ) {
|
||||||
|
|
||||||
if( UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK ) {
|
if( UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK ) {
|
||||||
|
|
||||||
@ -217,7 +346,7 @@ static void umm_assimilate_up( uint16_t c ) {
|
|||||||
|
|
||||||
/* Disconnect the next block from the FREE list */
|
/* Disconnect the next block from the FREE list */
|
||||||
|
|
||||||
umm_disconnect_from_free_list( UMM_NBLOCK(c) );
|
umm_disconnect_from_free_list( _context, UMM_NBLOCK(c) );
|
||||||
|
|
||||||
/* Assimilate the next block with this one */
|
/* Assimilate the next block with this one */
|
||||||
|
|
||||||
@ -232,7 +361,7 @@ static void umm_assimilate_up( uint16_t c ) {
|
|||||||
* up before assimilating down.
|
* up before assimilating down.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static uint16_t umm_assimilate_down( uint16_t c, uint16_t freemask ) {
|
static uint16_t umm_assimilate_down( umm_heap_context_t *_context, uint16_t c, uint16_t freemask ) {
|
||||||
|
|
||||||
// We are going to assimilate down to the previous block because
|
// We are going to assimilate down to the previous block because
|
||||||
// it was free, so remove it from the fragmentation metric
|
// it was free, so remove it from the fragmentation metric
|
||||||
@ -257,23 +386,18 @@ static uint16_t umm_assimilate_down( uint16_t c, uint16_t freemask ) {
|
|||||||
|
|
||||||
/* ------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
void umm_init( void ) {
|
static void umm_init_stage_2( umm_heap_context_t *_context ) {
|
||||||
/* init heap pointer and size, and memset it to 0 */
|
|
||||||
umm_heap = (umm_block *)UMM_MALLOC_CFG_HEAP_ADDR;
|
|
||||||
umm_numblocks = (UMM_MALLOC_CFG_HEAP_SIZE / sizeof(umm_block));
|
|
||||||
memset(umm_heap, 0x00, UMM_MALLOC_CFG_HEAP_SIZE);
|
|
||||||
|
|
||||||
/* setup initial blank heap structure */
|
/* setup initial blank heap structure */
|
||||||
UMM_FRAGMENTATION_METRIC_INIT();
|
UMM_FRAGMENTATION_METRIC_INIT();
|
||||||
|
|
||||||
/* init ummStats.free_blocks */
|
/* init stats.free_blocks */
|
||||||
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
|
||||||
#if defined(UMM_STATS_FULL)
|
#if defined(UMM_STATS_FULL)
|
||||||
ummStats.free_blocks_min =
|
_context->stats.free_blocks_min =
|
||||||
ummStats.free_blocks_isr_min = UMM_NUMBLOCKS - 2;
|
_context->stats.free_blocks_isr_min = UMM_NUMBLOCKS - 2;
|
||||||
#endif
|
#endif
|
||||||
#ifndef UMM_INLINE_METRICS
|
#ifndef UMM_INLINE_METRICS
|
||||||
ummStats.free_blocks = UMM_NUMBLOCKS - 2;
|
_context->stats.free_blocks = UMM_NUMBLOCKS - 2;
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -314,15 +438,89 @@ void umm_init( void ) {
|
|||||||
UMM_PBLOCK(UMM_BLOCK_LAST) = 1;
|
UMM_PBLOCK(UMM_BLOCK_LAST) = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void umm_init_common( size_t id, void *start_addr, size_t size, bool zero ) {
|
||||||
|
/* Preserve internal setup */
|
||||||
|
umm_heap_context_t *_context = umm_get_heap_by_id(id);
|
||||||
|
if (NULL == start_addr || NULL == _context || _context->heap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* init heap pointer and size, and memset it to 0 */
|
||||||
|
_context->id = id;
|
||||||
|
_context->heap = (umm_block *)start_addr;
|
||||||
|
_context->heap_end = (void *)((uintptr_t)start_addr + size);
|
||||||
|
_context->numblocks = (size / sizeof(umm_block));
|
||||||
|
|
||||||
|
// An option for blocking the zeroing of extra heaps allows for performing
|
||||||
|
// post-crash discovery.
|
||||||
|
if (zero) {
|
||||||
|
memset(_context->heap, 0x00, size);
|
||||||
|
#if (!defined(UMM_INLINE_METRICS) && defined(UMM_STATS)) || defined(UMM_STATS_FULL)
|
||||||
|
memset(&_context->stats, 0x00, sizeof(_context->stats));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Set up internal data structures */
|
||||||
|
umm_init_stage_2(_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void umm_init( void ) {
|
||||||
|
// if (umm_heap) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
for (size_t i = 0; i < UMM_NUM_HEAPS; i++) {
|
||||||
|
heap_context[i].heap = NULL;
|
||||||
|
}
|
||||||
|
memset(&heap_context[0], 0, sizeof(heap_context));
|
||||||
|
umm_init_common( UMM_HEAP_DRAM, (void *)UMM_MALLOC_CFG_HEAP_ADDR, UMM_MALLOC_CFG_HEAP_SIZE, true );
|
||||||
|
// umm_heap = (void *)&heap_context;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef UMM_HEAP_IRAM
|
||||||
|
void umm_init_iram_ex( void *addr, unsigned int size, bool zero ) {
|
||||||
|
/* We need the main, internal heap set up first */
|
||||||
|
UMM_INIT_HEAP;
|
||||||
|
|
||||||
|
umm_init_common(UMM_HEAP_IRAM, addr, size, zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _text_end(void);
|
||||||
|
void umm_init_iram(void) __attribute__((weak));
|
||||||
|
|
||||||
|
/*
|
||||||
|
By using a weak link, it is possible to reduce the IRAM heap size with a
|
||||||
|
user-supplied init function. This would allow the creation of a block of IRAM
|
||||||
|
dedicated to a sketch and possibly used/preserved across reboots.
|
||||||
|
*/
|
||||||
|
void umm_init_iram(void) {
|
||||||
|
umm_init_iram_ex(mmu_sec_heap(), mmu_sec_heap_size(), true);
|
||||||
|
}
|
||||||
|
#endif // #ifdef UMM_HEAP_IRAM
|
||||||
|
|
||||||
|
#ifdef UMM_HEAP_EXTERNAL
|
||||||
|
void umm_init_vm( void *vmaddr, unsigned int vmsize ) {
|
||||||
|
/* We need the main, internal (DRAM) heap set up first */
|
||||||
|
UMM_INIT_HEAP;
|
||||||
|
|
||||||
|
umm_init_common(UMM_HEAP_EXTERNAL, vmaddr, vmsize, true);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------
|
/* ------------------------------------------------------------------------
|
||||||
* Must be called only from within critical sections guarded by
|
* Must be called only from within critical sections guarded by
|
||||||
* UMM_CRITICAL_ENTRY() and UMM_CRITICAL_EXIT().
|
* UMM_CRITICAL_ENTRY() and UMM_CRITICAL_EXIT().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static void umm_free_core( void *ptr ) {
|
static void umm_free_core( umm_heap_context_t *_context, void *ptr ) {
|
||||||
|
|
||||||
uint16_t c;
|
uint16_t c;
|
||||||
|
|
||||||
|
if (NULL == _context) {
|
||||||
|
panic();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
STATS__FREE_REQUEST(id_free);
|
STATS__FREE_REQUEST(id_free);
|
||||||
/*
|
/*
|
||||||
* FIXME: At some point it might be a good idea to add a check to make sure
|
* FIXME: At some point it might be a good idea to add a check to make sure
|
||||||
@ -335,7 +533,7 @@ static void umm_free_core( void *ptr ) {
|
|||||||
|
|
||||||
/* Figure out which block we're in. Note the use of truncated division... */
|
/* Figure out which block we're in. Note the use of truncated division... */
|
||||||
|
|
||||||
c = (((uintptr_t)ptr)-(uintptr_t)(&(umm_heap[0])))/sizeof(umm_block);
|
c = (((uintptr_t)ptr)-(uintptr_t)(&(_context->heap[0])))/sizeof(umm_block);
|
||||||
|
|
||||||
DBGLOG_DEBUG( "Freeing block %6d\n", c );
|
DBGLOG_DEBUG( "Freeing block %6d\n", c );
|
||||||
|
|
||||||
@ -344,7 +542,7 @@ static void umm_free_core( void *ptr ) {
|
|||||||
|
|
||||||
/* Now let's assimilate this block with the next one if possible. */
|
/* Now let's assimilate this block with the next one if possible. */
|
||||||
|
|
||||||
umm_assimilate_up( c );
|
umm_assimilate_up( _context, c );
|
||||||
|
|
||||||
/* Then assimilate with the previous block if possible */
|
/* Then assimilate with the previous block if possible */
|
||||||
|
|
||||||
@ -352,7 +550,7 @@ static void umm_free_core( void *ptr ) {
|
|||||||
|
|
||||||
DBGLOG_DEBUG( "Assimilate down to previous block, which is FREE\n" );
|
DBGLOG_DEBUG( "Assimilate down to previous block, which is FREE\n" );
|
||||||
|
|
||||||
c = umm_assimilate_down(c, UMM_FREELIST_MASK);
|
c = umm_assimilate_down(_context, c, UMM_FREELIST_MASK);
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* The previous block is not a free block, so add this one to the head
|
* The previous block is not a free block, so add this one to the head
|
||||||
@ -376,9 +574,7 @@ static void umm_free_core( void *ptr ) {
|
|||||||
void umm_free( void *ptr ) {
|
void umm_free( void *ptr ) {
|
||||||
UMM_CRITICAL_DECL(id_free);
|
UMM_CRITICAL_DECL(id_free);
|
||||||
|
|
||||||
if (umm_heap == NULL) {
|
UMM_INIT_HEAP;
|
||||||
umm_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we're being asked to free a NULL pointer, well that's just silly! */
|
/* If we're being asked to free a NULL pointer, well that's just silly! */
|
||||||
|
|
||||||
@ -393,7 +589,8 @@ void umm_free( void *ptr ) {
|
|||||||
|
|
||||||
UMM_CRITICAL_ENTRY(id_free);
|
UMM_CRITICAL_ENTRY(id_free);
|
||||||
|
|
||||||
umm_free_core( ptr );
|
/* Need to be in the heap in which this block lives */
|
||||||
|
umm_free_core( umm_get_ptr_context( ptr ), ptr );
|
||||||
|
|
||||||
UMM_CRITICAL_EXIT(id_free);
|
UMM_CRITICAL_EXIT(id_free);
|
||||||
}
|
}
|
||||||
@ -403,7 +600,7 @@ void umm_free( void *ptr ) {
|
|||||||
* UMM_CRITICAL_ENTRY() and UMM_CRITICAL_EXIT().
|
* UMM_CRITICAL_ENTRY() and UMM_CRITICAL_EXIT().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static void *umm_malloc_core( size_t size ) {
|
static void *umm_malloc_core( umm_heap_context_t *_context, size_t size ) {
|
||||||
uint16_t blocks;
|
uint16_t blocks;
|
||||||
uint16_t blockSize = 0;
|
uint16_t blockSize = 0;
|
||||||
|
|
||||||
@ -414,6 +611,11 @@ static void *umm_malloc_core( size_t size ) {
|
|||||||
|
|
||||||
STATS__ALLOC_REQUEST(id_malloc, size);
|
STATS__ALLOC_REQUEST(id_malloc, size);
|
||||||
|
|
||||||
|
if (NULL == _context) {
|
||||||
|
panic();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
blocks = umm_blocks( size );
|
blocks = umm_blocks( size );
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -474,7 +676,8 @@ static void *umm_malloc_core( size_t size ) {
|
|||||||
|
|
||||||
/* Disconnect this block from the FREE list */
|
/* Disconnect this block from the FREE list */
|
||||||
|
|
||||||
umm_disconnect_from_free_list( cf );
|
umm_disconnect_from_free_list( _context, cf );
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
/* It's not an exact fit and we need to split off a block. */
|
/* It's not an exact fit and we need to split off a block. */
|
||||||
@ -484,7 +687,7 @@ static void *umm_malloc_core( size_t size ) {
|
|||||||
* split current free block `cf` into two blocks. The first one will be
|
* split current free block `cf` into two blocks. The first one will be
|
||||||
* returned to user, so it's not free, and the second one will be free.
|
* returned to user, so it's not free, and the second one will be free.
|
||||||
*/
|
*/
|
||||||
umm_split_block( cf, blocks, UMM_FREELIST_MASK /*new block is free*/ );
|
umm_split_block( _context, cf, blocks, UMM_FREELIST_MASK /*new block is free*/ );
|
||||||
|
|
||||||
UMM_FRAGMENTATION_METRIC_ADD(UMM_NBLOCK(cf));
|
UMM_FRAGMENTATION_METRIC_ADD(UMM_NBLOCK(cf));
|
||||||
|
|
||||||
@ -525,9 +728,61 @@ void *umm_malloc( size_t size ) {
|
|||||||
|
|
||||||
void *ptr = NULL;
|
void *ptr = NULL;
|
||||||
|
|
||||||
if (umm_heap == NULL) {
|
UMM_INIT_HEAP;
|
||||||
umm_init();
|
|
||||||
}
|
/*
|
||||||
|
* "Is it safe"
|
||||||
|
*
|
||||||
|
* Is it safe to call from an ISR? Is there a point during a malloc that a
|
||||||
|
* an interrupt and subsequent call to malloc result in undesired results?
|
||||||
|
*
|
||||||
|
* Heap selection in managed by the functions umm_push_heap, umm_pop_heap,
|
||||||
|
* umm_get_current_heap_id, and umm_set_heap_by_id. These functions are
|
||||||
|
* responsible for getting/setting the module static variable umm_heap_cur.
|
||||||
|
* The umm_heap_cur variable is an index that is used to select the current
|
||||||
|
* heap context. Depending on the situation this selection can be overriddened.
|
||||||
|
*
|
||||||
|
* All variables for a specific Heap are in a single structure. `heap_context`
|
||||||
|
* is an array of these structures. Each heap API function uses a function
|
||||||
|
* local variable `_context` to hold a pointer to the selected heap structure.
|
||||||
|
* This local pointer is referenced for all the "selected heap" operations.
|
||||||
|
* Coupled with critical sections around global data should allow the API
|
||||||
|
* functions to be reentrant.
|
||||||
|
*
|
||||||
|
* Using the `_context` name throughout made it easy to incorporate the
|
||||||
|
* context into existing macros.
|
||||||
|
*
|
||||||
|
* For allocating APIs `umm_heap_cur` is used to index and select a value for
|
||||||
|
* `_context`. If an allocation is made from an ISR, this value is ignored and
|
||||||
|
* the heap context for DRAM is loaded. For APIs that require operating on an
|
||||||
|
* existing allcation such as realloc and free, the heap context selected is
|
||||||
|
* done by matching the allocation's address with that of one of the heap
|
||||||
|
* address ranges.
|
||||||
|
*
|
||||||
|
* I think we are safe with multiple heaps when the non32-bit exception
|
||||||
|
* handler is used, as long as interrupts don't get enabled. There was a
|
||||||
|
* window in the Boot ROM "C" Exception Wrapper that would enable interrupts
|
||||||
|
* when running our non32-exception handler; however, that should be resolved
|
||||||
|
* by our replacement wrapper. For more information on exception handling
|
||||||
|
* issues for IRAM see comments above `_set_exception_handler_wrapper()` in
|
||||||
|
* `core_esp8266_non32xfer.cpp`.
|
||||||
|
*
|
||||||
|
* ISRs should not try and change heaps. umm_malloc will ignore the change.
|
||||||
|
* All should be fine as long as the caller puts the heap back the way it was.
|
||||||
|
* On return, everything must be the same. The foreground thread will continue
|
||||||
|
* with the same information that was there before the interrupt. All malloc()
|
||||||
|
* requests made from an ISR are fulfilled with DRAM.
|
||||||
|
*
|
||||||
|
* For umm_malloc, heap selection involves changing a single variable that is
|
||||||
|
* on the calling context stack. From the umm_mallac side, that variable is
|
||||||
|
* used to load a context pointer by index, heap ID. While an umm_malloc API
|
||||||
|
* function is running, all heap related variables are in the context variable
|
||||||
|
* pointer, registers, or the current stack as the request is processed. With
|
||||||
|
* a single variable to reference for heap selection, I think it is unlikely
|
||||||
|
* that umm_malloc can be called, with things in an unusable transition state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
umm_heap_context_t *_context = umm_get_current_heap();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* the very first thing we do is figure out if we're being asked to allocate
|
* the very first thing we do is figure out if we're being asked to allocate
|
||||||
@ -547,7 +802,21 @@ void *umm_malloc( size_t size ) {
|
|||||||
|
|
||||||
UMM_CRITICAL_ENTRY(id_malloc);
|
UMM_CRITICAL_ENTRY(id_malloc);
|
||||||
|
|
||||||
ptr = umm_malloc_core( size );
|
/*
|
||||||
|
* We handle the realloc of an existing IRAM allocation from an ISR with IRAM,
|
||||||
|
* while a new malloc from an ISR will always supply DRAM. That said, realloc
|
||||||
|
* from an ISR is not generally safe without special locking mechanisms and is
|
||||||
|
* not formally supported.
|
||||||
|
*
|
||||||
|
* Additionally, to avoid extending the IRQs disabled period, it is best to
|
||||||
|
* use DRAM for an ISR. Each 16-bit access to IRAM that umm_malloc has to make
|
||||||
|
* requires a pass through the exception handling logic.
|
||||||
|
*/
|
||||||
|
if (UMM_CRITICAL_WITHINISR(id_malloc)) {
|
||||||
|
_context = umm_get_heap_by_id(UMM_HEAP_DRAM);
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr = umm_malloc_core( _context, size );
|
||||||
|
|
||||||
UMM_CRITICAL_EXIT(id_malloc);
|
UMM_CRITICAL_EXIT(id_malloc);
|
||||||
|
|
||||||
@ -568,9 +837,7 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
|
|
||||||
size_t curSize;
|
size_t curSize;
|
||||||
|
|
||||||
if (umm_heap == NULL) {
|
UMM_INIT_HEAP;
|
||||||
umm_init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This code looks after the case of a NULL value for ptr. The ANSI C
|
* This code looks after the case of a NULL value for ptr. The ANSI C
|
||||||
@ -592,6 +859,13 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
* we should operate the same as free.
|
* we should operate the same as free.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Need to be in the heap in which this block lives */
|
||||||
|
umm_heap_context_t *_context = umm_get_ptr_context( ptr );
|
||||||
|
if (NULL == _context) {
|
||||||
|
panic();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if( 0 == size ) {
|
if( 0 == size ) {
|
||||||
DBGLOG_DEBUG( "realloc to 0 size, just free the block\n" );
|
DBGLOG_DEBUG( "realloc to 0 size, just free the block\n" );
|
||||||
STATS__ZERO_ALLOC_REQUEST(id_realloc, size);
|
STATS__ZERO_ALLOC_REQUEST(id_realloc, size);
|
||||||
@ -616,7 +890,7 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
|
|
||||||
/* Figure out which block we're in. Note the use of truncated division... */
|
/* Figure out which block we're in. Note the use of truncated division... */
|
||||||
|
|
||||||
c = (((uintptr_t)ptr)-(uintptr_t)(&(umm_heap[0])))/sizeof(umm_block);
|
c = (((uintptr_t)ptr)-(uintptr_t)(&(_context->heap[0])))/sizeof(umm_block);
|
||||||
|
|
||||||
/* Figure out how big this block is ... the free bit is not set :-) */
|
/* Figure out how big this block is ... the free bit is not set :-) */
|
||||||
|
|
||||||
@ -647,9 +921,21 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
|
|
||||||
DBGLOG_DEBUG( "realloc blocks %d blockSize %d nextBlockSize %d prevBlockSize %d\n", blocks, blockSize, nextBlockSize, prevBlockSize );
|
DBGLOG_DEBUG( "realloc blocks %d blockSize %d nextBlockSize %d prevBlockSize %d\n", blocks, blockSize, nextBlockSize, prevBlockSize );
|
||||||
|
|
||||||
//C This has changed need to review and see if UMM_REALLOC_MINIMIZE_COPY really
|
//C With each upstream update this section should be reevaluated.
|
||||||
//C is that any more. or is it equivalent or close enough to my defrag
|
/*C
|
||||||
//C - mjh
|
*
|
||||||
|
* The `#if defined(UMM_REALLOC_MINIMIZE_COPY)` section tracks the content of
|
||||||
|
* the upstream with some local macros added. Back when I made my 1st update to
|
||||||
|
* umm_malloc PR, I found the upstream had been refactored and removed the
|
||||||
|
* defragmenting properties that were originally present. It took some looking
|
||||||
|
* to see the logic, it didn't have any comments to make it stand out.
|
||||||
|
*
|
||||||
|
* I added the `#elif defined(UMM_REALLOC_DEFRAG)` to recreate and preserve the
|
||||||
|
* defragmenting functionality that was lost. This is the default build option
|
||||||
|
* we have set in `umm_malloc_cfg.h`. I have not done any structured testing to
|
||||||
|
* confirm; however, I think this to be the best option when considering the
|
||||||
|
* amount of reallocates that can occur with the Strings library.
|
||||||
|
*/
|
||||||
#if defined(UMM_REALLOC_MINIMIZE_COPY)
|
#if defined(UMM_REALLOC_MINIMIZE_COPY)
|
||||||
/*
|
/*
|
||||||
* Ok, now that we're here we know how many blocks we want and the current
|
* Ok, now that we're here we know how many blocks we want and the current
|
||||||
@ -701,15 +987,15 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
// Case 3 - prev block NOT free and block + next block fits
|
// Case 3 - prev block NOT free and block + next block fits
|
||||||
} else if ((0 == prevBlockSize) && (blockSize + nextBlockSize) >= blocks) {
|
} else if ((0 == prevBlockSize) && (blockSize + nextBlockSize) >= blocks) {
|
||||||
DBGLOG_DEBUG( "realloc using next block - %i\n", blocks );
|
DBGLOG_DEBUG( "realloc using next block - %i\n", blocks );
|
||||||
umm_assimilate_up( c );
|
umm_assimilate_up( _context, c );
|
||||||
STATS__FREE_BLOCKS_UPDATE( - nextBlockSize );
|
STATS__FREE_BLOCKS_UPDATE( - nextBlockSize );
|
||||||
blockSize += nextBlockSize;
|
blockSize += nextBlockSize;
|
||||||
|
|
||||||
// Case 4 - prev block + block fits
|
// Case 4 - prev block + block fits
|
||||||
} else if ((prevBlockSize + blockSize) >= blocks) {
|
} else if ((prevBlockSize + blockSize) >= blocks) {
|
||||||
DBGLOG_DEBUG( "realloc using prev block - %i\n", blocks );
|
DBGLOG_DEBUG( "realloc using prev block - %i\n", blocks );
|
||||||
umm_disconnect_from_free_list( UMM_PBLOCK(c) );
|
umm_disconnect_from_free_list( _context, UMM_PBLOCK(c) );
|
||||||
c = umm_assimilate_down(c, 0);
|
c = umm_assimilate_down(_context, c, 0);
|
||||||
STATS__FREE_BLOCKS_UPDATE( - prevBlockSize );
|
STATS__FREE_BLOCKS_UPDATE( - prevBlockSize );
|
||||||
STATS__FREE_BLOCKS_ISR_MIN();
|
STATS__FREE_BLOCKS_ISR_MIN();
|
||||||
blockSize += prevBlockSize;
|
blockSize += prevBlockSize;
|
||||||
@ -720,14 +1006,14 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
// Case 5 - prev block + block + next block fits
|
// Case 5 - prev block + block + next block fits
|
||||||
} else if ((prevBlockSize + blockSize + nextBlockSize) >= blocks) {
|
} else if ((prevBlockSize + blockSize + nextBlockSize) >= blocks) {
|
||||||
DBGLOG_DEBUG( "realloc using prev and next block - %d\n", blocks );
|
DBGLOG_DEBUG( "realloc using prev and next block - %d\n", blocks );
|
||||||
umm_assimilate_up( c );
|
umm_assimilate_up( _context, c );
|
||||||
umm_disconnect_from_free_list( UMM_PBLOCK(c) );
|
umm_disconnect_from_free_list( _context, UMM_PBLOCK(c) );
|
||||||
c = umm_assimilate_down(c, 0);
|
c = umm_assimilate_down(_context, c, 0);
|
||||||
STATS__FREE_BLOCKS_UPDATE( - prevBlockSize - nextBlockSize );
|
STATS__FREE_BLOCKS_UPDATE( - prevBlockSize - nextBlockSize );
|
||||||
#ifdef UMM_LIGHTWEIGHT_CPU
|
#ifdef UMM_LIGHTWEIGHT_CPU
|
||||||
if ((prevBlockSize + blockSize + nextBlockSize) > blocks) {
|
if ((prevBlockSize + blockSize + nextBlockSize) > blocks) {
|
||||||
umm_split_block( c, blocks, 0 );
|
umm_split_block( _context, c, blocks, 0 );
|
||||||
umm_free_core( (void *)&UMM_DATA(c+blocks) );
|
umm_free_core( _context, (void *)&UMM_DATA(c+blocks) );
|
||||||
}
|
}
|
||||||
STATS__FREE_BLOCKS_ISR_MIN();
|
STATS__FREE_BLOCKS_ISR_MIN();
|
||||||
blockSize = blocks;
|
blockSize = blocks;
|
||||||
@ -743,16 +1029,16 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
} else {
|
} else {
|
||||||
DBGLOG_DEBUG( "realloc a completely new block %i\n", blocks );
|
DBGLOG_DEBUG( "realloc a completely new block %i\n", blocks );
|
||||||
void *oldptr = ptr;
|
void *oldptr = ptr;
|
||||||
if( (ptr = umm_malloc_core( size )) ) {
|
if( (ptr = umm_malloc_core( _context, size )) ) {
|
||||||
DBGLOG_DEBUG( "realloc %i to a bigger block %i, copy, and free the old\n", blockSize, blocks );
|
DBGLOG_DEBUG( "realloc %i to a bigger block %i, copy, and free the old\n", blockSize, blocks );
|
||||||
UMM_CRITICAL_SUSPEND(id_realloc);
|
UMM_CRITICAL_SUSPEND(id_realloc);
|
||||||
memcpy( ptr, oldptr, curSize );
|
memcpy( ptr, oldptr, curSize );
|
||||||
UMM_CRITICAL_RESUME(id_realloc);
|
UMM_CRITICAL_RESUME(id_realloc);
|
||||||
umm_free_core( oldptr );
|
umm_free_core( _context, oldptr );
|
||||||
} else {
|
} else {
|
||||||
DBGLOG_DEBUG( "realloc %i to a bigger block %i failed - return NULL and leave the old block!\n", blockSize, blocks );
|
DBGLOG_DEBUG( "realloc %i to a bigger block %i failed - return NULL and leave the old block!\n", blockSize, blocks );
|
||||||
/* This space intentionally left blnk */
|
/* This space intentionally left blnk */
|
||||||
STATS__OOM_UPDATE();
|
/* STATS__OOM_UPDATE() has already been called by umm_malloc_core - don't duplicate count */
|
||||||
}
|
}
|
||||||
/* This is not accurate for OOM case; however, it will work for
|
/* This is not accurate for OOM case; however, it will work for
|
||||||
* stopping a call to free before return.
|
* stopping a call to free before return.
|
||||||
@ -786,8 +1072,8 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
* requested number of blocks and add what's left to the free list.
|
* requested number of blocks and add what's left to the free list.
|
||||||
*/
|
*/
|
||||||
if (prevBlockSize && (prevBlockSize + blockSize + nextBlockSize) >= blocks) { // 1
|
if (prevBlockSize && (prevBlockSize + blockSize + nextBlockSize) >= blocks) { // 1
|
||||||
umm_disconnect_from_free_list( UMM_PBLOCK(c) );
|
umm_disconnect_from_free_list( _context, UMM_PBLOCK(c) );
|
||||||
c = umm_assimilate_down(c, 0);
|
c = umm_assimilate_down( _context, c, 0 );
|
||||||
STATS__FREE_BLOCKS_UPDATE( - prevBlockSize );
|
STATS__FREE_BLOCKS_UPDATE( - prevBlockSize );
|
||||||
blockSize += prevBlockSize;
|
blockSize += prevBlockSize;
|
||||||
if (blockSize >= blocks) {
|
if (blockSize >= blocks) {
|
||||||
@ -795,13 +1081,13 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
STATS__FREE_BLOCKS_ISR_MIN();
|
STATS__FREE_BLOCKS_ISR_MIN();
|
||||||
} else {
|
} else {
|
||||||
DBGLOG_DEBUG( "realloc using prev and next block - %d\n", blocks );
|
DBGLOG_DEBUG( "realloc using prev and next block - %d\n", blocks );
|
||||||
umm_assimilate_up( c );
|
umm_assimilate_up( _context, c );
|
||||||
STATS__FREE_BLOCKS_UPDATE( - nextBlockSize );
|
STATS__FREE_BLOCKS_UPDATE( - nextBlockSize );
|
||||||
blockSize += nextBlockSize;
|
blockSize += nextBlockSize;
|
||||||
#ifdef UMM_LIGHTWEIGHT_CPU
|
#ifdef UMM_LIGHTWEIGHT_CPU
|
||||||
if (blockSize > blocks) {
|
if (blockSize > blocks) {
|
||||||
umm_split_block( c, blocks, 0 );
|
umm_split_block( _context, c, blocks, 0 );
|
||||||
umm_free_core( (void *)&UMM_DATA(c+blocks) );
|
umm_free_core( _context, (void *)&UMM_DATA(c+blocks) );
|
||||||
}
|
}
|
||||||
STATS__FREE_BLOCKS_ISR_MIN();
|
STATS__FREE_BLOCKS_ISR_MIN();
|
||||||
blockSize = blocks;
|
blockSize = blocks;
|
||||||
@ -816,22 +1102,22 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
/* This space intentionally left blank */
|
/* This space intentionally left blank */
|
||||||
} else if ((blockSize + nextBlockSize) >= blocks) { // 3
|
} else if ((blockSize + nextBlockSize) >= blocks) { // 3
|
||||||
DBGLOG_DEBUG( "realloc using next block - %d\n", blocks );
|
DBGLOG_DEBUG( "realloc using next block - %d\n", blocks );
|
||||||
umm_assimilate_up( c );
|
umm_assimilate_up( _context, c );
|
||||||
STATS__FREE_BLOCKS_UPDATE( - nextBlockSize );
|
STATS__FREE_BLOCKS_UPDATE( - nextBlockSize );
|
||||||
blockSize += nextBlockSize;
|
blockSize += nextBlockSize;
|
||||||
} else { // 4
|
} else { // 4
|
||||||
DBGLOG_DEBUG( "realloc a completely new block %d\n", blocks );
|
DBGLOG_DEBUG( "realloc a completely new block %d\n", blocks );
|
||||||
void *oldptr = ptr;
|
void *oldptr = ptr;
|
||||||
if( (ptr = umm_malloc_core( size )) ) {
|
if( (ptr = umm_malloc_core( _context, size )) ) {
|
||||||
DBGLOG_DEBUG( "realloc %d to a bigger block %d, copy, and free the old\n", blockSize, blocks );
|
DBGLOG_DEBUG( "realloc %d to a bigger block %d, copy, and free the old\n", blockSize, blocks );
|
||||||
UMM_CRITICAL_SUSPEND(id_realloc);
|
UMM_CRITICAL_SUSPEND(id_realloc);
|
||||||
memcpy( ptr, oldptr, curSize );
|
memcpy( ptr, oldptr, curSize );
|
||||||
UMM_CRITICAL_RESUME(id_realloc);
|
UMM_CRITICAL_RESUME(id_realloc);
|
||||||
umm_free_core( oldptr);
|
umm_free_core( _context, oldptr);
|
||||||
} else {
|
} else {
|
||||||
DBGLOG_DEBUG( "realloc %d to a bigger block %d failed - return NULL and leave the old block!\n", blockSize, blocks );
|
DBGLOG_DEBUG( "realloc %d to a bigger block %d failed - return NULL and leave the old block!\n", blockSize, blocks );
|
||||||
/* This space intentionally left blnk */
|
/* This space intentionally left blnk */
|
||||||
STATS__OOM_UPDATE();
|
/* STATS__OOM_UPDATE() has already been called by umm_malloc_core - don't duplicate count */
|
||||||
}
|
}
|
||||||
/* This is not accurate for OOM case; however, it will work for
|
/* This is not accurate for OOM case; however, it will work for
|
||||||
* stopping a call to free before return.
|
* stopping a call to free before return.
|
||||||
@ -847,16 +1133,16 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
} else {
|
} else {
|
||||||
DBGLOG_DEBUG( "realloc a completely new block %d\n", blocks );
|
DBGLOG_DEBUG( "realloc a completely new block %d\n", blocks );
|
||||||
void *oldptr = ptr;
|
void *oldptr = ptr;
|
||||||
if( (ptr = umm_malloc_core( size )) ) {
|
if( (ptr = umm_malloc_core( _context, size )) ) {
|
||||||
DBGLOG_DEBUG( "realloc %d to a bigger block %d, copy, and free the old\n", blockSize, blocks );
|
DBGLOG_DEBUG( "realloc %d to a bigger block %d, copy, and free the old\n", blockSize, blocks );
|
||||||
UMM_CRITICAL_SUSPEND(id_realloc);
|
UMM_CRITICAL_SUSPEND(id_realloc);
|
||||||
memcpy( ptr, oldptr, curSize );
|
memcpy( ptr, oldptr, curSize );
|
||||||
UMM_CRITICAL_RESUME(id_realloc);
|
UMM_CRITICAL_RESUME(id_realloc);
|
||||||
umm_free_core( oldptr );
|
umm_free_core( _context, oldptr );
|
||||||
} else {
|
} else {
|
||||||
DBGLOG_DEBUG( "realloc %d to a bigger block %d failed - return NULL and leave the old block!\n", blockSize, blocks );
|
DBGLOG_DEBUG( "realloc %d to a bigger block %d failed - return NULL and leave the old block!\n", blockSize, blocks );
|
||||||
/* This space intentionally left blnk */
|
/* This space intentionally left blnk */
|
||||||
STATS__OOM_UPDATE();
|
/* STATS__OOM_UPDATE() has already been called by umm_malloc_core - don't duplicate count */
|
||||||
}
|
}
|
||||||
/* This is not accurate for OOM case; however, it will work for
|
/* This is not accurate for OOM case; however, it will work for
|
||||||
* stopping a call to free before return.
|
* stopping a call to free before return.
|
||||||
@ -870,8 +1156,8 @@ void *umm_realloc( void *ptr, size_t size ) {
|
|||||||
|
|
||||||
if (blockSize > blocks ) {
|
if (blockSize > blocks ) {
|
||||||
DBGLOG_DEBUG( "split and free %d blocks from %d\n", blocks, blockSize );
|
DBGLOG_DEBUG( "split and free %d blocks from %d\n", blocks, blockSize );
|
||||||
umm_split_block( c, blocks, 0 );
|
umm_split_block( _context, c, blocks, 0 );
|
||||||
umm_free_core( (void *)&UMM_DATA(c+blocks) );
|
umm_free_core( _context, (void *)&UMM_DATA(c+blocks) );
|
||||||
}
|
}
|
||||||
|
|
||||||
STATS__FREE_BLOCKS_MIN();
|
STATS__FREE_BLOCKS_MIN();
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user