diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml
index 604caca6cc..13ab0e81cf 100644
--- a/.github/workflows/artifacts.yml
+++ b/.github/workflows/artifacts.yml
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 0
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 417fb63434..167fdff5ad 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -9,15 +9,15 @@ jobs:
uses: rlespinasse/github-slug-action@v4
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Cache pip
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.target }}
@@ -29,7 +29,7 @@ jobs:
pip install wheel
- name: Cache PlatformIO
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.platformio
key: ${{ runner.os }}-platformio
@@ -41,27 +41,13 @@ jobs:
platformio pkg update
PLATFORMIO_BUILD_FLAGS="-DRegulatory_Domain_ISM_2400" pio test -e native
- validation:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v3
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: '3.10'
- - name: Validate target files
- run: |
- cd src
- python python/targets_validator.py
-
targets:
runs-on: ubuntu-latest
outputs:
targets: ${{ steps.set-targets.outputs.targets }}
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- id: set-targets
run: echo "targets=[$( (grep '\[env:.*UART\]' src/targets/unified.ini ; grep -r '\[env:.*STLINK\]' src/targets/) | sed 's/.*://' | sed s/.$// | egrep "(STLINK|UART)" | grep -v DEPRECATED | tr '\n' ',' | sed 's/,$/"\n/' | sed 's/,/","/'g | sed 's/^/"/')]" >> $GITHUB_OUTPUT
@@ -78,15 +64,15 @@ jobs:
uses: rlespinasse/github-slug-action@v4
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Cache pip
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.target }}
@@ -98,7 +84,7 @@ jobs:
pip install wheel
- name: Cache PlatformIO
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ~/.platformio
key: ${{ runner.os }}-platformio
@@ -132,29 +118,38 @@ jobs:
esac
- name: Store Artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
- name: firmware
+ name: temp-${{ matrix.target }}
path: ~/artifacts/**/*
+ retention-days: 1
continue-on-error: true
firmware:
- needs: [build, validation, test]
+ needs: [build, test]
runs-on: ubuntu-latest
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 0
+ - name: Checkout targets repo
+ uses: actions/checkout@v4
+ with:
+ repository: ExpressLRS/targets
+ path: src/hardware
+
- name: Get firmware artifacts
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
- name: firmware
path: dist
+ merge-multiple: true
- name: Copy hardware files to firmware folder
- run: cp -r src/hardware dist/firmware
+ run: |
+ cp -r src/hardware dist/firmware
+ jq 'del(.generic)' src/hardware/targets.json > dist/firmware/hardware/targets.json
- name: Copy Lua to to firmware folder
run: |
@@ -165,7 +160,7 @@ jobs:
run: cd src ; find bootloader -name \*.bin -o -name \*.frk | grep -v src/ | cpio -pdm ../dist/firmware
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: '3.10'
@@ -176,7 +171,7 @@ jobs:
shiv -c flash -o ../../dist/firmware/flasher.pyz pyserial .
- name: Update firmware artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: firmware
path: dist/**/*
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 2607d1b394..018dc92ec2 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -10,7 +10,7 @@ jobs:
issues: write
pull-requests: write
steps:
- - uses: actions/stale@v3
+ - uses: actions/stale@v9
with:
days-before-issue-stale: -1
days-before-issue-close: 12
diff --git a/.gitignore b/.gitignore
index ea7bc2ef5d..0c62043d19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ src/src/fhss_freqs.h
src/.vscode
src/data/*.ini
src/data/*.json
+src/hardware
diff --git a/README.md b/README.md
index 39ef61742e..c63b4fac57 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[![Release](https://img.shields.io/github/v/release/ExpressLRS/ExpressLRS?style=flat-square)](https://github.com/ExpressLRS/ExpressLRS/releases)
-[![Build Status](https://img.shields.io/github/workflow/status/ExpressLRS/ExpressLRS/Build%20ExpressLRS?logo=github&style=flat-square)](https://github.com/ExpressLRS/ExpressLRS/actions)
+[![Build Status](https://img.shields.io/github/actions/workflow/status/ExpressLRS/ExpressLRS/build.yml?logo=github&style=flat-square)](https://github.com/ExpressLRS/ExpressLRS/actions)
[![License](https://img.shields.io/github/license/ExpressLRS/ExpressLRS?style=flat-square)](https://github.com/ExpressLRS/ExpressLRS/blob/master/LICENSE)
[![Stars](https://img.shields.io/github/stars/ExpressLRS/ExpressLRS?style=flat-square)](https://github.com/ExpressLRS/ExpressLRS/stargazers)
[![Chat](https://img.shields.io/discord/596350022191415318?color=%235865F2&logo=discord&logoColor=%23FFFFFF&style=flat-square)](https://discord.gg/expresslrs)
@@ -24,7 +24,7 @@ For general information on the project please refer to our guides on the [websit
## About
-ExpressLRS is an open source Radio Link for Radio Control applications. Designed to be the best FPV Racing link, it is based on the fantastic Semtech **SX127x**/**SX1280** LoRa hardware combined with an Espressif or STM32 Processor. Using LoRa modulation as well as reduced packet size it achieves best in class range and latency. It achieves this using a highly optimized over-the-air packet structure, giving simultaneous range and latency advantages. It supports both 900 MHz and 2.4 GHz links, each with their own benefits. 900 MHz supports a maximum of 200 Hz packet rate, with higher penetration. 2.4 GHz supports a blistering fast 1000 Hz on [EdgeTX](http://edgetx.org/). With over 60 different hardware targets and 13 hardware manufacuturers, the choice of hardware is ever growing, with different hardware suited to different requirements.
+ExpressLRS is an open source Radio Link for Radio Control applications. Designed to be the best FPV Racing link, it is based on the fantastic Semtech **SX127x**/**SX1280** LoRa hardware combined with an Espressif or STM32 Processor. Using LoRa modulation as well as reduced packet size it achieves best in class range and latency. It achieves this using a highly optimized over-the-air packet structure, giving simultaneous range and latency advantages. It supports both 900 MHz and 2.4 GHz links, each with their own benefits. 900 MHz supports a maximum of 200 Hz packet rate, with higher penetration. 2.4 GHz supports a blistering fast 1000 Hz on [EdgeTX](http://edgetx.org/). With over 60 different hardware targets and 13 hardware manufacturers, the choice of hardware is ever growing, with different hardware suited to different requirements.
## Configurator
To configure your ExpressLRS hardware, the ExpressLRS Configurator can be used, which is found here:
@@ -58,6 +58,6 @@ For an exhaustive list of hardware targets and their user guides, check out the
## Developers
-If you are a developer and would like to contribute to the project, feel free to join the [discord](https://discord.gg/expresslrs) and chat about bugs and issues. You can also look for issues at the [GitHub Issue Tracker](https://github.com/ExpressLRS/ExpressLRS/issues). The best thing to do is to a submit a Pull Request to the GitHub Repository.
+If you are a developer and would like to contribute to the project, feel free to join the [discord](https://discord.gg/expresslrs) and chat about bugs and issues. You can also look for issues at the [GitHub Issue Tracker](https://github.com/ExpressLRS/ExpressLRS/issues). The best thing to do is to submit a Pull Request to the GitHub Repository.
![](https://github.com/ExpressLRS/ExpressLRS-Hardware/blob/master/img/community.png?raw=true)
diff --git a/src/.gitignore b/src/.gitignore
index 74fb79857d..14ebea7511 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -8,4 +8,5 @@ python/__pycache__/
*.pyc
super_defines.txt
include/WebContent.h
+include/flashdiscrim.h
.DS_Store
diff --git a/src/hardware/RX/DIY 900 TTGO V1.json b/src/hardware/RX/DIY 900 TTGO V1.json
deleted file mode 100644
index bb66bbbbaa..0000000000
--- a/src/hardware/RX/DIY 900 TTGO V1.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_dio0": 26,
- "radio_miso": 19,
- "radio_mosi": 27,
- "radio_nss": 18,
- "radio_rst": 14,
- "radio_sck": 5,
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [8,12,15],
- "led": 2
-}
diff --git a/src/hardware/RX/DIY 900 TTGO V2.json b/src/hardware/RX/DIY 900 TTGO V2.json
deleted file mode 100644
index 68c922b0ec..0000000000
--- a/src/hardware/RX/DIY 900 TTGO V2.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_dio0": 26,
- "radio_miso": 19,
- "radio_mosi": 27,
- "radio_nss": 18,
- "radio_rst": 23,
- "radio_sck": 5,
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [8,12,15],
- "led": 25
-}
diff --git a/src/hardware/RX/DIY Huzzah RFM95.json b/src/hardware/RX/DIY Huzzah RFM95.json
deleted file mode 100644
index 9a8976bf59..0000000000
--- a/src/hardware/RX/DIY Huzzah RFM95.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_dio0": 5,
- "radio_dio1": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "power_min": 0,
- "power_high": 0,
- "power_max": 0,
- "power_default": 0,
- "power_control": 0,
- "power_values": [15],
- "led": 0
-}
diff --git a/src/hardware/RX/Frank 2400.json b/src/hardware/RX/Frank 2400.json
deleted file mode 100644
index 19e722d1f4..0000000000
--- a/src/hardware/RX/Frank 2400.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 36,
- "radio_dio1": 39,
- "radio_miso": 38,
- "radio_mosi": 23,
- "radio_nss": 10,
- "radio_rst": 9,
- "radio_sck": 18,
- "power_min": 0,
- "power_high": 0,
- "power_max": 0,
- "power_default": 0,
- "power_control": 0,
- "power_values": [13],
- "led_rgb": 2,
- "led_rgb_isgrb": true,
- "ledidx_rgb_status": [0],
- "ledidx_rgb_vtx": [1],
- "ledidx_rgb_boot": [0,1],
- "vtx_nss": 15,
- "vtx_miso": 37,
- "vtx_mosi": 13,
- "vtx_sck": 14,
- "vtx_amp_pwm": 25,
- "vtx_amp_vpd": 35,
- "vtx_amp_vref": 26,
- "vtx_amp_vpd_25mW": [1350,1350,1350,1350],
- "vtx_amp_vpd_100mW": [1600,1600,1600,1600],
- "pwm_outputs": [4, 5, 12, 21, 22, 27, 32, 33],
- "vbat": 34,
- "vbat_offset": 12,
- "vbat_scale": 410,
- "vbat_atten": 0,
- "button": 0
-}
diff --git a/src/hardware/RX/Generic 2400 Diversity PA.json b/src/hardware/RX/Generic 2400 Diversity PA.json
deleted file mode 100644
index f77a955e52..0000000000
--- a/src/hardware/RX/Generic 2400 Diversity PA.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 5,
- "radio_dio1": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "ant_ctrl": 9,
- "power_txen": 10,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 3,
- "power_max": 3,
- "power_default": 3,
- "power_control": 0,
- "power_values": [-10,-6,-3,1],
- "button": 0,
- "led": 16
-}
\ No newline at end of file
diff --git a/src/hardware/RX/Generic 2400 PA RGB.json b/src/hardware/RX/Generic 2400 PA RGB.json
deleted file mode 100644
index 45e20b71a9..0000000000
--- a/src/hardware/RX/Generic 2400 PA RGB.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 5,
- "radio_dio1": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 16,
- "radio_sck": 14,
- "radio_dcdc": true,
- "power_rxen": 9,
- "power_txen": 10,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 3,
- "power_max": 3,
- "power_default": 3,
- "power_control": 0,
- "power_values": [-10,-6,-3,1],
- "button": 0,
- "led_rgb": 2,
- "led_rgb_isgrb": 1
-}
diff --git a/src/hardware/RX/Generic 2400 PA.json b/src/hardware/RX/Generic 2400 PA.json
deleted file mode 100644
index bdb204ebcd..0000000000
--- a/src/hardware/RX/Generic 2400 PA.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 5,
- "radio_dio1": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "power_rxen": 9,
- "power_txen": 10,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 3,
- "power_max": 3,
- "power_default": 3,
- "power_control": 0,
- "power_values": [-10,-6,-3,1],
- "button": 0,
- "led": 16
-}
\ No newline at end of file
diff --git a/src/hardware/RX/Generic 2400 PWMP5.json b/src/hardware/RX/Generic 2400 PWMP5.json
deleted file mode 100644
index 626c838a4c..0000000000
--- a/src/hardware/RX/Generic 2400 PWMP5.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "radio_busy": 5,
- "radio_dio1": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "power_min": 0,
- "power_high": 0,
- "power_max": 0,
- "power_default": 0,
- "power_control": 0,
- "power_values": [13],
- "led": 16,
- "pwm_outputs": [0,1,3,9,10],
- "vbat": 17,
- "vbat_offset": 12,
- "vbat_scale": 410
-}
\ No newline at end of file
diff --git a/src/hardware/RX/Generic 2400 PWMP6.json b/src/hardware/RX/Generic 2400 PWMP6.json
deleted file mode 100644
index bd92e61d58..0000000000
--- a/src/hardware/RX/Generic 2400 PWMP6.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "radio_dio1": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "power_min": 0,
- "power_high": 0,
- "power_max": 0,
- "power_default": 0,
- "power_control": 0,
- "power_values": [13],
- "led": 16,
- "pwm_outputs": [0,1,3,9,10,5],
- "vbat": 17,
- "vbat_offset": 12,
- "vbat_scale": 310
-}
\ No newline at end of file
diff --git a/src/hardware/RX/Generic 2400 PWMP7.json b/src/hardware/RX/Generic 2400 PWMP7.json
deleted file mode 100644
index 4433291910..0000000000
--- a/src/hardware/RX/Generic 2400 PWMP7.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "radio_dio1": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_sck": 14,
- "power_min": 0,
- "power_high": 0,
- "power_max": 0,
- "power_default": 0,
- "power_control": 0,
- "power_values": [13],
- "led": 16,
- "pwm_outputs": [0,1,3,9,10,5,2],
- "vbat": 17,
- "vbat_offset": 12,
- "vbat_scale": 310
-}
\ No newline at end of file
diff --git a/src/hardware/RX/Generic 2400 True Diversity PA PWM 14.json b/src/hardware/RX/Generic 2400 True Diversity PA PWM 14.json
deleted file mode 100644
index aacb6621eb..0000000000
--- a/src/hardware/RX/Generic 2400 True Diversity PA PWM 14.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "radio_miso":35,
- "radio_mosi":32,
- "radio_rst":25,
- "radio_sck":33,
-
- "radio_busy":37,
- "radio_dio1":38,
- "radio_nss":26,
-
- "radio_busy_2":39,
- "radio_dio1_2":34,
- "radio_nss_2":27,
-
- "power_txen": 14,
- "power_txen_2": 12,
- "power_lna_gain": 12,
-
- "power_min": 0,
- "power_high": 3,
- "power_max": 3,
- "power_default": 3,
- "power_control": 0,
- "power_values": [-10,-6,-3,1],
-
- "led_rgb":21,
- "led_rgb_isgrb":true,
- "ledidx_rgb_status":[0],
- "ledidx_rgb_boot":[0],
-
- "pwm_outputs":[13,15,2,0,4,9,10,5,18,23,19,22,3,1],
-
- "vbat": 36,
- "vbat_offset": -197,
- "vbat_scale": 1200,
- "vbat_atten": 0
-}
\ No newline at end of file
diff --git a/src/hardware/RX/Generic 2400 True Diversity PA and VTx.json b/src/hardware/RX/Generic 2400 True Diversity PA and VTx.json
deleted file mode 100644
index ab2e564288..0000000000
--- a/src/hardware/RX/Generic 2400 True Diversity PA and VTx.json
+++ /dev/null
@@ -1,50 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
-
- "radio_miso": 33,
- "radio_mosi": 32,
- "radio_rst": 26,
- "radio_sck": 25,
-
- "radio_busy": 36,
- "radio_dio1": 37,
- "radio_nss": 27,
-
- "radio_busy_2": 39,
- "radio_dio1_2": 34,
- "radio_nss_2": 13,
-
- "power_rxen": 10,
- "power_txen": 14,
- "power_rxen_2": 9,
- "power_txen_2": 15,
- "power_lna_gain": 12,
-
- "power_min": 0,
- "power_high": 3,
- "power_max": 3,
- "power_default": 3,
- "power_control": 0,
- "power_values": [-10,-6,-3,1],
-
- "led_rgb": 22,
- "led_rgb_isgrb": true,
- "ledidx_rgb_status": [0],
- "ledidx_rgb_vtx": [1],
- "ledidx_rgb_boot": [0,1],
-
- "vtx_nss": 19,
- "vtx_miso": 23,
- "vtx_mosi": 18,
- "vtx_sck": 5,
-
- "vtx_amp_pwm": 12,
- "vtx_amp_vpd": 4,
- "vtx_amp_vref": 2,
-
- "vtx_amp_vpd_25mW": [600,600,600,600],
- "vtx_amp_vpd_100mW": [1500,1500,1500,1500],
-
- "button": 0
-}
diff --git a/src/hardware/RX/Generic 2400 True Diversity PA.json b/src/hardware/RX/Generic 2400 True Diversity PA.json
deleted file mode 100644
index 389ec7c711..0000000000
--- a/src/hardware/RX/Generic 2400 True Diversity PA.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
-
- "radio_miso": 33,
- "radio_mosi": 32,
- "radio_rst": 26,
- "radio_sck": 25,
-
- "radio_busy": 36,
- "radio_dio1": 37,
- "radio_nss": 27,
-
- "radio_busy_2": 39,
- "radio_dio1_2": 34,
- "radio_nss_2": 13,
-
- "power_rxen": 10,
- "power_txen": 14,
- "power_rxen_2": 9,
- "power_txen_2": 15,
- "power_lna_gain": 12,
-
- "power_min": 0,
- "power_high": 3,
- "power_max": 3,
- "power_default": 3,
- "power_control": 0,
- "power_values": [-10,-6,-3,1],
-
- "led_rgb": 22,
- "led_rgb_isgrb": true,
- "ledidx_rgb_status": [0],
- "ledidx_rgb_boot": [0],
-
- "button": 0
-}
diff --git a/src/hardware/RX/Generic 2400 True Diversity PWM 16.json b/src/hardware/RX/Generic 2400 True Diversity PWM 16.json
deleted file mode 100644
index 2971b4d70b..0000000000
--- a/src/hardware/RX/Generic 2400 True Diversity PWM 16.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "radio_miso":35,
- "radio_mosi":32,
- "radio_rst":25,
- "radio_sck":33,
-
- "radio_busy":37,
- "radio_dio1":38,
- "radio_nss":26,
-
- "radio_busy_2":39,
- "radio_dio1_2":34,
- "radio_nss_2":27,
-
- "power_min":0,
- "power_high":0,
- "power_max":0,
- "power_default":0,
- "power_control":0,
- "power_values":[13],
-
- "led_rgb":21,
- "led_rgb_isgrb":true,
- "ledidx_rgb_status":[0],
- "ledidx_rgb_boot":[0],
-
- "pwm_outputs":[14,12,13,15,2,0,4,9,10,5,18,23,19,22,3,1],
-
- "vbat": 36,
- "vbat_offset": -197,
- "vbat_scale": 1200,
- "vbat_atten": 0
-}
diff --git a/src/hardware/RX/Generic 2400 True Diversity and VTx.json b/src/hardware/RX/Generic 2400 True Diversity and VTx.json
deleted file mode 100644
index b45eab3661..0000000000
--- a/src/hardware/RX/Generic 2400 True Diversity and VTx.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
-
- "radio_miso": 33,
- "radio_mosi": 32,
- "radio_rst": 26,
- "radio_sck": 25,
-
- "radio_busy": 36,
- "radio_dio1": 37,
- "radio_nss": 27,
-
- "radio_busy_2": 39,
- "radio_dio1_2": 34,
- "radio_nss_2": 13,
-
- "power_min": 0,
- "power_high": 0,
- "power_max": 0,
- "power_default": 0,
- "power_control": 0,
- "power_values": [13],
-
- "led_rgb": 22,
- "led_rgb_isgrb": true,
- "ledidx_rgb_status": [0],
- "ledidx_rgb_vtx": [1],
- "ledidx_rgb_boot": [0,1],
-
- "vtx_nss": 19,
- "vtx_miso": 23,
- "vtx_mosi": 18,
- "vtx_sck": 5,
-
- "vtx_amp_pwm": 12,
- "vtx_amp_vpd": 4,
- "vtx_amp_vref": 2,
-
- "vtx_amp_vpd_25mW": [600,600,600,600],
- "vtx_amp_vpd_100mW": [1500,1500,1500,1500],
-
- "button": 0
-}
diff --git a/src/hardware/RX/Generic 2400 Whoop Rx and VTx.json b/src/hardware/RX/Generic 2400 Whoop Rx and VTx.json
deleted file mode 100644
index 803bdc86f5..0000000000
--- a/src/hardware/RX/Generic 2400 Whoop Rx and VTx.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
-
- "radio_miso": 33,
- "radio_mosi": 32,
- "radio_rst": 26,
- "radio_sck": 25,
-
- "radio_busy": 36,
- "radio_dio1": 37,
- "radio_nss": 27,
-
- "power_min": 0,
- "power_high": 0,
- "power_max": 0,
- "power_default": 0,
- "power_control": 0,
- "power_values": [13],
-
- "led_rgb": 22,
- "led_rgb_isgrb": true,
- "ledidx_rgb_status": [0],
- "ledidx_rgb_vtx": [1],
- "ledidx_rgb_boot": [0,1],
-
- "vtx_nss": 19,
- "vtx_miso": 23,
- "vtx_mosi": 18,
- "vtx_sck": 5,
-
- "vtx_amp_pwm": 12,
- "vtx_amp_vpd": 4,
- "vtx_amp_vref": 2,
-
- "vtx_amp_vpd_25mW": [600,600,600,600],
- "vtx_amp_vpd_100mW": [1500,1500,1500,1500],
-
- "button": 0
-}
diff --git a/src/hardware/RX/Generic 2400.json b/src/hardware/RX/Generic 2400.json
deleted file mode 100644
index 6322d7f8a7..0000000000
--- a/src/hardware/RX/Generic 2400.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 5,
- "radio_dio1": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "power_min": 0,
- "power_high": 0,
- "power_max": 0,
- "power_default": 0,
- "power_control": 0,
- "power_values": [13],
- "led": 16,
- "button": 0
-}
\ No newline at end of file
diff --git a/src/hardware/RX/Generic 900 PWMP.json b/src/hardware/RX/Generic 900 PWMP.json
deleted file mode 100644
index 32ffeadc54..0000000000
--- a/src/hardware/RX/Generic 900 PWMP.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "radio_dio0": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [8,12,15],
- "led": 16,
- "pwm_outputs": [0,1,3,5,9,10]
-}
\ No newline at end of file
diff --git a/src/hardware/RX/Generic 900 True Diversity PA.json b/src/hardware/RX/Generic 900 True Diversity PA.json
deleted file mode 100644
index f1730376f5..0000000000
--- a/src/hardware/RX/Generic 900 True Diversity PA.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
-
- "radio_miso": 33,
- "radio_mosi": 32,
- "radio_rst": 26,
- "radio_sck": 25,
-
- "radio_dio0": 36,
- "radio_dio1": 37,
- "radio_nss": 27,
-
- "radio_dio0_2": 39,
- "radio_dio1_2": 34,
- "radio_nss_2": 13,
-
- "power_rxen": 10,
- "power_txen": 14,
- "power_rxen_2": 9,
- "power_txen_2": 15,
-
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [8,12,15],
-
- "led_rgb": 22,
- "led_rgb_isgrb": true,
- "ledidx_rgb_status": [0],
- "ledidx_rgb_boot": [0],
-
- "button": 0
-}
diff --git a/src/hardware/RX/Generic 900 True Diversity PWM 16.json b/src/hardware/RX/Generic 900 True Diversity PWM 16.json
deleted file mode 100644
index 2d445e6ef4..0000000000
--- a/src/hardware/RX/Generic 900 True Diversity PWM 16.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "radio_miso":35,
- "radio_mosi":32,
- "radio_rst":25,
- "radio_sck":33,
-
- "radio_dio0":37,
- "radio_dio1":38,
- "radio_nss":26,
-
- "radio_dio0_2":39,
- "radio_dio1_2":34,
- "radio_nss_2":27,
-
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [8,12,15],
-
- "led_rgb":21,
- "led_rgb_isgrb":true,
- "ledidx_rgb_status":[0],
- "ledidx_rgb_boot":[0],
-
- "pwm_outputs":[14,12,13,15,2,0,4,9,10,5,18,23,19,22,3,1],
-
- "vbat": 36,
- "vbat_offset": -197,
- "vbat_scale": 1200,
- "vbat_atten": 0
-}
diff --git a/src/hardware/RX/Generic 900.json b/src/hardware/RX/Generic 900.json
deleted file mode 100644
index 3fb2d55b05..0000000000
--- a/src/hardware/RX/Generic 900.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_dio0": 4,
- "radio_dio1": 5,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [8,12,15],
- "led": 16,
- "button": 0
-}
diff --git a/src/hardware/RX/Namimno FLASH 2400 PA.json b/src/hardware/RX/Namimno FLASH 2400 PA.json
deleted file mode 100644
index b3c22c0949..0000000000
--- a/src/hardware/RX/Namimno FLASH 2400 PA.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 5,
- "radio_dio1": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "ant_ctrl": 0,
- "power_txen": 9,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 3,
- "power_max": 3,
- "power_default": 3,
- "power_control": 0,
- "power_values": [-15,-10,-7,3],
- "led": 16
-}
\ No newline at end of file
diff --git a/src/hardware/RX/Namimno VOYAGER 900.json b/src/hardware/RX/Namimno VOYAGER 900.json
deleted file mode 100644
index c41890b0b6..0000000000
--- a/src/hardware/RX/Namimno VOYAGER 900.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_dio0": 5,
- "radio_dio1": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [8,12,15],
- "button": 0,
- "led": 16
-}
\ No newline at end of file
diff --git a/src/hardware/RX/Radiomaster 2400 ER8G.json b/src/hardware/RX/Radiomaster 2400 ER8G.json
deleted file mode 100644
index 9e57f656c8..0000000000
--- a/src/hardware/RX/Radiomaster 2400 ER8G.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
-
- "radio_busy": 36,
- "radio_dio1": 37,
- "radio_miso": 33,
- "radio_mosi": 32,
- "radio_nss": 27,
- "radio_rst": 13,
- "radio_sck": 25,
- "radio_dcdc": true,
-
- "ant_ctrl": 5,
-
- "power_txen": 26,
- "power_min": 0,
- "power_high": 3,
- "power_max": 3,
- "power_default": 3,
- "power_control": 0,
- "power_values": [-5,-4,-2,-1],
-
- "pwm_outputs": [14,12,15,2,4,9,19,22],
-
- "led_rgb": 21,
- "led_rgb_isgrb": true,
- "i2c_sda": 23,
- "i2c_scl": 18,
- "vbat": 39,
- "button": 0,
- "vbat_scale": 700,
- "vbat_offset": -30,
- "vbat_atten": 7
-}
diff --git a/src/hardware/RX/Vario 2400.json b/src/hardware/RX/Vario 2400.json
deleted file mode 100644
index 3452005695..0000000000
--- a/src/hardware/RX/Vario 2400.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "radio_busy": 36,
- "radio_dio1": 37,
- "radio_miso": 33,
- "radio_mosi": 32,
- "radio_nss": 27,
- "radio_rst": 13,
- "radio_sck": 25,
- "radio_dcdc": true,
- "ant_ctrl": 5,
- "power_txen": 26,
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-4,-2,2],
- "i2c_sda": 23,
- "i2c_scl": 18,
- "led": 21,
- "pwm_outputs": [14,1,3,15,2,4],
- "vbat": 39,
- "button": 0,
- "vbat_scale": 1024,
- "vbat_offset": -62,
- "vbat_atten": 7
-}
\ No newline at end of file
diff --git a/src/hardware/RX/iFlight 2400.json b/src/hardware/RX/iFlight 2400.json
deleted file mode 100644
index c536cc8785..0000000000
--- a/src/hardware/RX/iFlight 2400.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 4,
- "radio_dio1": 5,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "radio_dcdc": true,
- "power_rxen": 9,
- "power_txen": 10,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 3,
- "power_max": 3,
- "power_default": 3,
- "power_control": 0,
- "power_values": [-4,-2,2,5],
- "led": 16,
- "button": 0
-}
\ No newline at end of file
diff --git a/src/hardware/TX/AXIS Thor 2400.json b/src/hardware/TX/AXIS Thor 2400.json
deleted file mode 100644
index 805065f7d0..0000000000
--- a/src/hardware/TX/AXIS Thor 2400.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 6,
- "power_max": 6,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-16,-12,-9,-6,-2,0,7],
- "five_way1": 39,
- "five_way2": 35,
- "five_way3": 34,
- "led_rgb": 25,
- "led_rgb_isgrb": true,
- "screen_cs": 22,
- "screen_dc": 12,
- "screen_mosi": 16,
- "screen_rst": 0,
- "screen_sck": 17,
- "screen_type": 4,
- "screen_bl": 15,
- "use_backpack": true,
- "debug_backpack_baud": 420000,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "i2c_scl": 32,
- "i2c_sda": 33,
- "misc_gsensor_int": 37,
- "gsensor_stk8xxx": true,
- "thermal_lm75a": true
-}
\ No newline at end of file
diff --git a/src/hardware/TX/BETAFPV 2400 LiteRadio 3.json b/src/hardware/TX/BETAFPV 2400 LiteRadio 3.json
deleted file mode 100644
index 70d4cefc19..0000000000
--- a/src/hardware/TX/BETAFPV 2400 LiteRadio 3.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_dio2": 22,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 4,
- "power_max": 4,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-17,-14,-10,-7,-2],
- "use_backpack": false,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 16,
- "debug_backpack_tx": 17
-}
\ No newline at end of file
diff --git a/src/hardware/TX/BETAFPV 2400 Micro 1W.json b/src/hardware/TX/BETAFPV 2400 Micro 1W.json
deleted file mode 100644
index a4b6fdf315..0000000000
--- a/src/hardware/TX/BETAFPV 2400 Micro 1W.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 1,
- "power_high": 6,
- "power_max": 6,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-15,-12,-7,-4,2],
- "joystick": 25,
- "joystick_values": [2839,2191,1616,3511,0,4095],
- "led_rgb": 16,
- "led_rgb_isgrb": true,
- "screen_sck": 32,
- "screen_sda": 22,
- "screen_type": 1,
- "screen_reversed": 1,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "misc_fan_en": 17
-}
\ No newline at end of file
diff --git a/src/hardware/TX/BETAFPV 2400 Micro.json b/src/hardware/TX/BETAFPV 2400 Micro.json
deleted file mode 100644
index d5b7bbad65..0000000000
--- a/src/hardware/TX/BETAFPV 2400 Micro.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 5,
- "power_max": 5,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-15,-11,-8,-4,3],
- "joystick": 25,
- "joystick_values": [2839,2191,1616,3511,0,4095],
- "led_rgb": 16,
- "led_rgb_isgrb": true,
- "screen_sck": 32,
- "screen_sda": 22,
- "screen_type": 1,
- "screen_reversed": 1,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "misc_fan_en": 17
-}
\ No newline at end of file
diff --git a/src/hardware/TX/BETAFPV 2400 Nano.json b/src/hardware/TX/BETAFPV 2400 Nano.json
deleted file mode 100644
index 394bcca8c6..0000000000
--- a/src/hardware/TX/BETAFPV 2400 Nano.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 5,
- "power_max": 5,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-15,-13,-9,-4,3],
- "button": 25,
- "led_blue": 17,
- "led_green": 16,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1
-}
\ No newline at end of file
diff --git a/src/hardware/TX/BETAFPV 900 Micro.json b/src/hardware/TX/BETAFPV 900 Micro.json
deleted file mode 100644
index 0496b6db6d..0000000000
--- a/src/hardware/TX/BETAFPV 900 Micro.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_dio0": 4,
- "radio_dio1": 2,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_rfo_hf": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_min": 3,
- "power_high": 5,
- "power_max": 5,
- "power_default": 3,
- "power_control": 0,
- "power_values": [0,3,8],
- "joystick": 25,
- "joystick_values": [2839,2191,1616,3511,0,4095],
- "led_rgb": 16,
- "led_rgb_isgrb": true,
- "screen_sck": 32,
- "screen_sda": 22,
- "screen_type": 1,
- "screen_reversed": 1,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "misc_fan_en": 17
-}
\ No newline at end of file
diff --git a/src/hardware/TX/BETAFPV 900 Nano.json b/src/hardware/TX/BETAFPV 900 Nano.json
deleted file mode 100644
index 6968b0d21a..0000000000
--- a/src/hardware/TX/BETAFPV 900 Nano.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_dio0": 4,
- "radio_dio1": 2,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_rfo_hf": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_min": 3,
- "power_high": 5,
- "power_max": 5,
- "power_default": 3,
- "power_control": 0,
- "power_values": [0,3,8],
- "button": 25,
- "led_blue": 17,
- "led_green": 16,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1
-}
\ No newline at end of file
diff --git a/src/hardware/TX/DIY 2400 BLE Joystick.json b/src/hardware/TX/DIY 2400 BLE Joystick.json
deleted file mode 100644
index b74ff9ed32..0000000000
--- a/src/hardware/TX/DIY 2400 BLE Joystick.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13
-}
\ No newline at end of file
diff --git a/src/hardware/TX/DIY 2400 DupleTX ESP.json b/src/hardware/TX/DIY 2400 DupleTX ESP.json
deleted file mode 100644
index b76d086134..0000000000
--- a/src/hardware/TX/DIY 2400 DupleTX ESP.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 5,
- "radio_dio1": 4,
- "radio_miso": 12,
- "radio_mosi": 13,
- "radio_nss": 15,
- "radio_rst": 2,
- "radio_sck": 14,
- "radio_dcdc": false,
- "power_rxen": 9,
- "power_txen": 10,
- "power_lna_gain": 0,
- "power_min": 0,
- "power_high": 1,
- "power_max": 1,
- "power_default": 0,
- "power_control": 0,
- "power_values": [10,13],
- "button": 0,
- "led_red": 16,
- "led_red_invert": false
-}
\ No newline at end of file
diff --git a/src/hardware/TX/DIY 2400 DupleTX.json b/src/hardware/TX/DIY 2400 DupleTX.json
deleted file mode 100644
index f87f47a7ed..0000000000
--- a/src/hardware/TX/DIY 2400 DupleTX.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_dio2": 33,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 4,
- "power_max": 4,
- "power_default": 2,
- "power_pdet": 35,
- "power_pdet_intercept": 1.5,
- "power_pdet_slope": 0.032,
- "power_control": 0,
- "power_values": [-17,-13,-9,-6,-2],
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 16,
- "debug_backpack_tx": 17,
- "backpack_boot": 25,
- "backpack_en": 15
-}
\ No newline at end of file
diff --git a/src/hardware/TX/DIY 2400 E28.json b/src/hardware/TX/DIY 2400 E28.json
deleted file mode 100644
index 036d577113..0000000000
--- a/src/hardware/TX/DIY 2400 E28.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 4,
- "power_max": 4,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-15,-11,-8,-5,-1],
- "led_rgb": 15,
- "led_rgb_isgrb": true,
- "screen_cs": 2,
- "screen_dc": 22,
- "screen_mosi": 32,
- "screen_rst": 16,
- "screen_sck": 33,
- "screen_sda": 32,
- "screen_type": 0,
- "use_backpack": false,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "misc_fan_en": 17
-}
\ No newline at end of file
diff --git a/src/hardware/TX/DIY 2400 F27.json b/src/hardware/TX/DIY 2400 F27.json
deleted file mode 100644
index c9c4b01851..0000000000
--- a/src/hardware/TX/DIY 2400 F27.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 4,
- "power_max": 4,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-4,0,3,6,12],
- "led_rgb": 15,
- "led_rgb_isgrb": true,
- "screen_cs": 2,
- "screen_dc": 22,
- "screen_mosi": 32,
- "screen_rst": 16,
- "screen_sck": 33,
- "screen_sda": 32,
- "screen_type": 0,
- "use_backpack": false,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "misc_fan_en": 17
-}
\ No newline at end of file
diff --git a/src/hardware/TX/DIY 2400 Mini.json b/src/hardware/TX/DIY 2400 Mini.json
deleted file mode 100644
index 0c1b41e168..0000000000
--- a/src/hardware/TX/DIY 2400 Mini.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_lna_gain": 0,
- "power_min": 0,
- "power_high": 1,
- "power_max": 1,
- "power_default": 1,
- "power_control": 0,
- "power_values": [8,13],
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1
-}
\ No newline at end of file
diff --git a/src/hardware/TX/DIY 900 E19.json b/src/hardware/TX/DIY 900 E19.json
deleted file mode 100644
index ab80f8a19a..0000000000
--- a/src/hardware/TX/DIY 900 E19.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "serial_rx": 2,
- "serial_tx": 2,
- "radio_dio0": 26,
- "radio_dio1": 25,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 13,
- "power_txen": 12,
- "power_min": 2,
- "power_high": 4,
- "power_max": 6,
- "power_default": 2,
- "power_control": 0,
- "power_values": [2,5,8,12,15],
- "led": 27
-}
\ No newline at end of file
diff --git a/src/hardware/TX/DIY 900 RFM95.json b/src/hardware/TX/DIY 900 RFM95.json
deleted file mode 100644
index 12135beb56..0000000000
--- a/src/hardware/TX/DIY 900 RFM95.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "serial_rx": 2,
- "serial_tx": 2,
- "radio_dio0": 26,
- "radio_dio1": 25,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 13,
- "power_txen": 12,
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [8,12,15],
- "led": 27
-}
\ No newline at end of file
diff --git a/src/hardware/TX/DIY 900 TTGO V1.json b/src/hardware/TX/DIY 900 TTGO V1.json
deleted file mode 100644
index 7782d23eab..0000000000
--- a/src/hardware/TX/DIY 900 TTGO V1.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_dio0": 26,
- "radio_miso": 19,
- "radio_mosi": 27,
- "radio_nss": 18,
- "radio_rst": 14,
- "radio_sck": 5,
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [8,12,15],
- "button": 0,
- "led": 2,
- "screen_rst": 16,
- "screen_sck": 15,
- "screen_sda": 4,
- "screen_type": 1
-}
\ No newline at end of file
diff --git a/src/hardware/TX/DIY 900 TTGO V2.json b/src/hardware/TX/DIY 900 TTGO V2.json
deleted file mode 100644
index 0b71f67ae4..0000000000
--- a/src/hardware/TX/DIY 900 TTGO V2.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_dio0": 26,
- "radio_miso": 19,
- "radio_mosi": 27,
- "radio_nss": 18,
- "radio_rst": 23,
- "radio_sck": 5,
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [8,12,15],
- "screen_sck": 22,
- "screen_sda": 21,
- "screen_type": 1,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "led": 25
-}
\ No newline at end of file
diff --git a/src/hardware/TX/EMAX 2400 Nano.json b/src/hardware/TX/EMAX 2400 Nano.json
deleted file mode 100644
index 8b70ff805c..0000000000
--- a/src/hardware/TX/EMAX 2400 Nano.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 6,
- "power_max": 6,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-15,-12,-9,-4,-1,3],
- "led_rgb": 12,
- "led_rgb_isgrb": true,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 16,
- "debug_backpack_tx": 17,
- "backpack_boot": 15,
- "backpack_en": 25,
- "passthrough_baud": 230400,
- "misc_fan_en": 22
-}
diff --git a/src/hardware/TX/EMAX 2400 OLED.json b/src/hardware/TX/EMAX 2400 OLED.json
deleted file mode 100644
index c49d351ca5..0000000000
--- a/src/hardware/TX/EMAX 2400 OLED.json
+++ /dev/null
@@ -1,36 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 6,
- "power_max": 6,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-15,-12,-9,-4,-1,3],
- "joystick": 35,
- "joystick_values": [1905, 1160, 580, 2580, 0, 4095],
- "led_rgb": 12,
- "led_rgb_isgrb": true,
- "screen_sck": 32,
- "screen_sda": 33,
- "screen_type": 1,
- "screen_reversed": 1,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 16,
- "debug_backpack_tx": 17,
- "backpack_boot": 15,
- "backpack_en": 25,
- "passthrough_baud": 230400,
- "misc_fan_en": 22
-}
diff --git a/src/hardware/TX/EMAX 900 Nano.json b/src/hardware/TX/EMAX 900 Nano.json
deleted file mode 100644
index aa030f3897..0000000000
--- a/src/hardware/TX/EMAX 900 Nano.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_dio0": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 12,
- "power_min": 0,
- "power_high": 7,
- "power_max": 7,
- "power_default": 2,
- "power_control": 3,
- "power_values": [10,20,30,40,60,110,150,225],
- "power_apc2": 26,
- "led_rgb": 27,
- "led_rgb_isgrb": true,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 16,
- "debug_backpack_tx": 17,
- "backpack_boot": 15,
- "backpack_en": 25,
- "passthrough_baud": 230400,
- "misc_fan_en": 32
-}
\ No newline at end of file
diff --git a/src/hardware/TX/EMAX 900 OLED.json b/src/hardware/TX/EMAX 900 OLED.json
deleted file mode 100644
index 0b928e058a..0000000000
--- a/src/hardware/TX/EMAX 900 OLED.json
+++ /dev/null
@@ -1,34 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_dio0": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 12,
- "power_min": 0,
- "power_high": 7,
- "power_max": 7,
- "power_default": 2,
- "power_control": 3,
- "power_values": [30,40,50,60,80,90,130,225],
- "power_apc2": 26,
- "joystick": 33,
- "joystick_values": [2010, 1230, 635, 2730, 0, 4095],
- "led_rgb": 27,
- "led_rgb_isgrb": true,
- "screen_sck": 21,
- "screen_sda": 22,
- "screen_type": 1,
- "screen_reversed": 1,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 16,
- "debug_backpack_tx": 17,
- "backpack_boot": 15,
- "backpack_en": 25,
- "passthrough_baud": 230400,
- "misc_fan_en": 32
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Generic 2400 Gemini.json b/src/hardware/TX/Generic 2400 Gemini.json
deleted file mode 100644
index fd34c19e43..0000000000
--- a/src/hardware/TX/Generic 2400 Gemini.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
-
- "radio_miso": 33,
- "radio_mosi": 32,
- "radio_rst": 26,
- "radio_sck": 25,
-
- "radio_busy": 36,
- "radio_dio1": 37,
- "radio_nss": 27,
-
- "radio_busy_2": 39,
- "radio_dio1_2": 34,
- "radio_nss_2": 13,
-
- "power_rxen": 10,
- "power_txen": 14,
- "power_rxen_2": 9,
- "power_txen_2": 15,
-
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 4,
- "power_max": 4,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-15,-11,-8,-5,-1],
-
- "led_rgb": 22,
- "led_rgb_isgrb": true,
-
- "radio_dcdc": true,
-
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 18,
- "debug_backpack_tx": 5,
- "backpack_boot": 23,
- "backpack_en": 19
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Generic 900 Gemini.json b/src/hardware/TX/Generic 900 Gemini.json
deleted file mode 100644
index 58445e0795..0000000000
--- a/src/hardware/TX/Generic 900 Gemini.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
-
- "radio_miso": 33,
- "radio_mosi": 32,
- "radio_rst": 26,
- "radio_sck": 25,
-
- "radio_dio0": 36,
- "radio_dio1": 37,
- "radio_nss": 27,
-
- "radio_dio0_2": 39,
- "radio_dio1_2": 34,
- "radio_nss_2": 13,
-
- "power_rxen": 10,
- "power_txen": 14,
- "power_rxen_2": 9,
- "power_txen_2": 15,
-
- "power_min": 0,
- "power_high": 2,
- "power_max": 2,
- "power_default": 2,
- "power_control": 0,
- "power_values": [8,12,15],
-
- "led_rgb": 22,
- "led_rgb_isgrb": true,
- "ledidx_rgb_status": [0],
- "ledidx_rgb_boot": [0],
-
- "use_backpack": true,
- "debug_backpack_baud": 420000,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "backpack_boot": 23,
- "backpack_en": 19
-}
\ No newline at end of file
diff --git a/src/hardware/TX/HGLRC Hermes 2400.json b/src/hardware/TX/HGLRC Hermes 2400.json
deleted file mode 100644
index 9332e049f1..0000000000
--- a/src/hardware/TX/HGLRC Hermes 2400.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 4,
- "power_max": 4,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-15,-11,-8,-4],
- "use_backpack": false,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1
-}
\ No newline at end of file
diff --git a/src/hardware/TX/HappyModel ES24 Pro.json b/src/hardware/TX/HappyModel ES24 Pro.json
deleted file mode 100644
index 38005bb061..0000000000
--- a/src/hardware/TX/HappyModel ES24 Pro.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 1,
- "power_high": 6,
- "power_max": 6,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-15,-12,-7,-4,2],
- "led_rgb": 15,
- "led_rgb_isgrb": true,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "misc_fan_en": 17
-}
\ No newline at end of file
diff --git a/src/hardware/TX/HappyModel ES24.json b/src/hardware/TX/HappyModel ES24.json
deleted file mode 100644
index 6d3d5e2ca2..0000000000
--- a/src/hardware/TX/HappyModel ES24.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 4,
- "power_max": 4,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-17,-13,-9,-6,-2],
- "led_rgb": 15,
- "led_rgb_isgrb": true,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "misc_fan_en": 17
-}
\ No newline at end of file
diff --git a/src/hardware/TX/HappyModel ES900.json b/src/hardware/TX/HappyModel ES900.json
deleted file mode 100644
index 702f0124c8..0000000000
--- a/src/hardware/TX/HappyModel ES900.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "serial_rx": 2,
- "serial_tx": 2,
- "radio_dio0": 26,
- "radio_dio1": 25,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_apc2": 25,
- "power_rxen": 13,
- "power_txen": 12,
- "power_min": 0,
- "power_high": 4,
- "power_max": 6,
- "power_default": 2,
- "power_control": 3,
- "power_values": [41,60,73,90,110,132,190],
- "led_rgb": 27,
- "led_rgb_isgrb": true,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "misc_fan_en": 17
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Jumper AION Nano.json b/src/hardware/TX/Jumper AION Nano.json
deleted file mode 100644
index 8366a66646..0000000000
--- a/src/hardware/TX/Jumper AION Nano.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 5,
- "power_max": 5,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-15,-11,-8,-4,3],
- "joystick": 35,
- "joystick_values": [870,600,230,70,0,4095],
- "led_rgb": 15,
- "led_rgb_isgrb": true,
- "screen_sck": 33,
- "screen_sda": 32,
- "screen_type": 1,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Jumper T-20 2400.json b/src/hardware/TX/Jumper T-20 2400.json
deleted file mode 100644
index d1111a3b8f..0000000000
--- a/src/hardware/TX/Jumper T-20 2400.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_enable": 25,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 1,
- "power_high": 6,
- "power_max": 6,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-16,-13,-9,-5,-3,3]
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Jumper T-20 900.json b/src/hardware/TX/Jumper T-20 900.json
deleted file mode 100644
index 5b71e44bce..0000000000
--- a/src/hardware/TX/Jumper T-20 900.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_dio0": 26,
- "radio_dio1": 25,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 13,
- "power_txen": 12,
- "power_min": 4,
- "power_high": 6,
- "power_max": 6,
- "power_default": 4,
- "power_control": 0,
- "power_values": [0,3,15]
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Jumper T-Lite.json b/src/hardware/TX/Jumper T-Lite.json
deleted file mode 100644
index 5499817652..0000000000
--- a/src/hardware/TX/Jumper T-Lite.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_dio2": 22,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 3,
- "power_max": 3,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-11,-7,-4,1],
- "use_backpack": false,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 13,
- "debug_backpack_tx": 12
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Jumper T-Pro.json b/src/hardware/TX/Jumper T-Pro.json
deleted file mode 100644
index 81059137a9..0000000000
--- a/src/hardware/TX/Jumper T-Pro.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_dio2": 22,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_enable": 25,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 1,
- "power_high": 6,
- "power_max": 6,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-13,-10,-5,-2,3],
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 13,
- "debug_backpack_tx": 12
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Namimno Flash OLED.json b/src/hardware/TX/Namimno Flash OLED.json
deleted file mode 100644
index 0365e08d65..0000000000
--- a/src/hardware/TX/Namimno Flash OLED.json
+++ /dev/null
@@ -1,37 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 22,
- "radio_dio1": 17,
- "radio_dio2": 16,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 21,
- "radio_sck": 18,
- "power_enable": 25,
- "power_rxen": 32,
- "power_txen": 33,
- "power_lna_gain": 12,
- "power_min": 1,
- "power_high": 6,
- "power_max": 6,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-13,-10,-5,-2,3],
- "joystick": 35,
- "joystick_values": [1850, 900, 490, 1427, 0, 2978],
- "led_rgb": 4,
- "led_rgb_isgrb": true,
- "screen_cs": 12,
- "screen_dc": 26,
- "screen_mosi": 15,
- "screen_rst": 27,
- "screen_sck": 14,
- "screen_type": 2,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1,
- "misc_fan_en": 2
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Namimno Voyager OLED.json b/src/hardware/TX/Namimno Voyager OLED.json
deleted file mode 100644
index 63b56675a6..0000000000
--- a/src/hardware/TX/Namimno Voyager OLED.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_dio0": 17,
- "radio_dio1": 16,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 21,
- "radio_sck": 18,
- "power_apc2": 25,
- "power_rxen": 33,
- "power_min": 1,
- "power_high": 7,
- "power_max": 7,
- "power_default": 2,
- "power_control": 3,
- "power_values": [57,67,76,90,112,132,167,201],
- "joystick": 35,
- "joystick_values": [1850, 900, 490, 1427, 0, 2978],
- "led_rgb": 4,
- "led_rgb_isgrb": true,
- "screen_cs": 12,
- "screen_dc": 26,
- "screen_mosi": 15,
- "screen_rst": 27,
- "screen_sck": 14,
- "screen_type": 2,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1
-}
\ No newline at end of file
diff --git a/src/hardware/TX/QuadKopters JR 2400.json b/src/hardware/TX/QuadKopters JR 2400.json
deleted file mode 100644
index d55c4cb1ae..0000000000
--- a/src/hardware/TX/QuadKopters JR 2400.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 4,
- "power_max": 4,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-15,-11,-8,-5,0],
- "led_rgb": 15,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Radiomaster Boxer.json b/src/hardware/TX/Radiomaster Boxer.json
deleted file mode 100644
index b58e417f8e..0000000000
--- a/src/hardware/TX/Radiomaster Boxer.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_dio2": 22,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_min": 0,
- "power_high": 0,
- "power_max": 0,
- "power_default": 0,
- "power_control": 0,
- "power_values": [-13],
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 16,
- "debug_backpack_tx": 17,
- "backpack_boot": 15,
- "backpack_en": 25,
- "misc_fan_pwm": 2,
- "misc_fan_speeds": [165,165,165,165,165,165]
-}
diff --git a/src/hardware/TX/Radiomaster Ranger Micro.json b/src/hardware/TX/Radiomaster Ranger Micro.json
deleted file mode 100644
index 8153cdf445..0000000000
--- a/src/hardware/TX/Radiomaster Ranger Micro.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 22,
- "radio_dio1": 21,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 4,
- "radio_rst": 5,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 32,
- "power_txen": 33,
- "power_min": 1,
- "power_high": 6,
- "power_max": 6,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-17,-15,-12,-7,-4,2],
- "led_rgb": 15,
- "led_rgb_isgrb": true,
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 16,
- "debug_backpack_tx": 17,
- "backpack_boot": 26,
- "backpack_en": 25,
- "passthrough_baud": 230400,
- "misc_fan_en": 27
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Radiomaster Ranger.json b/src/hardware/TX/Radiomaster Ranger.json
deleted file mode 100644
index 596c70a345..0000000000
--- a/src/hardware/TX/Radiomaster Ranger.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 22,
- "radio_dio1": 21,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 4,
- "radio_rst": 5,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 32,
- "power_txen": 33,
- "power_min": 1,
- "power_high": 6,
- "power_max": 6,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-15,-12,-8,-5,-2,0],
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 16,
- "debug_backpack_tx": 17,
- "backpack_boot": 26,
- "backpack_en": 25,
- "passthrough_baud": 230400,
- "led_rgb": 15,
- "led_rgb_isgrb": true,
- "ledidx_rgb_status": [2,3,4,5],
- "misc_fan_pwm": 2,
- "misc_fan_tacho": 27,
- "misc_fan_speeds": [47,63,95,127,191,255,255],
- "gsensor_stk8xxx": true,
- "i2c_scl": 12,
- "i2c_sda": 14,
- "misc_gsensor_int": 37,
- "screen_type": 1,
- "screen_sck": 12,
- "screen_sda": 14,
- "joystick": 39,
- "joystick_values": [3227, 0, 1961, 2668, 1290, 4095],
- "button": 34,
- "button_led_index": 0,
- "button2": 35,
- "button2_led_index": 1
-}
diff --git a/src/hardware/TX/Radiomaster TX16S 2400.json b/src/hardware/TX/Radiomaster TX16S 2400.json
deleted file mode 100644
index 6db7563307..0000000000
--- a/src/hardware/TX/Radiomaster TX16S 2400.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_dio2": 22,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 4,
- "power_max": 4,
- "power_default": 2,
- "power_pdet": 35,
- "power_pdet_intercept": 1.5,
- "power_pdet_slope": 0.032,
- "power_control": 0,
- "power_values": [-17,-13,-9,-6,-2],
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 16,
- "debug_backpack_tx": 17,
- "backpack_boot": 15,
- "backpack_en": 25
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Radiomaster Zorro.json b/src/hardware/TX/Radiomaster Zorro.json
deleted file mode 100644
index a4dd5bce41..0000000000
--- a/src/hardware/TX/Radiomaster Zorro.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "serial_rx": 3,
- "serial_tx": 1,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_dio2": 22,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 4,
- "power_max": 4,
- "power_default": 2,
- "power_pdet": 35,
- "power_pdet_intercept": 2.4,
- "power_pdet_slope": 0.031,
- "power_control": 0,
- "power_values": [-17,-13,-9,-6,-2],
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 16,
- "debug_backpack_tx": 17,
- "backpack_boot": 15,
- "backpack_en": 25
-}
\ No newline at end of file
diff --git a/src/hardware/TX/Vantac Lite.json b/src/hardware/TX/Vantac Lite.json
deleted file mode 100644
index 761d31842c..0000000000
--- a/src/hardware/TX/Vantac Lite.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 21,
- "radio_dio1": 4,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 14,
- "radio_sck": 18,
- "power_rxen": 27,
- "power_txen": 26,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 5,
- "power_max": 5,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-17,-14,-11,-7,-1,2],
- "led_rgb": 33,
- "led_rgb_isgrb": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1
-}
\ No newline at end of file
diff --git a/src/hardware/TX/iFlight 2400 V2.json b/src/hardware/TX/iFlight 2400 V2.json
deleted file mode 100644
index 3e7303763e..0000000000
--- a/src/hardware/TX/iFlight 2400 V2.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 25,
- "radio_dio1": 26,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 22,
- "radio_sck": 18,
- "radio_dcdc": true,
- "ant_ctrl": 2,
- "power_rxen": 21,
- "power_txen": 4,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 5,
- "power_max": 5,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-15,-13,-9,-4,3],
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 9,
- "debug_backpack_tx": 10,
- "backpack_boot": 15,
- "backpack_en": 27,
- "passthrough_baud": 230400,
- "misc_fan_en": 32
-}
\ No newline at end of file
diff --git a/src/hardware/TX/iFlight 2400.json b/src/hardware/TX/iFlight 2400.json
deleted file mode 100644
index f7303e8a11..0000000000
--- a/src/hardware/TX/iFlight 2400.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_busy": 25,
- "radio_dio1": 17,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 22,
- "radio_sck": 18,
- "radio_dcdc": true,
- "power_rxen": 21,
- "power_txen": 4,
- "power_lna_gain": 12,
- "power_min": 0,
- "power_high": 5,
- "power_max": 5,
- "power_default": 2,
- "power_control": 0,
- "power_values": [-18,-15,-13,-9,-4,3],
- "button": 15,
- "led_blue": 26,
- "led_green": 27,
- "led_red": 14,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1
-}
\ No newline at end of file
diff --git a/src/hardware/TX/iFlight 900 V2.json b/src/hardware/TX/iFlight 900 V2.json
deleted file mode 100644
index 2b44e4b624..0000000000
--- a/src/hardware/TX/iFlight 900 V2.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_dio0": 26,
- "radio_dio1": 25,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 22,
- "radio_sck": 18,
- "radio_rfo_hf": true,
- "power_rxen": 21,
- "power_txen": 4,
- "power_min": 3,
- "power_high": 6,
- "power_max": 6,
- "power_default": 3,
- "power_control": 0,
- "power_values": [0,4,7,11],
- "use_backpack": true,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 9,
- "debug_backpack_tx": 10,
- "backpack_boot": 15,
- "backpack_en": 27,
- "passthrough_baud": 230400,
- "misc_fan_en": 32
-}
\ No newline at end of file
diff --git a/src/hardware/TX/iFlight 900.json b/src/hardware/TX/iFlight 900.json
deleted file mode 100644
index 053451b1c0..0000000000
--- a/src/hardware/TX/iFlight 900.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "serial_rx": 13,
- "serial_tx": 13,
- "radio_dio0": 17,
- "radio_dio1": 16,
- "radio_miso": 19,
- "radio_mosi": 23,
- "radio_nss": 5,
- "radio_rst": 22,
- "radio_sck": 18,
- "radio_rfo_hf": true,
- "power_rxen": 21,
- "power_txen": 4,
- "power_min": 3,
- "power_high": 6,
- "power_max": 6,
- "power_default": 3,
- "power_control": 0,
- "power_values": [0,4,7,11],
- "button": 15,
- "led_blue": 26,
- "led_green": 27,
- "led_red": 14,
- "debug_backpack_baud": 460800,
- "debug_backpack_rx": 3,
- "debug_backpack_tx": 1
-}
\ No newline at end of file
diff --git a/src/hardware/targets.json b/src/hardware/targets.json
deleted file mode 100644
index e525644da7..0000000000
--- a/src/hardware/targets.json
+++ /dev/null
@@ -1,1817 +0,0 @@
-{
- "anyleaf": {
- "name": "AnyLeaf",
- "rx_2400": {
- "single-radio": {
- "product_name": "Anyleaf 2.4GHz single radio RX",
- "lua_name": "AL single RX",
- "layout_file": "Generic 2400 Diversity PA.json",
- "overlay": {
- "radio_dcdc": true
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "MATEK_2400_RX_R24D"
- },
- "dual-radio": {
- "product_name": "Anyleaf 2.4GHz dual radio RX",
- "lua_name": "AL dual RX",
- "layout_file": "Generic 2400 True Diversity PA.json",
- "overlay": {
- "radio_dcdc": true
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "h7-fc": {
- "product_name": "Anyleaf FC integrated RX",
- "lua_name": "AL FC RX",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- }
- }
- },
- "axis": {
- "name": "Axisflying",
- "tx_2400": {
- "thor": {
- "product_name": "AXIS Thor 2.4GHz TX",
- "lua_name": "AXIS THOR 2400TX",
- "layout_file": "AXIS Thor 2400.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "AXIS_THOR_2400_TX"
- }
- },
- "rx_2400": {
- "thor": {
- "product_name": "AXIS Thor 2.4GHz RX",
- "lua_name": "AXIS THOR 2400RX",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "AXIS_THOR_2400_RX"
- }
- }
- },
- "betafpv": {
- "name": "BETAFPV",
- "tx_900": {
- "nano900": {
- "product_name": "BETAFPV 900MHz Nano TX",
- "lua_name": "BETAFPV 900Nano",
- "layout_file": "BETAFPV 900 Nano.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "BETAFPV_900_TX"
- },
- "micro900": {
- "product_name": "BETAFPV 900MHz Micro TX",
- "lua_name": "BETAFPV 900Micro",
- "layout_file": "BETAFPV 900 Micro.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "BETAFPV_900_TX_MICRO"
- }
- },
- "rx_900": {
- "plain": {
- "product_name": "BETAFPV 900MHz RX",
- "lua_name": "BETAFPV 900RX",
- "layout_file": "Generic 900.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "BETAFPV_900_RX"
- },
- "superd": {
- "product_name": "BETAFPV SuperD 900MHz RX",
- "lua_name": "BFPV SuperD 900",
- "layout_file": "Generic 900 True Diversity PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_RX"
- },
- "superp": {
- "product_name": "BETAFPV SuperP 14Ch 900MHz RX",
- "lua_name": "BFPV 14CH P900",
- "layout_file": "Generic 900 True Diversity PWM 16.json",
- "overlay": {
- "pwm_outputs":[13,15,2,0,4,9,10,5,18,23,19,22,3,1],
- "vbat_offset": 18,
- "vbat_scale": 1284,
- "vbat_atten": 7
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_RX"
- }
- },
- "tx_2400": {
- "nano2g4": {
- "product_name": "BETAFPV 2.4GHz Nano TX",
- "lua_name": "BETAFPV 2G4Nano",
- "layout_file": "BETAFPV 2400 Nano.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "BETAFPV_2400_TX"
- },
- "micro2g4": {
- "product_name": "BETAFPV 2.4GHz Micro TX",
- "lua_name": "BETAFPV2G4Micro",
- "layout_file": "BETAFPV 2400 Micro.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "BETAFPV_2400_TX_MICRO"
- },
- "micro1w": {
- "product_name": "BETAFPV 2.4GHz 1W Micro TX",
- "lua_name": "BFPV 2G4Micro1W",
- "layout_file": "BETAFPV 2400 Micro 1W.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "BETAFPV_2400_TX_MICRO_1000mw"
- },
- "literadio3": {
- "product_name": "BETAFPV 2.4GHz LiteRadio 3 Pro",
- "lua_name": "BETAFPV LR3 Pro",
- "layout_file": "BETAFPV 2400 LiteRadio 3.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "BETAFPV_2400_TX_LITERADIO3"
- }
- },
- "rx_2400": {
- "aio": {
- "product_name": "BETAFPV 2.4GHz AIO RX",
- "lua_name": "BFPV AIO 2G4RX",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- },
- "lite": {
- "product_name": "BETAFPV 2.4GHz Lite RX",
- "lua_name": "BFPV Lite 2G4RX",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- },
- "nano": {
- "product_name": "BETAFPV 2.4GHz Nano RX",
- "lua_name": "BFPV Nano 2G4RX",
- "layout_file": "Generic 2400 PA.json",
- "overlay": {
- "radio_dcdc": true
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "BETAFPV_Nano_2400_RX"
- },
- "pwmp": {
- "product_name": "BETAFPV PWM 2.4GHz RX",
- "lua_name": "BFPV PWM 2G4RX",
- "layout_file": "Generic 2400 PWMP5.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_PWMP"
- },
- "superd": {
- "product_name": "BETAFPV SuperD 2.4GHz RX",
- "lua_name": "BFPV SuperD 2G4",
- "layout_file": "Generic 2400 True Diversity PA.json",
- "overlay": {
- "radio_dcdc": true
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX",
- "prior_target_name": "BETAFPV_SuperD_2400_RX"
- },
- "superp": {
- "product_name": "BETAFPV SuperP 14Ch 2.4GHz RX",
- "lua_name": "BFPV 14CH P2G4",
- "layout_file": "Generic 2400 True Diversity PA PWM 14.json",
- "overlay": {
- "radio_dcdc": true,
- "vbat_offset": 18,
- "vbat_scale": 1284,
- "vbat_atten": 7
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- }
- }
- },
- "diy": {
- "name": "DIY devices",
- "tx_900": {
- "ttgov1": {
- "product_name": "DIY TTGO V1 900MHz TX",
- "lua_name": "DIY900 TTGO V1",
- "layout_file": "DIY 900 TTGO V1.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "DIY_900_TX_TTGO_V1_SX127x"
- },
- "ttgov2": {
- "product_name": "DIY TTGO V2 900MHz TX",
- "lua_name": "DIY900 TTGO V2",
- "layout_file": "DIY 900 TTGO V2.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "DIY_900_TX_TTGO_V2_SX127x"
- },
- "e19": {
- "product_name": "DIY ESP32 E19 900MHz TX",
- "lua_name": "DIY900 E19",
- "layout_file": "DIY 900 E19.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "DIY_900_TX_ESP32_SX127x_E19"
- },
- "rfm95": {
- "product_name": "DIY ESP32 RFM95 900MHz TX",
- "lua_name": "DIY900 RFM95",
- "layout_file": "DIY 900 RFM95.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "DIY_900_TX_ESP32_SX127x_RFM95"
- },
- "stm32-sx1272": {
- "product_name": "DIY STM32 SX1272 TX",
- "upload_methods": ["stlink"],
- "platform": "stm32",
- "firmware": "DIY_900_TX_STM32_SX1272",
- "stlink": {
- "cpus": ["STM32F103C8T6", "STM32F103CBT6"],
- "offset": "0x4000",
- "bootloader": "sx1272_pcb_bootloader.bin"
- }
- }
- },
- "rx_900": {
- "ttgov1": {
- "product_name": "DIY TTGO V1 900MHz RX",
- "lua_name": "DIY900 TTGO V1RX",
- "layout_file": "DIY 900 TTGO V1.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_RX",
- "prior_target_name": "DIY_900_RX_TTGO_V1_SX127x"
- },
- "ttgov2": {
- "product_name": "DIY TTGO V2 900MHz RX",
- "lua_name": "DIY900 TTGO V2RX",
- "layout_file": "DIY 900 TTGO V2.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_RX",
- "prior_target_name": "DIY_900_RX_TTGO_V2_SX127x"
- },
- "huzzah": {
- "product_name": "DIY Huzzah RFM95 900MHz TX",
- "lua_name": "ELRS RFM95 900RX",
- "layout_file": "DIY Huzzah RFM95.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "DIY_900_RX_HUZZAH_RFM95W"
- },
- "stm32-sx1272": {
- "product_name": "DIY STM32 SX1272 RX",
- "upload_methods": ["stlink"],
- "platform": "stm32",
- "firmware": "DIY_900_RX_STM32_SX1272",
- "stlink": {
- "cpus": ["STM32F103C8T6", "STM32F103CBT6"],
- "offset": "0x4000",
- "bootloader": "sx1272_pcb_bootloader.bin"
- }
- }
- },
- "tx_2400": {
- "mini": {
- "product_name": "DIY Mini ESP32 SX1280 2.4GHz TX",
- "lua_name": "DIY2400 Mini",
- "layout_file": "DIY 2400 Mini.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "DIY_2400_TX_ESP32_SX1280_Mini"
- },
- "e28": {
- "product_name": "DIY ESP32 E28 2.4GHz TX",
- "lua_name": "DIY2400 E28",
- "layout_file": "DIY 2400 E28.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "DIY_2400_TX_ESP32_SX1280_E28"
- },
- "f27": {
- "product_name": "DIY ESP32 F27 2.4GHz TX",
- "lua_name": "DIY2400 F27",
- "layout_file": "DIY 2400 F27.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "DIY_2400_TX_ESP32_SX1280_LORA1280F27"
- },
- "ble": {
- "product_name": "DIY ESP32 BLE Joystick TX",
- "lua_name": "DIY BLE Joystick",
- "layout_file": "DIY 2400 BLE Joystick.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "DIY_BLE_JOYSTICK"
- }
- }
- },
- "emax": {
- "name": "EMAX",
- "tx_900": {
- "plain": {
- "product_name": "EMAX OLED 900MHz TX",
- "lua_name": "EMAX 900TX",
- "layout_file": "EMAX 900 OLED.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "EMAX_900_TX"
- },
- "nano": {
- "product_name": "EMAX Nano 900MHz TX",
- "lua_name": "EMAX NANO 900TX",
- "layout_file": "EMAX 900 Nano.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "EMAX_NANO_900_TX"
- }
- },
- "rx_900": {
- "plain": {
- "product_name": "EMAX 900MHz RX",
- "lua_name": "EMAX 900RX",
- "layout_file": "Generic 900.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "DIY_900_RX_ESP8285_SX127x"
- }
- },
- "tx_2400": {
- "plain": {
- "product_name": "EMAX OLED 2.4GHz TX",
- "lua_name": "EMAX 2400TX",
- "layout_file": "EMAX 2400 OLED.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "EMAX_2400_TX"
- },
- "nano": {
- "product_name": "EMAX Nano 2.4GHz TX",
- "lua_name": "EMAX NANO 2G4TX",
- "layout_file": "EMAX 2400 Nano.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "EMAX_NANO_2400_TX"
- }
- },
- "rx_2400": {
- "plain": {
- "product_name": "EMAX 2.4GHz RX",
- "lua_name": "EMAX 2400RX",
- "layout_file": "Generic 2400.json",
- "overlay": {
- "radio_dcdc": true
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- },
- "pa": {
- "product_name": "EMAX 2.4GHz PA RX",
- "lua_name": "EMAX 2400RX",
- "layout_file": "Generic 2400 PA.json",
- "overlay": {
- "radio_dcdc": true
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "BETAFPV_Nano_2400_RX"
- }
- }
- },
- "flywoo": {
- "name": "Flywoo",
- "rx_2400": {
- "el24e": {
- "product_name": "Flywoo EL24E 2.4GHz RX",
- "lua_name": "Flywoo EL24E",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- },
- "el24p": {
- "product_name": "Flywoo EL24P 2.4GHz RX",
- "lua_name": "Flywoo EL24P",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- }
- }
- },
- "foxeer": {
- "name": "Foxeer",
- "rx_2400": {
- "pa-rgb": {
- "product_name": "Foxeer 2.4Ghz RX",
- "lua_name": "Foxeer 2400 RX",
- "layout_file": "Generic 2400 PA RGB.json",
- "overlay": {
- "radio_dcdc": true
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "Foxeer_2400_RX"
- },
- "lite": {
- "product_name": "Foxeer Lite 2.4GHz RX",
- "lua_name": "Foxeer Lite 2G4",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- }
- },
- "rx_900": {
- "plain": {
- "product_name": "Foxeer 900MHz RX",
- "lua_name": "Foxeer 900 RX",
- "layout_file": "Generic 900.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "BETAFPV_900_RX"
- }
- }
- },
- "frsky": {
- "name": "Frsky",
- "tx_900": {
- "r9m": {
- "product_name": "FrSky R9M 900MHz TX",
- "upload_methods": ["stlink", "stock", "wifi"],
- "platform": "stm32",
- "firmware": "Frsky_TX_R9M",
- "features": ["buzzer", "unlock-higher-power", "fan"],
- "stlink": {
- "cpus": ["STM32F103C8T6", "STM32F103CBT6"],
- "offset": "0x4000",
- "bootloader": "r9m_bootloader.bin"
- }
- },
- "r9m-lite": {
- "product_name": "FrSky R9M Lite 900MHz TX",
- "upload_methods": ["stlink", "stock"],
- "platform": "stm32",
- "firmware": "Frsky_TX_R9M_LITE",
- "stlink": {
- "cpus": ["STM32F103C8T6"],
- "offset": "0x4000",
- "bootloader": "r9m_bootloader.bin"
- }
- },
- "r9m-lite-pro": {
- "product_name": "FrSky R9M Lite Pro 900MHz TX",
- "upload_methods": ["stlink"],
- "platform": "stm32",
- "firmware": "Frsky_TX_R9M_LITE_PRO",
- "stlink": {
- "cpus": ["STM32F303CCT6"],
- "offset": "0x8000",
- "bootloader": "r9m_lite_pro_bootloader.bin"
- }
- }
- },
- "rx_900": {
- "r9mm": {
- "product_name": "FrSky R9MM/Mini 900MHz RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "Frsky_RX_R9MM_R9MINI",
- "features": ["sbus-uart"],
- "stlink": {
- "cpus": ["STM32F103CBT6"],
- "offset": "0x8000",
- "bootloader": "r9mm_bootloader.bin"
- }
- },
- "r9slim": {
- "product_name": "FrSky R9 Slim 900MHz RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "Frsky_RX_R9SLIM",
- "stlink": {
- "cpus": ["STM32F103CBT6"],
- "offset": "0x8000",
- "bootloader": "r9slim_no_btn_bootloader.bin"
- }
- },
- "r9slim-plus": {
- "product_name": "FrSky R9 Slim Plus 900MHz RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "Frsky_RX_R9SLIMPLUS",
- "stlink": {
- "cpus": ["STM32F103CBT6"],
- "offset": "0x8000",
- "bootloader": "r9slim_plus_bootloader.bin"
- }
- },
- "r9slim-plus-ota": {
- "product_name": "FrSky R9 Slim Plus OTA 900MHz RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "Frsky_RX_R9SLIMPLUS_OTA",
- "stlink": {
- "cpus": ["STM32F303CCT6"],
- "offset": "0x8000",
- "bootloader": "r9slim_plus_ota_bootloader.bin"
- }
- },
- "r9mx": {
- "product_name": "FrSky R9MX 900MHz RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "Frsky_RX_R9MX",
- "stlink": {
- "cpus": ["STM32F103CBT6"],
- "offset": "0x8000",
- "bootloader": "r9mx_bootloader.bin"
- }
- }
- }
- },
- "generic": {
- "name": "Generic targets used as a base",
- "tx_900": {
- "gemini": {
- "product_name": "Generic ESP32 900MHz Gemini TX",
- "lua_name": "Gemini 900TX",
- "layout_file": "Generic 900 Gemini.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "Unified_ESP32_900_TX"
- }
- },
- "rx_900": {
- "plain": {
- "product_name": "Generic ESP8285 SX127x 900MHz RX",
- "lua_name": "ELRS 900RX",
- "layout_file": "Generic 900.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "DIY_900_RX_ESP8285_SX127x"
- },
- "pwmp": {
- "product_name": "Generic ESP8285 SX127x with PWM 900MHz RX",
- "lua_name": "ELRS PWMP 900RX",
- "layout_file": "Generic 900 PWMP.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "DIY_900_RX_PWMP"
- },
- "true_diversity": {
- "product_name": "Generic ESP32 True Diversity PA 900MHz RX",
- "lua_name": "Diversity 900RX",
- "layout_file": "Generic 900 True Diversity PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_RX"
- },
- "true_diversity_pwm16": {
- "product_name": "Generic ESP32 True Diversity 16xPWM 900MHz RX ",
- "lua_name": "16PWM TD 900RX",
- "layout_file": "Generic 900 True Diversity PWM 16.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_RX"
- }
- },
- "tx_2400": {
- "dupletx": {
- "product_name": "Generic Full-duplex 2.4GHz TX",
- "lua_name": "DupleTX",
- "layout_file": "DIY 2400 DupleTX.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "DIY_2400_TX_DUPLETX"
- },
- "dupletxesp": {
- "product_name": "Generic ESP8285 Full-duplex 2.4GHz RX as TX",
- "lua_name": "DupleTX ESP",
- "layout_file": "DIY 2400 DupleTX ESP.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_TX",
- "prior_target_name": "DIY_2400_TX_DUPLETX_ESP"
- },
- "gemini": {
- "product_name": "Generic ESP32 2.4Ghz Gemini TX",
- "lua_name": "Gemini 2.4TX",
- "layout_file": "Generic 2400 Gemini.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "DIY_2400_TX_GEMINI"
- }
- },
- "rx_2400": {
- "pp": {
- "product_name": "Generic STM32 PP 2.4Ghz RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "DIY_2400_RX_STM32_CCG_Nano_v0_5",
- "stlink": {
- "cpus": ["STM32L432KCUx"],
- "offset": "0x4000",
- "bootloader": "sx1280_rx_nano_pcb_v0.5_bootloader.bin"
- }
- },
- "plain": {
- "product_name": "Generic ESP8285 2.4Ghz RX",
- "lua_name": "ELRS 2400RX",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- },
- "pwm5": {
- "product_name": "Generic ESP8285 5xPWM 2.4Ghz RX",
- "lua_name": "ELRS+PWM 2400RX",
- "layout_file": "Generic 2400 PWMP5.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_PWMP"
- },
- "pwm6": {
- "product_name": "Generic ESP8285 6xPWM 2.4Ghz RX",
- "lua_name": "ELRS+PWM 2400RX",
- "layout_file": "Generic 2400 PWMP6.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_PWMPEX"
- },
- "pwm-vario": {
- "product_name": "Generic ESP32 PWM Vario 2.4Ghz RX",
- "lua_name": "Vario 2400RX",
- "layout_file": "Vario 2400.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX",
- "prior_target_name": "DIY_2400_RX_PWM_VARIO"
- },
- "pwm7": {
- "product_name": "Generic ESP8285 7xPWM 2.4Ghz RX",
- "lua_name": "ELRS+PWM 2400RX",
- "layout_file": "Generic 2400 PWMP7.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX"
- },
- "pa": {
- "product_name": "Generic ESP8285 PA 2.4Ghz RX",
- "lua_name": "ELRS+PA 2400RX",
- "layout_file": "Generic 2400 PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX"
- },
- "pa-rgb": {
- "product_name": "Generic ESP8285 RGB + PA 2.4Ghz RX",
- "lua_name": "ELRS+RGB 2400RX",
- "layout_file": "Generic 2400 PA RGB.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_PA_RGB_RX"
- },
- "diversity": {
- "product_name": "Generic ESP8285 Diversity PA 2.4Ghz RX",
- "lua_name": "ELRS+D 2400RX",
- "layout_file": "Generic 2400 Diversity PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX"
- },
- "frank": {
- "product_name": "Generic ESP32 2.4Ghz Franken RX",
- "lua_name": "Frank 2400RX",
- "layout_file": "Frank 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "rx_vtx": {
- "product_name": "Generic ESP32 2.4Ghz Rx and VTx",
- "lua_name": "Rx+VTx 2400RX",
- "layout_file": "Generic 2400 Whoop Rx and VTx.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "true_diversity": {
- "product_name": "Generic ESP32 True Diversity PA 2.4Ghz RX",
- "lua_name": "Diversity 2.4RX",
- "layout_file": "Generic 2400 True Diversity PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "diversity_pa_vtx": {
- "product_name": "Generic ESP32 True Diversity PA 2.4Ghz RX and VTx",
- "lua_name": "Div+VTx 2.4RX",
- "layout_file": "Generic 2400 True Diversity PA and VTx.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "diversity_vtx": {
- "product_name": "Generic ESP32 True Diversity 2.4Ghz RX and VTx",
- "lua_name": "Div+VTx 2.4RX",
- "layout_file": "Generic 2400 True Diversity and VTx.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "true_diversity_pwm16": {
- "product_name": "Generic ESP32 True Diversity 16xPWM 2.4Ghz RX ",
- "lua_name": "16PWM TD 2.4RX",
- "layout_file": "Generic 2400 True Diversity PWM 16.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "true_diversity_pwm14": {
- "product_name": "Generic ESP32 True Diversity PA 14xPWM 2.4Ghz RX ",
- "lua_name": "14PWM TD 2.4RX",
- "layout_file": "Generic 2400 True Diversity PA PWM 14.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- }
- }
- },
- "geprc": {
- "name": "GEPRC",
- "rx_900": {
- "nano": {
- "product_name": "GEPRC Nano 900MHz RX",
- "lua_name": "GEPRC Nano RX",
- "layout_file": "Generic 900.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "DIY_900_RX_ESP8285_SX127x"
- }
- },
- "rx_2400": {
- "nano": {
- "product_name": "GEPRC Nano(SE) 2.4GHz RX",
- "lua_name": "GEPRC Nano RX",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- },
- "geprc-dual": {
- "product_name": "GEPRC Dual Diversity 2.4GHz RX",
- "lua_name": "GEPRC Dual RX",
- "layout_file": "Generic 2400 True Diversity PA.json",
- "overlay": {
- "radio_dcdc": true
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- }
- }
- },
- "sub250": {
- "name": "Sub250",
- "rx_2400": {
- "nano": {
- "product_name": "Sub250 2400 RX",
- "lua_name": "Sub250 2400 RX",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- }
- }
- },
- "happymodel": {
- "name": "Happymodel",
- "tx_900": {
- "es915": {
- "product_name": "HappyModel ES915 TX",
- "upload_methods": ["stlink", "stock"],
- "platform": "stm32",
- "firmware": "HappyModel_TX_ES915TX",
- "has_buzzer": true,
- "stlink": {
- "cpus": ["STM32F103C8T6", "STM32F103CBT6"],
- "offset": "0x4000",
- "bootloader": "r9m_bootloader.bin"
- }
- },
- "es900": {
- "product_name": "HappyModel ES900 TX",
- "lua_name": "HM ES900TX",
- "layout_file": "HappyModel ES900.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "HappyModel_TX_ES900TX"
- }
- },
- "rx_900": {
- "es915": {
- "product_name": "HappyModel ES915 RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "HappyModel_RX_ES915RX",
- "stlink": {
- "cpus": ["STM32F103C8T6", "STM32F103CBT6"],
- "offset": "0x8000",
- "bootloader": "r9mm_bootloader.bin"
- }
- },
- "es900": {
- "product_name": "HappyModel ES900 RX",
- "lua_name": "HM ES900RX",
- "layout_file": "Generic 900.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "features": ["fan"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "HappyModel_RX_ES900RX"
- },
- "es900_dual": {
- "product_name": "HappyModel ES900 Dual RX",
- "lua_name": "HM ES900 Dual",
- "layout_file": "Generic 900 True Diversity PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_RX"
- },
- "epw6": {
- "product_name": "HappyModel EPW6 PWM RX",
- "lua_name": "HM EPW6 900RX",
- "layout_file": "Generic 900 PWMP.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "DIY_900_RX_PWMP"
- }
- },
- "tx_2400": {
- "es24": {
- "product_name": "HappyModel ES24 2.4GHz TX",
- "lua_name": "HM ES24TX",
- "layout_file": "HappyModel ES24.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "HappyModel_ES24TX_2400_TX"
- },
- "es24pro": {
- "product_name": "HappyModel ES24 Pro 2.4GHz TX",
- "lua_name": "HM ES24TX Pro",
- "layout_file": "HappyModel ES24 Pro.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "HappyModel_ES24TX_Pro_Series_2400_TX"
- },
- "es24slimpro": {
- "product_name": "HappyModel ES24 Slim Pro 2.4GHz TX",
- "lua_name": "HM ES24 SlimPro",
- "layout_file": "HappyModel ES24 Pro.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "HappyModel_ES24TX_Pro_Series_2400_TX"
- }
- },
- "rx_2400": {
- "pp": {
- "product_name": "HappyModel PP 2.4GHz RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "HappyModel_PP_2400_RX",
- "stlink": {
- "cpus": ["STM32L432KCUx"],
- "offset": "0x4000",
- "bootloader": "sx1280_rx_nano_pcb_v0.5_bootloader.bin"
- },
- "prior_target_name": "HappyModel_PP_2400_RX"
- },
- "ep1": {
- "product_name": "HappyModel EP1 2.4GHz RX",
- "lua_name": "HM EP1 2400",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "HappyModel_EP_2400_RX"
- },
- "ep2": {
- "product_name": "HappyModel EP2 2.4GHz RX",
- "lua_name": "HM EP2 2400",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "HappyModel_EP_2400_RX"
- },
- "ep-dual": {
- "product_name": "HappyModel EP Dual 2.4GHz RX",
- "lua_name": "HM EP Dual 2400",
- "layout_file": "Generic 2400 True Diversity PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX",
- "prior_target_name": "HappyModel_EP_Dual_2400_RX"
- },
- "epw5": {
- "product_name": "HappyModel EPW5 2.4GHz PWM RX",
- "lua_name": "HM EPW5 2400",
- "layout_file": "Generic 2400 PWMP5.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_PWMP"
- },
- "epw6": {
- "product_name": "HappyModel EPW6 2.4GHz PWM RX",
- "lua_name": "HM EPW6 2400",
- "layout_file": "Generic 2400 PWMP6.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_PWMPEX"
- }
- }
- },
- "hglrc": {
- "name": "HGLRC",
- "rx_900": {
- "hermes": {
- "product_name": "HGLRC Hermes 900MHz RX",
- "lua_name": "HGLRC Hermes 900",
- "layout_file": "Generic 900.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "HGLRC_Hermes_900_RX"
- }
- },
- "tx_2400": {
- "hermes": {
- "product_name": "HGLRC Hermes 2.4GHz TX",
- "lua_name": "HGLRC Hermes",
- "layout_file": "HGLRC Hermes 2400.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "HGLRC_Hermes_2400_TX"
- }
- },
- "rx_2400": {
- "hermes": {
- "product_name": "HGLRC Hermes 2.4GHz RX",
- "lua_name": "HGLRC Hermes 2G4",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "HGLRC_Hermes_2400_RX"
- }
- }
- },
- "hiyounger": {
- "name": "HiYOUNGER",
- "tx_2400": {
- "plain": {
- "product_name": "HiYOUNGER 2.4GHz TX",
- "lua_name": "HiYOUNGER TX2G4",
- "layout_file": "BETAFPV 2400 Nano.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "BETAFPV_2400_TX"
- }
- },
- "rx_2400": {
- "rx24t": {
- "product_name": "HiYOUNGER RX24T 2.4GHz RX",
- "lua_name": "HiYOUNGER RX24T",
- "layout_file": "Generic 2400 PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "BETAFPV_Nano_2400_RX"
- },
- "ep24s": {
- "product_name": "HiYOUNGER EP24S 2.4GHz RX",
- "lua_name": "HiYOUNGER EP24S",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- },
- "sp24s": {
- "product_name": "HiYOUNGER SP24S 2.4GHz RX",
- "lua_name": "HiYOUNGER SP24S",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- }
- },
- "tx_900": {
- "plain": {
- "product_name": "HiYOUNGER 900 TX",
- "lua_name": "HiYOUNGER 900 TX",
- "layout_file": "BETAFPV 900 Nano.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "BETAFPV_900_TX"
- }
- },
- "rx_900": {
- "plain": {
- "product_name": "HiYOUNGER 900MHz RX",
- "lua_name": "HiYOUNGER 900RX",
- "layout_file": "Generic 900.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "BETAFPV_900_RX"
- }
- }
- },
- "iflight": {
- "name": "iFlight",
- "tx_900": {
- "plain": {
- "product_name": "iFlight 900MHz TX",
- "lua_name": "iFlight 900TX",
- "layout_file": "iFlight 900.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "iFlight_900_TX"
- },
- "v2": {
- "product_name": "iFlight 900MHz TX V2",
- "lua_name": "iFlight 900TX",
- "layout_file": "iFlight 900 V2.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX"
- }
- },
- "rx_900": {
- "plain": {
- "product_name": "iFlight 900MHz RX",
- "lua_name": "iFlight 900RX",
- "layout_file": "Generic 900.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "iFlight_900_RX"
- }
- },
- "tx_2400": {
- "plain": {
- "product_name": "iFlight 2.4GHz TX",
- "lua_name": "iFlight 2400TX",
- "layout_file": "iFlight 2400.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "iFlight_2400_TX"
- },
- "v2": {
- "product_name": "iFlight 2.4GHz TX V2",
- "lua_name": "iFlight 2400TX",
- "layout_file": "iFlight 2400 V2.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX"
- }
- },
- "rx_2400": {
- "plain": {
- "product_name": "iFlight 2.4GHz RX",
- "lua_name": "iFlight 2400RX",
- "layout_file": "iFlight 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "iFlight_2400_RX"
- }
- }
- },
- "imrc": {
- "name": "ImmersionRC",
- "tx_2400": {
- "ghost": {
- "product_name": "Ghost 2.4GHz TX",
- "upload_methods": ["stlink"],
- "platform": "stm32",
- "firmware": "GHOST_2400_TX",
- "features": ["buzzer"],
- "stlink": {
- "cpus": ["STM32F303CCT6"],
- "offset": "0x4000",
- "bootloader": "ghost/ghost_tx_bootloader.bin"
- }
- },
- "ghost_lite": {
- "product_name": "Ghost Lite 2.4GHz TX",
- "upload_methods": ["stlink"],
- "platform": "stm32",
- "firmware": "GHOST_2400_TX_LITE",
- "features": ["buzzer"],
- "stlink": {
- "cpus": ["STM32F303CCT6"],
- "offset": "0x4000",
- "bootloader": "ghost/ghost_tx_bootloader.bin"
- }
- }
- },
- "rx_2400": {
- "atto": {
- "product_name": "Ghost Atto 2.4GHz RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "GHOST_ATTO_2400_RX",
- "stlink": {
- "cpus": ["STM32F303K8Tx"],
- "offset": "0x4000",
- "bootloader": "ghost/ghost_atto_bootloader.bin"
- }
- }
- }
- },
- "jhemcu": {
- "name": "JHEMCU",
- "rx_2400": {
- "rx24t": {
- "product_name": "JHEMCU RX24T 2.4GHz RX",
- "lua_name": "JHEMCU RX24T",
- "layout_file": "Generic 2400 PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "BETAFPV_Nano_2400_RX"
- },
- "ep24s": {
- "product_name": "JHEMCU EP24S 2.4GHz RX",
- "lua_name": "JHEMCU EP24S",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- },
- "sp24s": {
- "product_name": "JHEMCU SP24S 2.4GHz RX",
- "lua_name": "JHEMCU SP24S",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- }
- }
- },
- "jumper": {
- "name": "Jumper",
- "tx_2400": {
- "t-20": {
- "product_name": "Jumper AION T-20 2.4GHz TX",
- "lua_name": "T-20 2G4 TX",
- "layout_file": "Jumper T-20 2400.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX"
- },
- "t-pro": {
- "product_name": "Jumper AION T-Pro 2.4GHz TX",
- "lua_name": "AION T-Pro TX",
- "layout_file": "Jumper T-Pro.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "Jumper_AION_2400_T-Pro_TX"
- },
- "t-lite": {
- "product_name": "Jumper AION T-Lite 2.4GHz TX",
- "lua_name": "AION T-Lite TX",
- "layout_file": "Jumper T-Lite.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "Jumper_AION_2400_T-Lite_TX"
- },
- "nano": {
- "product_name": "Jumper AION Nano 2.4GHz TX",
- "lua_name": "AION 2G4NANO TX",
- "layout_file": "Jumper AION Nano.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "Jumper_AION_NANO_2400_TX"
- }
- },
- "tx_900": {
- "t-20": {
- "product_name": "Jumper AION T-20 900M TX",
- "lua_name": "T-20 900 TX",
- "layout_file": "Jumper T-20 900.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX"
- }
- },
- "rx_2400": {
- "ep24s": {
- "product_name": "Jumper AION Mini 2.4GHz RX",
- "lua_name": "AION RX24 MINI",
- "layout_file": "Generic 2400.json",
- "overlay": {
- "radio_dcdc": true
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- },
- "nano": {
- "product_name": "Jumper AION Nano 2.4GHz RX",
- "lua_name": "AION 2G4NANORX",
- "layout_file": "Generic 2400 PA.json",
- "overlay": {
- "radio_dcdc": true
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "Jumper_AION_Nano_2400_RX"
- }
- },
- "rx_900": {
- "mini": {
- "product_name": "Jumper 900 Mini RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "Jumper_RX_R900MINI",
- "stlink": {
- "cpus": ["STM32F103C8T6", "STM32F103CBT6"],
- "offset": "0x8000",
- "bootloader": "jumper_r900_bootloader.bin"
- }
- }
- }
- },
- "matek": {
- "name": "Matek Systems",
- "rx_2400": {
- "r24d": {
- "product_name": "MATEK R24-D 2.4GHz RX",
- "lua_name": "MATEK R24-D",
- "layout_file": "Generic 2400 Diversity PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "MATEK_2400_RX_R24D"
- },
- "r24s": {
- "product_name": "MATEK R24-S 2.4GHz RX",
- "lua_name": "MATEK R24-S",
- "layout_file": "Generic 2400 PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "MATEK_2400_RX"
- },
- "r24p": {
- "product_name": "MATEK R24-P PWM 2.4GHz RX",
- "lua_name": "MATEK R24-P",
- "layout_file": "Generic 2400 PWMP5.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "MATEK_2400_RX_PWM"
- }
- }
- },
- "namimnorc": {
- "name": "NamimnoRC",
- "tx_900": {
- "voyager_stm": {
- "product_name": "NamimnoRC Voyager 900MHz TX",
- "upload_methods": ["stlink", "stock"],
- "platform": "stm32",
- "firmware": "NamimnoRC_VOYAGER_900_TX",
- "features": ["fan"],
- "stlink": {
- "cpus": ["STM32F103C8T6", "STM32F103CBT6"],
- "offset": "0x4000",
- "bootloader": "namimnorc/tx/namimnorc_tx_bootloader.bin"
- }
- },
- "voyager": {
- "product_name": "NamimnoRC Voyager OLED 900MHz TX",
- "lua_name": "NM Voyager OLED",
- "layout_file": "Namimno Voyager OLED.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_900_TX",
- "prior_target_name": "NamimnoRC_VOYAGER_900_OLED_TX"
- }
- },
- "rx_900": {
- "voyager_stm": {
- "product_name": "NamimnoRC Voyager STM 900MHz RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "NamimnoRC_VOYAGER_900_RX",
- "stlink": {
- "cpus": ["STM32F103C8T6", "STM32F103CBT6"],
- "offset": "0x8000",
- "bootloader": "namimnorc/rx/voyager_900_bootloader.bin"
- }
- },
- "voyager": {
- "product_name": "NamimnoRC Voyager ESP 900MHz RX",
- "lua_name": "Namimno 900RX",
- "layout_file": "Namimno VOYAGER 900.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "NamimnoRC_VOYAGER_900_ESP_RX"
- }
- },
- "tx_2400": {
- "flash_stm": {
- "product_name": "NamimnoRC Flash 2.4GHz TX",
- "upload_methods": ["stlink", "stock"],
- "platform": "stm32",
- "firmware": "NamimnoRC_FLASH_2400_TX",
- "features": ["fan"],
- "stlink": {
- "cpus": ["STM32F103C8T6", "STM32F103CBT6"],
- "offset": "0x4000",
- "bootloader": "namimnorc/tx/namimnorc_tx_bootloader.bin"
- }
- },
- "flash": {
- "product_name": "NamimnoRC Flash OLED 2.4GHz TX",
- "lua_name": "NM Flash OLED",
- "layout_file": "Namimno Flash OLED.json",
- "features": ["fan"],
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "NamimnoRC_FLASH_2400_OLED_TX"
- }
- },
- "rx_2400": {
- "flash_stm": {
- "product_name": "NamimnoRC Flash STM 2.4GHz RX",
- "upload_methods": ["stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "NamimnoRC_FLASH_2400_RX",
- "stlink": {
- "cpus": ["STM32F103C8T6", "STM32F103CBT6"],
- "offset": "0x8000",
- "bootloader": "namimnorc/rx/flash_2400_bootloader.bin"
- }
- },
- "flash": {
- "product_name": "NamimnoRC Flash ESP 2.4GHz RX",
- "lua_name": "Namimno 2400RX",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "NamimnoRC_FLASH_2400_ESP_RX"
- },
- "flash-pa": {
- "product_name": "NamimnoRC Flash 100mW Diversity 2.4GHz RX",
- "lua_name": "Namimno 2400RX",
- "layout_file": "Namimno FLASH 2400 PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "NamimnoRC_FLASH_2400_ESP_RX_PA"
- }
- }
- },
- "neutronrc": {
- "name": "NeutronRC",
- "rx_900": {
- "plain": {
- "product_name": "NeutronRC 900MHz RX",
- "lua_name": "NeutronRC 900",
- "layout_file": "Generic 900.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_900_RX",
- "prior_target_name": "NeutronRC_900_RX"
- }
- }
- },
- "newbeedrone": {
- "name": "NewBeeDrone",
- "rx_2400": {
- "rx24d": {
- "product_name": "NewBeeDrone Diversity 2.4Ghz RX",
- "lua_name": "NBD 2400RX",
- "layout_file": "Generic 2400 Diversity PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_ESP8285_SX1280"
- }
- }
- },
- "quadkopters": {
- "name": "QuadKopters",
- "tx_2400": {
- "jr": {
- "product_name": "QuadKopters JR 2.4GHz TX",
- "lua_name": "QuadKopters 2G4",
- "layout_file": "QuadKopters JR 2400.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "QuadKopters_JR_2400_TX"
- },
- "slim": {
- "product_name": "QuadKopters Slim 2.4GHz TX",
- "lua_name": "QuadKopters 2G4",
- "layout_file": "QuadKopters JR 2400.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "QuadKopters_JR_2400_TX"
- }
- },
- "rx_2400": {
- "nano": {
- "product_name": "QuadKopters Nano 2.4GHz RX",
- "lua_name": "QuadKopters 2G4",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "QuadKopters_NANO_RX"
- }
- }
- },
- "radiomaster": {
- "name": "RadioMaster",
- "tx_2400": {
- "zorro": {
- "product_name": "RadioMaster Zorro Internal 2.4GHz TX",
- "lua_name": "RM Zorro",
- "layout_file": "Radiomaster Zorro.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "RadioMaster_Zorro_2400_TX"
- },
- "tx16s": {
- "product_name": "RadioMaster TX16S Internal 2.4GHz TX",
- "lua_name": "RM TX16S",
- "layout_file": "Radiomaster TX16S 2400.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "RadioMaster_TX16S_2400_TX"
- },
- "tx12": {
- "product_name": "RadioMaster TX12 Internal 2.4GHz TX",
- "lua_name": "RM TX12",
- "layout_file": "Radiomaster Zorro.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "RadioMaster_Zorro_2400_TX"
- },
- "ranger": {
- "product_name": "RadioMaster Ranger 2.4GHz TX",
- "lua_name": "RM Ranger",
- "layout_file": "Radiomaster Ranger.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "RadioMaster_Ranger_2400_TX"
- },
- "ranger-micro": {
- "product_name": "RadioMaster Ranger Micro 2.4GHz TX",
- "lua_name": "RM Ranger Micro",
- "layout_file": "Radiomaster Ranger Micro.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "RadioMaster_Ranger_Micro_2400_TX"
- },
- "ranger-nano": {
- "product_name": "RadioMaster Ranger Nano 2.4GHz TX",
- "lua_name": "RM Ranger Nano",
- "layout_file": "Radiomaster Ranger Micro.json",
- "overlay": {
- "power_values": [-18,-15,-12,-8,-5,0]
- },
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "RadioMaster_Ranger_Nano_2400_TX"
- },
- "boxer": {
- "product_name": "RadioMaster Boxer Internal 2.4GHz TX",
- "lua_name": "RM Boxer",
- "layout_file": "Radiomaster Boxer.json",
- "upload_methods": ["uart", "wifi", "etx"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "RadioMaster_Boxer_2400_TX"
- }
- },
- "rx_2400": {
- "er4": {
- "product_name": "RadioMaster ER4 2.4GHz PWM RX",
- "lua_name": "RM ER4",
- "layout_file": "Generic 2400 PWMP5.json",
- "overlay": {
- "power_dcdc": true,
- "pwm_outputs": [0,1,3,9],
- "vbat_offset": -10,
- "vbat_scale": 292
- },
- "upload_methods": ["uart", "wifi"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_PWMP"
- },
- "er5": {
- "product_name": "RadioMaster ER5A/C 2.4GHz PWM RX",
- "lua_name": "RM ER5A/C",
- "layout_file": "Generic 2400 PWMP5.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "DIY_2400_RX_PWMP"
- },
- "er6": {
- "product_name": "RadioMaster ER6 2.4GHz Diversity+6xPWM RX",
- "lua_name": "RM ER6",
- "layout_file": "Radiomaster 2400 ER8G.json",
- "overlay": {
- "power_values": [-9,-6,-3,0],
- "pwm_outputs": [14, 12, 15, 2, 4, 9]
- },
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "er6g": {
- "product_name": "RadioMaster ER6-G 2.4GHz Diversity+6xPWM RX",
- "lua_name": "RM ER6-G",
- "layout_file": "Vario 2400.json",
- "overlay": {
- "power_values": [-2,-1,3]
- },
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "er6gv": {
- "product_name": "RadioMaster ER6-GV 2.4GHz Diversity+6xPWM+Vario RX",
- "lua_name": "RM ER6-GV",
- "layout_file": "Vario 2400.json",
- "overlay": {
- "power_values": [-2,-1,3]
- },
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX",
- "prior_target_name": "Vario 2400RX"
- },
- "er8": {
- "product_name": "RadioMaster ER8 2.4GHz Diversity+8xPWM RX",
- "lua_name": "RM ER8",
- "layout_file": "Radiomaster 2400 ER8G.json",
- "overlay": {
- "power_values": [-9,-6,-3,0]
- },
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "er8g": {
- "product_name": "RadioMaster ER8-G 2.4GHz Diversity+8xPWM RX",
- "lua_name": "RM ER8-G",
- "layout_file": "Radiomaster 2400 ER8G.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "er8gv": {
- "product_name": "RadioMaster ER8-GV 2.4GHz Diversity+8xPWM+Vario RX",
- "lua_name": "RM ER8-GV",
- "layout_file": "Radiomaster 2400 ER8G.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_RX"
- },
- "rp1": {
- "product_name": "RadioMaster RP1 2.4GHz RX",
- "lua_name": "RM RP1",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "HappyModel_EP_2400_RX"
- },
- "rp2": {
- "product_name": "RadioMaster RP2 2.4GHz RX",
- "lua_name": "RM RP2",
- "layout_file": "Generic 2400.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "HappyModel_EP_2400_RX"
- },
- "rp3": {
- "product_name": "RadioMaster RP3 Diversity 2.4GHz RX",
- "lua_name": "RM RP3",
- "layout_file": "Generic 2400 Diversity PA.json",
- "overlay": {
- "radio_dcdc": true
- },
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "MATEK_2400_RX_R24D"
- }
- }
- },
- "siyi": {
- "name": "SIYI",
- "tx_2400": {
- "fm30": {
- "product_name": "SIYI FM30 2.4GHz TX",
- "upload_methods": ["dfu", "stlink"],
- "platform": "stm32",
- "firmware": "FM30_TX",
- "stlink": {
- "cpus": ["STM32F103C8T6", "STM32F103CBT6"],
- "offset": "0x1000",
- "bootloader": "fm30_bootloader.bin"
- }
- },
- "rx_as_tx": {
- "product_name": "SIYI FM30 Mini 2.4GHz RX as TX",
- "upload_methods": ["uart", "stlink"],
- "platform": "stm32",
- "firmware": "FM30_RX_MINI_AS_TX",
- "stlink": {
- "cpus": ["STM32F373CCT6"],
- "offset": "0x4000",
- "bootloader": "fm30_mini_rxtx_bootloader.bin"
- }
- }
- },
- "rx_2400": {
- "mini": {
- "product_name": "SIYI FM30 Mini 2.4GHz RX",
- "upload_methods": ["uart", "stlink", "betaflight"],
- "platform": "stm32",
- "firmware": "FM30_RX_MINI",
- "stlink": {
- "cpus": ["STM32F373CCT6"],
- "offset": "0x4000",
- "bootloader": "fm30_mini_bootloader.bin"
- }
- }
- }
- },
- "skystars": {
- "name": "Skystars",
- "rx_2400": {
- "ss24d": {
- "product_name": "Skystars SS24D 2.4GHz RX",
- "lua_name": "Skystars SS24D",
- "layout_file": "Generic 2400 Diversity PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "MATEK_2400_RX_R24D"
- }
- }
- },
- "vantac": {
- "name": "Vantac",
- "tx_2400": {
- "lite": {
- "product_name": "Vantac Lite 2.4GHz TX",
- "lua_name": "Vantac Lite 2G4",
- "layout_file": "Vantac Lite.json",
- "upload_methods": ["uart", "wifi"],
- "platform": "esp32",
- "firmware": "Unified_ESP32_2400_TX",
- "prior_target_name": "Vantac_Lite_2400_TX"
- }
- },
- "rx_2400": {
- "plain": {
- "product_name": "Vantac 2.4GHz RX",
- "lua_name": "Vantac 2400 RX",
- "layout_file": "Generic 2400 PA.json",
- "upload_methods": ["uart", "wifi", "betaflight"],
- "platform": "esp8285",
- "firmware": "Unified_ESP8285_2400_RX",
- "prior_target_name": "Vantac_2400_RX"
- }
- }
- }
-}
diff --git a/src/html/elrs.css b/src/html/elrs.css
index c0ba4248ba..c6964bd91d 100644
--- a/src/html/elrs.css
+++ b/src/html/elrs.css
@@ -13,6 +13,26 @@
text-align: center;
}
+.upload > label > input {
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ white-space: nowrap;
+ width: 1px;
+}
+.upload > label {
+ cursor: pointer;
+ padding: 10px 16px 10px 16px;
+}
+.upload {
+ padding: 0;
+ tab-index: -1;
+}
+
body, input, select, textarea {
color: #666;
font-family: "Source Sans Pro", Helvetica, sans-serif;
@@ -21,6 +41,19 @@ body, input, select, textarea {
line-height: 1.65em;
}
+.fixed-column {
+ width: 20px;
+ padding-left: 4px !important;
+ padding-right: 4px !important;
+}
+
+.compact {
+ padding-left: 10px !important;
+ padding-right: 10px !important;
+ padding-top: 5px !important;
+ margin-bottom: 5px !important;
+}
+
@media screen and (max-width: 800px) {
.logo {
width:25%;
@@ -315,6 +348,12 @@ body, input, select, textarea {
}
/* Custom code for ExpressLRS PWM Output table */
+ .pwmpnl {
+ min-width: fit-content;
+ }
+ .pwmtbl table {
+ overflow-x: auto;
+ }
.pwmtbl th {
text-align: center;
font-weight: bold;
@@ -322,6 +361,11 @@ body, input, select, textarea {
.pwmtbl td {
text-align: center;
}
+ .pwmitm {
+ min-width: 6em;
+ white-space: nowrap;
+ width: fit-content;
+ }
/*==========================*/
diff --git a/src/html/hardware.html b/src/html/hardware.html
index e3b848016b..7cf7dbfc33 100644
--- a/src/html/hardware.html
+++ b/src/html/hardware.html
@@ -1,6 +1,6 @@
-@@require(PLATFORM, VERSION, isTX, sx127x)
+@@require(PLATFORM, VERSION, isTX)
-
+
Welcome to your ExpressLRS System
@@ -69,16 +69,14 @@ ExpressLRS
You can download the configuration or reset to pre-configured defaults and reboot.
-
+
Upload target configuration (remember to press "Save Target Configuration" below):
+
+
+ Upload
+
+
+
+
or drop files here
@@end
@@ -285,11 +334,11 @@ Current Home network:
- WiFi SSID
+ WiFi SSID
- WiFi password
+ WiFi password
Confirm
diff --git a/src/html/scan.js b/src/html/scan.js
index fd41fedefa..9ad763ed7b 100644
--- a/src/html/scan.js
+++ b/src/html/scan.js
@@ -9,6 +9,9 @@ let colorTimer = undefined;
let colorUpdated = false;
let storedModelId = 255;
let buttonActions = [];
+let modeSelectionInit = true;
+let originalUID = undefined;
+let originalUIDType = undefined;
function _(el) {
return document.getElementById(el);
@@ -24,13 +27,15 @@ function getPwmFormData() {
const invert = _(`pwm_${ch}_inv`).checked ? 1 : 0;
const narrow = _(`pwm_${ch}_nar`).checked ? 1 : 0;
const failsafeField = _(`pwm_${ch}_fs`);
+ const failsafeModeField = _(`pwm_${ch}_fsmode`);
let failsafe = failsafeField.value;
if (failsafe > 2011) failsafe = 2011;
if (failsafe < 988) failsafe = 988;
failsafeField.value = failsafe;
+ let failsafeMode = failsafeModeField.value;
- const raw = (narrow << 19) | (mode << 15) | (invert << 14) | (inChannel << 10) | (failsafe - 988);
- // console.log(`PWM ${ch} mode=${mode} input=${inChannel} fs=${failsafe} inv=${invert} nar=${narrow} raw=${raw}`);
+ const raw = (narrow << 19) | (mode << 15) | (invert << 14) | (inChannel << 10) | (failsafeMode << 20) | (failsafe - 988);
+ // console.log(`PWM ${ch} mode=${mode} input=${inChannel} fs=${failsafe} fsmode=${failsafeMode} inv=${invert} nar=${narrow} raw=${raw}`);
outData.push(raw);
++ch;
}
@@ -39,54 +44,93 @@ function getPwmFormData() {
function enumSelectGenerate(id, val, arOptions) {
// Generate a item with every option in arOptions, and select the val element (0-based)
- const retVal = `` +
+ const retVal = `` +
arOptions.map((item, idx) => {
- if (item) return `${item} `;
+ if (item) return `${item} `;
return '';
}).join('') + '
';
return retVal;
}
+function generateFeatureBadges(features) {
+ let str = '';
+ if (!!(features & 1)) str += `TX `;
+ else if (!!(features & 2)) str += `RX `;
+ if ((features & 12) === 12) str += `I2C `;
+ else if (!!(features & 4)) str += `SCL `;
+ else if (!!(features & 8)) str += `SDA `;
+ return str;
+}
+
@@if not isTX:
function updatePwmSettings(arPwm) {
if (arPwm === undefined) {
if (_('model_tab')) _('model_tab').style.display = 'none';
return;
}
- var pin1Index = undefined;
- var pin3Index = undefined;
+ var pinRxIndex = undefined;
+ var pinTxIndex = undefined;
+ var pinModes = []
// arPwm is an array of raw integers [49664,50688,51200]. 10 bits of failsafe position, 4 bits of input channel, 1 bit invert, 4 bits mode, 1 bit for narrow/750us
- const htmlFields = ['Output Mode Input Invert? 750us? Failsafe '];
+ const htmlFields = ['Output Features Mode Input Invert? 750us? Failsafe Mode Failsafe Pos '];
arPwm.forEach((item, index) => {
const failsafe = (item.config & 1023) + 988; // 10 bits
+ const failsafeMode = (item.config >> 20) & 3; // 2 bits
const ch = (item.config >> 10) & 15; // 4 bits
const inv = (item.config >> 14) & 1;
const mode = (item.config >> 15) & 15; // 4 bits
const narrow = (item.config >> 19) & 1;
- const pin = item.pin;
+ const features = item.features;
const modes = ['50Hz', '60Hz', '100Hz', '160Hz', '333Hz', '400Hz', '10KHzDuty', 'On/Off'];
- if (pin == 1) {
+ if (features & 16) {
+ modes.push('DShot');
+ } else {
+ modes.push(undefined);
+ }
+ if (features & 1) {
modes.push('Serial TX');
+ modes.push(undefined); // SCL
+ modes.push(undefined); // SDA
modes.push(undefined); // true PWM
- pin1Index = index;
- }
- if (pin == 3) {
+ pinRxIndex = index;
+ } else if (features & 2) {
modes.push('Serial RX');
+ modes.push(undefined); // SCL
+ modes.push(undefined); // SDA
+ modes.push(undefined); // true PWM
+ pinTxIndex = index;
+ } else {
+ modes.push(undefined); // Serial
+ if (features & 4) {
+ modes.push('I2C SCL');
+ } else {
+ modes.push(undefined);
+ }
+ if (features & 8) {
+ modes.push('I2C SDA');
+ } else {
+ modes.push(undefined);
+ }
modes.push(undefined); // true PWM
- pin3Index = index;
}
+ modes.push(undefined); // true PWM
const modeSelect = enumSelectGenerate(`pwm_${index}_mode`, mode, modes);
const inputSelect = enumSelectGenerate(`pwm_${index}_ch`, ch,
['ch1', 'ch2', 'ch3', 'ch4',
'ch5 (AUX1)', 'ch6 (AUX2)', 'ch7 (AUX3)', 'ch8 (AUX4)',
'ch9 (AUX5)', 'ch10 (AUX6)', 'ch11 (AUX7)', 'ch12 (AUX8)',
'ch13 (AUX9)', 'ch14 (AUX10)', 'ch15 (AUX11)', 'ch16 (AUX12)']);
- htmlFields.push(`${index+1}
+ const failsafeModeSelect = enumSelectGenerate(`pwm_${index}_fsmode`, failsafeMode,
+ ['Set Position', 'No Pulses', 'Last Position']); // match eServoOutputFailsafeMode
+ htmlFields.push(`${index + 1}
+ ${generateFeatureBadges(features)}
${modeSelect}
${inputSelect}
-
`);
+ ${failsafeModeSelect}
+
`);
+ pinModes[index] = mode;
});
htmlFields.push('
');
@@ -96,47 +140,92 @@ function updatePwmSettings(arPwm) {
_('pwm').appendChild(grp);
- var setDisabled = (index, onoff) => {
+ const setDisabled = (index, onoff) => {
_(`pwm_${index}_ch`).disabled = onoff;
_(`pwm_${index}_inv`).disabled = onoff;
_(`pwm_${index}_nar`).disabled = onoff;
_(`pwm_${index}_fs`).disabled = onoff;
+ _(`pwm_${index}_fsmode`).disabled = onoff;
}
- // put some contraints on pin1/3 mode selects
- if (pin1Index !== undefined && pin3Index !== undefined) {
- const pin1Mode = _(`pwm_${pin1Index}_mode`);
- const pin3Mode = _(`pwm_${pin3Index}_mode`);
- pin1Mode.onchange = () => {
- if (pin1Mode.value == 8) { // Serial
- pin3Mode.value = 8;
- setDisabled(pin1Index, true);
- setDisabled(pin3Index, true);
- pin3Mode.disabled = true;
+ arPwm.forEach((item,index)=>{
+ const pinMode = _(`pwm_${index}_mode`)
+ pinMode.onchange = () => {
+ setDisabled(index, pinMode.value > 9);
+ const updateOthers = (value, enable) => {
+ if (value > 9) { // disable others
+ arPwm.forEach((item, other) => {
+ if (other != index) {
+ document.querySelectorAll(`#pwm_${other}_mode option`).forEach(opt => {
+ if (opt.value == value) {
+ if (modeSelectionInit)
+ opt.disabled = true;
+ else
+ opt.disabled = enable;
+ }
+ });
+ }
+ })
+ }
+ }
+ updateOthers(pinMode.value, true); // disable others
+ updateOthers(pinModes[index], false); // enable others
+ pinModes[index] = pinMode.value;
+ }
+ pinMode.onchange();
+
+ // disable and hide the failsafe position field if not using the set-position failsafe mode
+ const failsafeMode = _(`pwm_${index}_fsmode`);
+ failsafeMode.onchange = () => {
+ const failsafeField = _(`pwm_${index}_fs`);
+ if (failsafeMode.value == 0) {
+ failsafeField.disabled = false;
+ failsafeField.style.display = 'block';
+ }
+ else {
+ failsafeField.disabled = true;
+ failsafeField.style.display = 'none';
+ }
+ };
+ failsafeMode.onchange();
+ });
+
+ modeSelectionInit = false;
+
+ // put some contraints on pinRx/Tx mode selects
+ if (pinRxIndex !== undefined && pinTxIndex !== undefined) {
+ const pinRxMode = _(`pwm_${pinRxIndex}_mode`);
+ const pinTxMode = _(`pwm_${pinTxIndex}_mode`);
+ pinRxMode.onchange = () => {
+ if (pinRxMode.value == 9) { // Serial
+ pinTxMode.value = 9;
+ setDisabled(pinRxIndex, true);
+ setDisabled(pinTxIndex, true);
+ pinTxMode.disabled = true;
_('serial-config').style.display = 'block';
_('baud-config').style.display = 'block';
}
else {
- pin3Mode.value = 0;
- setDisabled(pin1Index, false);
- setDisabled(pin3Index, false);
- pin3Mode.disabled = false;
+ pinTxMode.value = 0;
+ setDisabled(pinRxIndex, false);
+ setDisabled(pinTxIndex, false);
+ pinTxMode.disabled = false;
_('serial-config').style.display = 'none';
_('baud-config').style.display = 'none';
}
}
- pin3Mode.onchange = () => {
- if (pin3Mode.value == 8) { // Serial
- pin1Mode.value = 8;
- setDisabled(pin1Index, true);
- setDisabled(pin3Index, true);
- pin3Mode.disabled = true;
+ pinTxMode.onchange = () => {
+ if (pinTxMode.value == 9) { // Serial
+ pinRxMode.value = 9;
+ setDisabled(pinRxIndex, true);
+ setDisabled(pinTxIndex, true);
+ pinTxMode.disabled = true;
_('serial-config').style.display = 'block';
_('baud-config').style.display = 'block';
}
}
- const pin3 = pin3Mode.value;
- pin1Mode.onchange();
- if(pin1Mode.value != 8) pin3Mode.value = pin3;
+ const pinTx = pinTxMode.value;
+ pinRxMode.onchange();
+ if(pinRxMode.value != 9) pinTxMode.value = pinTx;
}
}
@@end
@@ -152,7 +241,7 @@ function init() {
_('model-match').onclick = () => {
if (_('model-match').checked) {
_('modelid').style.display = 'block';
- if (storedModelId == 255) {
+ if (storedModelId === 255) {
_('modelid').value = '';
} else {
_('modelid').value = storedModelId;
@@ -162,85 +251,79 @@ function init() {
_('modelid').value = '255';
}
};
+ // Start on the model tab
+ mui.tabs.activate('pane-justified-3');
+@@else:
+ // Start on the options tab
+ mui.tabs.activate('pane-justified-1');
@@end
+ initFiledrag();
initOptions();
}
-function changeCurrentColors() {
- if (colorTimer === undefined) {
- sendCurrentColors();
- colorTimer = setInterval(timeoutCurrentColors, 50);
- } else {
- colorUpdated = true;
- }
-}
-
-function sendCurrentColors() {
- const formData = new FormData(_('upload_options'));
- const data = Object.fromEntries(formData);
- colors = [];
- for (const [k, v] of Object.entries(data)) {
- if (_(k) && _(k).type == 'color') {
- const index = parseInt(k.substring('6')) - 1;
- if (_(k + '-div').style.display === 'none') colors[index] = -1;
- else colors[index] = parseInt(v.substring(1), 16);
- }
- }
- const xmlhttp = new XMLHttpRequest();
- xmlhttp.open('POST', '/buttons', true);
- xmlhttp.setRequestHeader('Content-type', 'application/json');
- xmlhttp.send(JSON.stringify(colors));
- colorUpdated = false;
-}
-
-function timeoutCurrentColors() {
- if (colorUpdated) {
- sendCurrentColors();
- } else {
- clearInterval(colorTimer);
- colorTimer = undefined;
- }
-}
-
-function updateConfig(data, options) {
- if (data.product_name) _('product_name').textContent = data.product_name;
- if (data.reg_domain) _('reg_domain').textContent = data.reg_domain;
- if (data.uid) _('uid').value = data.uid.toString();
-
+function updateUIDType(uidtype) {
let bg = '';
- let fg = '';
- let text = data.uidtype;
+ let fg = 'white';
let desc = '';
- if (!data.uidtype || data.uidtype === 'Not set') {
- bg = '#D50000'; // default 'red' for 'Not set'
- fg = 'white';
- text = 'Not set';
- desc = 'The default binding UID from the device address will be used';
+
+ if (!uidtype || uidtype.startsWith('Not set')) // TX
+ {
+ bg = '#D50000'; // red/white
+ uidtype = 'Not set';
+ desc = 'Using autogenerated binding UID';
}
- if (data.uidtype === 'Flashed') {
+ else if (uidtype === 'Flashed') // TX
+ {
bg = '#1976D2'; // blue/white
- fg = 'white';
desc = 'The binding UID was generated from a binding phrase set at flash time';
}
- if (data.uidtype === 'Overridden') {
- bg = '#689F38'; // green
+ else if (uidtype === 'Overridden') // TX
+ {
+ bg = '#689F38'; // green/black
fg = 'black';
- desc = 'The binding UID has been generated from a bind-phrase previously entered into the "binding phrase" field above';
+ desc = 'The binding UID has been generated from a binding phrase previously entered into the "binding phrase" field above';
}
- if (data.uidtype === 'Traditional') {
- bg = '#D50000'; // red
- fg = 'white';
- desc = 'The binding UID has been set using traditional binding method i.e. button or 3-times power cycle and bound via the Lua script';
+ else if (uidtype === 'Modified') // edited here
+ {
+ bg = '#7c00d5'; // purple
+ desc = 'The binding UID has been modified, but not yet saved';
}
- if (data.uidtype === 'On loan') {
+ else if (uidtype === 'Volatile') // RX
+ {
bg = '#FFA000'; // amber
- fg = 'black';
- desc = 'The binding UID has been set using the model-loan feature';
+ desc = 'The binding UID will be cleared on boot';
}
+ else // RX
+ {
+ if (_('uid').value.endsWith('0,0,0,0'))
+ {
+ bg = '#FFA000'; // amber
+ uidtype = 'Not bound';
+ desc = 'This receiver is unbound and will boot to binding mode';
+ }
+ else
+ {
+ bg = '#1976D2'; // blue/white
+ uidtype = 'Bound';
+ desc = 'This receiver is bound and will boot waiting for connection';
+ }
+ }
+
_('uid-type').style.backgroundColor = bg;
_('uid-type').style.color = fg;
- _('uid-type').textContent = text;
+ _('uid-type').textContent = uidtype;
_('uid-text').textContent = desc;
+}
+
+function updateConfig(data, options) {
+ if (data.product_name) _('product_name').textContent = data.product_name;
+ if (data.reg_domain) _('reg_domain').textContent = data.reg_domain;
+ if (data.uid) {
+ _('uid').value = data.uid.toString();
+ originalUID = data.uid;
+ }
+ originalUIDType = data.uidtype;
+ updateUIDType(data.uidtype);
if (data.mode==='STA') {
_('stamode').style.display = 'block';
@@ -249,7 +332,7 @@ function updateConfig(data, options) {
_('apmode').style.display = 'block';
}
@@if not isTX:
- if (data.hasOwnProperty('modelid') && data.modelid != 255) {
+ if (data.hasOwnProperty('modelid') && data.modelid !== 255) {
_('modelid').style.display = 'block';
_('model-match').checked = true;
storedModelId = data.modelid;
@@ -261,29 +344,46 @@ function updateConfig(data, options) {
_('modelid').value = storedModelId;
_('force-tlm').checked = data.hasOwnProperty('force-tlm') && data['force-tlm'];
_('serial-protocol').onchange = () => {
+ const proto = Number(_('serial-protocol').value);
if (_('is-airport').checked) {
_('rcvr-uart-baud').disabled = false;
_('rcvr-uart-baud').value = options['rcvr-uart-baud'];
+ _('serial-config').style.display = 'none';
+ _('sbus-config').style.display = 'none';
+ return;
}
- else if (_('serial-protocol').value == 0 || _('serial-protocol').value == 1) {
+ _('serial-config').style.display = 'block';
+ if (proto === 0 || proto === 1) { // Airport or CRSF
_('rcvr-uart-baud').disabled = false;
_('rcvr-uart-baud').value = options['rcvr-uart-baud'];
+ _('sbus-config').style.display = 'none';
}
- else if (_('serial-protocol').value == 2 || _('serial-protocol').value == 3) {
+ else if (proto === 2 || proto === 3 || proto === 5) { // SBUS (and inverted) or DJI-RS Pro
_('rcvr-uart-baud').disabled = true;
_('rcvr-uart-baud').value = '100000';
_('sbus-config').style.display = 'block';
_('sbus-failsafe').value = data['sbus-failsafe'];
}
- else if (_('serial-protocol').value == 4) {
+ else if (proto === 4) { // SUMD
_('rcvr-uart-baud').disabled = true;
_('rcvr-uart-baud').value = '115200';
+ _('sbus-config').style.display = 'none';
+ }
+ else if (proto === 6) { // HoTT
+ _('rcvr-uart-baud').disabled = true;
+ _('rcvr-uart-baud').value = '19200';
+ _('sbus-config').style.display = 'none';
}
}
updatePwmSettings(data.pwm);
_('serial-protocol').value = data['serial-protocol'];
_('serial-protocol').onchange();
_('is-airport').onchange = _('serial-protocol').onchange;
+ _('vbind').checked = data.hasOwnProperty('vbind') && data['vbind'];
+ _('vbind').onchange = () => {
+ _('bindphrase').style.display = _('vbind').checked ? 'none' : 'block';
+ }
+ _('vbind').onchange();
@@end
@@if isTX:
if (data.hasOwnProperty['button-colors']) {
@@ -305,14 +405,13 @@ function updateConfig(data, options) {
}
function initOptions() {
- initBindingPhraseGen();
const xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
- if (this.readyState == 4 && this.status == 200) {
+ if (this.readyState === 4 && this.status === 200) {
const data = JSON.parse(this.responseText);
updateOptions(data['options']);
updateConfig(data['config'], data['options']);
- setTimeout(getNetworks, 2000);
+ initBindingPhraseGen();
}
};
xmlhttp.open('GET', '/config', true);
@@ -322,7 +421,7 @@ function initOptions() {
function getNetworks() {
const xmlhttp = new XMLHttpRequest();
xmlhttp.onload = function() {
- if (this.status == 204) {
+ if (this.status === 204) {
setTimeout(getNetworks, 2000);
} else {
const data = JSON.parse(this.responseText);
@@ -339,12 +438,40 @@ function getNetworks() {
xmlhttp.send();
}
+_('network-tab').addEventListener('mui.tabs.showstart', getNetworks);
+
// =========================================================
-function uploadFile() {
+function initFiledrag() {
+ const fileselect = _('firmware_file');
+ const filedrag = _('filedrag');
+
+ fileselect.addEventListener('change', fileSelectHandler, false);
+
+ const xhr = new XMLHttpRequest();
+ if (xhr.upload) {
+ filedrag.addEventListener('dragover', fileDragHover, false);
+ filedrag.addEventListener('dragleave', fileDragHover, false);
+ filedrag.addEventListener('drop', fileSelectHandler, false);
+ filedrag.style.display = 'block';
+ }
+}
+
+function fileDragHover(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ if (e.target === _('filedrag')) e.target.className = (e.type === 'dragover' ? 'hover' : '');
+}
+
+function fileSelectHandler(e) {
+ fileDragHover(e);
+ const files = e.target.files || e.dataTransfer.files;
+ uploadFile(files[0]);
+}
+
+function uploadFile(file) {
_('upload_btn').disabled = true
try {
- const file = _('firmware_file').files[0];
const formdata = new FormData();
formdata.append('upload', file, file.name);
const ajax = new XMLHttpRequest();
@@ -387,7 +514,7 @@ function completeHandler(event) {
percent = percent + 2;
_('progressBar').value = percent;
_('status').innerHTML = percent + '% flashed... please wait';
- if (percent == 100) {
+ if (percent === 100) {
clearInterval(interval);
_('status').innerHTML = '';
_('progressBar').value = 0;
@@ -404,10 +531,10 @@ function completeHandler(event) {
}).then((e)=>{
const xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
- if (this.readyState == 4) {
+ if (this.readyState === 4) {
_('status').innerHTML = '';
_('progressBar').value = 0;
- if (this.status == 200) {
+ if (this.status === 200) {
const data = JSON.parse(this.responseText);
cuteAlert({
type: 'info',
@@ -459,11 +586,7 @@ function abortHandler(event) {
});
}
-_('upload_form').addEventListener('submit', (e) => {
- e.preventDefault();
- uploadFile();
-});
-
+@@if isTX:
_('fileselect').addEventListener('change', (e) => {
const files = e.target.files || e.dataTransfer.files;
const reader = new FileReader();
@@ -471,8 +594,8 @@ _('fileselect').addEventListener('change', (e) => {
xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
_('fileselect').value = '';
- if (this.readyState == 4) {
- if (this.status == 200) {
+ if (this.readyState === 4) {
+ if (this.status === 200) {
cuteAlert({
type: 'info',
title: 'Upload Model Configuration',
@@ -493,6 +616,7 @@ _('fileselect').addEventListener('change', (e) => {
}
reader.readAsText(files[0]);
}, false);
+@@end
// =========================================================
@@ -502,8 +626,8 @@ function callback(title, msg, url, getdata, success) {
e.preventDefault();
xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
- if (this.readyState == 4) {
- if (this.status == 200) {
+ if (this.readyState === 4) {
+ if (this.status === 200) {
if (success) success();
cuteAlert({
type: 'info',
@@ -555,7 +679,7 @@ _('reset-options').addEventListener('click', callback('Reset Runtime Options', '
_('sethome').addEventListener('submit', setupNetwork);
_('connect').addEventListener('click', callback('Connect to Home Network', 'An error occurred connecting to the Home network', '/connect', null));
-if (_('config') != undefined) {
+if (_('config')) {
_('config').addEventListener('submit', callback('Set Configuration', 'An error occurred updating the configuration', '/config',
(xmlhttp) => {
xmlhttp.setRequestHeader('Content-Type', 'application/json');
@@ -564,9 +688,16 @@ if (_('config') != undefined) {
"serial-protocol": +_('serial-protocol').value,
"sbus-failsafe": +_('sbus-failsafe').value,
"modelid": +_('modelid').value,
- "force-tlm": +_('force-tlm').checked
+ "force-tlm": +_('force-tlm').checked,
+ "vbind": +_('vbind').checked,
+ "uid": _('uid').value.split(',').map(Number),
});
- }));
+ }, () => {
+ originalUID = _('uid').value;
+ originalUIDType = 'Bound';
+ _('phrase').value = '';
+ updateUIDType(originalUIDType);
+ }));
}
function submitOptions(e) {
@@ -587,25 +718,25 @@ function submitOptions(e) {
xhr.send(JSON.stringify(formObject, function(k, v) {
if (v === '') return undefined;
if (_(k)) {
- if (_(k).type == 'color') return undefined;
- if (_(k).type == 'checkbox') return v === 'on';
+ if (_(k).type === 'color') return undefined;
+ if (_(k).type === 'checkbox') return v === 'on';
if (_(k).classList.contains('datatype-boolean')) return v === 'true';
if (_(k).classList.contains('array')) {
const arr = v.split(',').map((element) => {
return Number(element);
});
- return arr.length == 0 ? undefined : arr;
+ return arr.length === 0 ? undefined : arr;
}
}
if (typeof v === 'boolean') return v;
- if (v == 'true') return true;
- if (v == 'false') return false;
+ if (v === 'true') return true;
+ if (v === 'false') return false;
return isNaN(v) ? v : +v;
}));
xhr.onreadystatechange = function() {
- if (this.readyState == 4) {
- if (this.status == 200) {
+ if (this.readyState === 4) {
+ if (this.status === 200) {
cuteAlert({
type: 'question',
title: 'Upload Succeeded',
@@ -613,7 +744,13 @@ function submitOptions(e) {
confirmText: 'Reboot',
cancelText: 'Close'
}).then((e) => {
- if (e == 'confirm') {
+@@if isTX:
+ originalUID = _('uid').value;
+ originalUIDType = 'Overridden';
+ _('phrase').value = '';
+ updateUIDType(originalUIDType);
+@@end
+ if (e === 'confirm') {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/reboot');
xhr.setRequestHeader('Content-Type', 'application/json');
@@ -647,8 +784,8 @@ function submitButtonActions(e) {
xhr.send(JSON.stringify({'button-actions': buttonActions}));
xhr.onreadystatechange = function() {
- if (this.readyState == 4) {
- if (this.status == 200) {
+ if (this.readyState === 4) {
+ if (this.status === 200) {
cuteAlert({
type: 'info',
title: 'Success',
@@ -661,7 +798,7 @@ function submitButtonActions(e) {
message: 'An error occurred while saving button configuration'
});
}
- };
+ }
}
}
_('submit-actions').addEventListener('click', submitButtonActions);
@@ -669,8 +806,9 @@ _('submit-actions').addEventListener('click', submitButtonActions);
function updateOptions(data) {
for (const [key, value] of Object.entries(data)) {
+ if (key ==='wifi-on-interval' && value === -1) continue;
if (_(key)) {
- if (_(key).type == 'checkbox') {
+ if (_(key).type === 'checkbox') {
_(key).checked = value;
} else {
if (Array.isArray(value)) _(key).value = value.toString();
@@ -704,7 +842,9 @@ function updateButtons(data) {
for (const [p, v] of Object.entries(_v['action'])) {
appendRow(parseInt(b), parseInt(p), v);
}
- _(`button${parseInt(b)+1}-color-div`).style.display = 'block';
+ if (_v['color'] !== undefined) {
+ _(`button${parseInt(b)+1}-color-div`).style.display = 'block';
+ }
_(`button${parseInt(b)+1}-color`).value = toRGB(_v['color']);
}
_('button1-color').oninput = changeCurrentColors;
@@ -731,7 +871,7 @@ function sendCurrentColors() {
const data = Object.fromEntries(formData);
colors = [];
for (const [k, v] of Object.entries(data)) {
- if (_(k) && _(k).type == 'color') {
+ if (_(k) && _(k).type === 'color') {
const index = parseInt(k.substring('6')) - 1;
if (_(k + '-div').style.display === 'none') colors[index] = -1;
else colors[index] = to8bit(v);
@@ -919,27 +1059,49 @@ md5 = function() {
return calcMD5;
}();
+function isValidUidByte(s) {
+ let f = parseFloat(s);
+ return !isNaN(f) && isFinite(s) && Number.isInteger(f) && f >= 0 && f < 256;
+}
+
function uidBytesFromText(text) {
+ // If text is 4-6 numbers separated with [commas]/[spaces] use as a literal UID
+ // This is a strict parser to not just extract numbers from text, but only accept if text is only UID bytes
+ if (/^[0-9, ]+$/.test(text))
+ {
+ let asArray = text.split(',').filter(isValidUidByte).map(Number);
+ if (asArray.length >= 4 && asArray.length <= 6)
+ {
+ while (asArray.length < 6)
+ asArray.unshift(0);
+ return asArray;
+ }
+ }
+
const bindingPhraseFull = `-DMY_BINDING_PHRASE="${text}"`;
const bindingPhraseHashed = md5(bindingPhraseFull);
- const uidBytes = bindingPhraseHashed.subarray(0, 6);
-
- return uidBytes;
+ return bindingPhraseHashed.subarray(0, 6);
}
function initBindingPhraseGen() {
const uid = _('uid');
function setOutput(text) {
- const uidBytes = uidBytesFromText(text);
- uid.value = uidBytes;
+ if (text.length === 0) {
+ uid.value = originalUID.toString();
+ updateUIDType(originalUIDType);
+ }
+ else {
+ uid.value = uidBytesFromText(text.trim());
+ updateUIDType('Modified');
+ }
}
function updateValue(e) {
setOutput(e.target.value);
}
- phrase.addEventListener('input', updateValue);
+ _('phrase').addEventListener('input', updateValue);
setOutput('');
}
diff --git a/src/include/common.h b/src/include/common.h
index b4f7dbe329..66084389b0 100644
--- a/src/include/common.h
+++ b/src/include/common.h
@@ -5,6 +5,8 @@
#if defined(RADIO_SX127X)
#include "SX127xDriver.h"
+#elif defined(RADIO_LR1121)
+#include "LR1121Driver.h"
#elif defined(RADIO_SX128X)
#include "SX1280Driver.h"
#else
@@ -16,10 +18,7 @@
// Used to XOR with OtaCrcInitializer and macSeed to reduce compatibility with previous versions.
// It should be incremented when the OTA packet structure is modified.
#define OTA_VERSION_ID 3
-
-extern uint8_t BindingUID[6];
-extern uint8_t UID[6];
-extern uint8_t MasterUID[6];
+#define UID_LEN 6
typedef enum : uint8_t
{
@@ -44,9 +43,10 @@ typedef enum
MODE_STATES,
// States below here are special mode states
noCrossfire,
+ bleJoystick,
+ NO_CONFIG_SAVE_STATES,
wifiUpdate,
serialUpdate,
- bleJoystick,
// Failure states go below here to display immediately
FAILURE_STATES,
radioFailed,
@@ -70,9 +70,6 @@ typedef enum
tim_locked = 2
} RXtimerState_e;
-extern connectionState_e connectionState;
-extern bool connectionHasModelMatch;
-
typedef enum
{
RF_DOWNLINK_INFO = 0,
@@ -97,10 +94,14 @@ typedef enum : uint8_t
RATE_FLRC_500HZ,
RATE_FLRC_1000HZ,
RATE_DVDA_50HZ,
-} expresslrs_RFrates_e; // Max value of 16 since only 4 bits have been assigned in the sync package.
+ RATE_LORA_200HZ_8CH,
+} expresslrs_RFrates_e;
enum {
RADIO_TYPE_SX127x_LORA,
+ RADIO_TYPE_LR1121_LORA_900,
+ RADIO_TYPE_LR1121_LORA_2G4,
+ RADIO_TYPE_LR1121_LORA_DUAL,
RADIO_TYPE_SX128x_LORA,
RADIO_TYPE_SX128x_FLRC,
};
@@ -115,6 +116,10 @@ typedef enum : uint8_t
// Value used for expresslrs_rf_pref_params_s.DynpowerUpThresholdSnr if SNR should not be used
#define DYNPOWER_SNR_THRESH_NONE -127
+#define SNR_SCALE(snr) ((int8_t)((float)snr * RADIO_SNR_SCALE))
+#define SNR_DESCALE(snrScaled) (snrScaled / RADIO_SNR_SCALE)
+// Bound is any of the last 4 bytes nonzero (unbound is all zeroes)
+#define UID_IS_BOUND(uid) (uid[2] != 0 && uid[3] != 0 && uid[4] != 0 && uid[5] != 0)
typedef struct expresslrs_rf_pref_params_s
{
@@ -140,17 +145,20 @@ typedef struct expresslrs_mod_settings_s
uint8_t bw;
uint8_t sf;
uint8_t cr;
+ uint8_t PreambleLen;
+#if defined(RADIO_LR1121)
+ uint8_t bw2;
+ uint8_t sf2;
+ uint8_t cr2;
+ uint8_t PreambleLen2;
+#endif
expresslrs_tlm_ratio_e TLMinterval; // every X packets is a response TLM packet, should be a power of 2
uint8_t FHSShopInterval; // every X packets we hop to a new frequency. Max value of 16 since only 4 bits have been assigned in the sync package.
int32_t interval; // interval in us seconds that corresponds to that frequency
- uint8_t PreambleLen;
uint8_t PayloadLength; // Number of OTA bytes to be sent.
uint8_t numOfSends; // Number of packets to send.
} expresslrs_mod_settings_t;
-// The config mode only allows a maximum of 2 actions per button
-#define MAX_BUTTON_ACTIONS 2
-
// Limited to 16 possible ACTIONs by config storage currently
typedef enum : uint8_t {
ACTION_NONE,
@@ -175,10 +183,20 @@ enum eServoOutputMode : uint8_t
som400Hz,
som10KHzDuty,
somOnOff, // Digital 0/1 mode
+ somDShot, // DShot300
somSerial, // Serial TX or RX depending on pin
+ somSCL, // I2C clock signal
+ somSDA, // I2C data line
somPwm, // True PWM mode (NOT SUPPORTED)
};
+enum eServoOutputFailsafeMode : uint8_t
+{
+ PWMFAILSAFE_SET_POSITION, // user customizable pulse value
+ PWMFAILSAFE_NO_PULSES, // stop pulsing
+ PWMFAILSAFE_LAST_POSITION, // continue to pulse last used value
+};
+
enum eSerialProtocol : uint8_t
{
PROTOCOL_CRSF,
@@ -186,7 +204,8 @@ enum eSerialProtocol : uint8_t
PROTOCOL_SBUS,
PROTOCOL_INVERTED_SBUS,
PROTOCOL_SUMD,
- PROTOCOL_DJI_RS_PRO
+ PROTOCOL_DJI_RS_PRO,
+ PROTOCOL_HOTT_TLM
};
enum eFailsafeMode : uint8_t
@@ -196,6 +215,28 @@ enum eFailsafeMode : uint8_t
FAILSAFE_SET_POSITION
};
+enum eAuxChannels : uint8_t
+{
+ AUX1 = 4,
+ AUX2 = 5,
+ AUX3 = 6,
+ AUX4 = 7,
+ AUX5 = 8,
+ AUX6 = 9,
+ AUX7 = 10,
+ AUX8 = 11,
+ AUX9 = 12,
+ AUX10 = 13,
+ AUX11 = 14,
+ AUX12 = 15,
+ CRSF_NUM_CHANNELS = 16
+};
+
+//ELRS SPECIFIC OTA CRC
+//Koopman formatting https://users.ece.cmu.edu/~koopman/crc/
+#define ELRS_CRC_POLY 0x07 // 0x83
+#define ELRS_CRC14_POLY 0x2E57 // 0x372B
+
#ifndef UNIT_TEST
#if defined(RADIO_SX127X)
#define RATE_MAX 6
@@ -203,13 +244,20 @@ enum eFailsafeMode : uint8_t
extern SX127xDriver Radio;
+#elif defined(RADIO_LR1121)
+#define RATE_MAX 14
+#define RATE_BINDING RATE_LORA_50HZ
+#define RATE_DUALBAND_BINDING 9 // 2.4GHz 50Hz
+
+extern LR1121Driver Radio;
+
#elif defined(RADIO_SX128X)
#define RATE_MAX 10 // 2xFLRC + 2xDVDA + 4xLoRa + 2xFullRes
#define RATE_BINDING RATE_LORA_50HZ
extern SX1280Driver Radio;
#endif
-
+#endif // UNIT_TEST
expresslrs_mod_settings_s *get_elrs_airRateConfig(uint8_t index);
expresslrs_rf_pref_params_s *get_elrs_RFperfParams(uint8_t index);
@@ -219,36 +267,16 @@ uint8_t TLMratioEnumToValue(expresslrs_tlm_ratio_e const enumval);
uint8_t TLMBurstMaxForRateRatio(uint16_t const rateHz, uint8_t const ratioDiv);
uint8_t enumRatetoIndex(expresslrs_RFrates_e const eRate);
+extern uint8_t UID[UID_LEN];
+extern bool connectionHasModelMatch;
+extern bool teamraceHasModelMatch;
+extern bool InBindingMode;
extern uint8_t ExpressLRS_currTlmDenom;
+extern connectionState_e connectionState;
extern expresslrs_mod_settings_s *ExpressLRS_currAirRate_Modparams;
extern expresslrs_rf_pref_params_s *ExpressLRS_currAirRate_RFperfParams;
+extern uint32_t ChannelData[CRSF_NUM_CHANNELS]; // Current state of channels, CRSF format
-#define SNR_SCALE(snr) ((int8_t)((float)snr * RADIO_SNR_SCALE))
-#define SNR_DESCALE(snrScaled) (snrScaled / RADIO_SNR_SCALE)
-
-#endif // UNIT_TEST
-
-uint32_t uidMacSeedGet(void);
-void initUID();
+uint32_t uidMacSeedGet();
bool isDualRadio();
-
-#define CRSF_NUM_CHANNELS 16
-#define AUX1 4
-#define AUX2 5
-#define AUX3 6
-#define AUX4 7
-#define AUX5 8
-#define AUX6 9
-#define AUX7 10
-#define AUX8 11
-#define AUX9 12
-#define AUX10 13
-#define AUX11 14
-#define AUX12 15
-
-//ELRS SPECIFIC OTA CRC
-//Koopman formatting https://users.ece.cmu.edu/~koopman/crc/
-#define ELRS_CRC_POLY 0x07 // 0x83
-#define ELRS_CRC14_POLY 0x2E57 // 0x372B
-
-extern uint32_t ChannelData[CRSF_NUM_CHANNELS]; // Current state of channels, CRSF format
+void EnterBindingModeSafely(); // defined in rx_main/tx_main
\ No newline at end of file
diff --git a/src/include/deferred.h b/src/include/deferred.h
new file mode 100644
index 0000000000..35d3e0c756
--- /dev/null
+++ b/src/include/deferred.h
@@ -0,0 +1,11 @@
+#pragma once
+
+#include
+
+void deferExecutionMicros(unsigned long us, std::function f);
+void executeDeferredFunction(unsigned long now);
+
+static inline void deferExecutionMillis(unsigned long ms, std::function f)
+{
+ deferExecutionMicros(ms * 1000, f);
+}
\ No newline at end of file
diff --git a/src/include/dynpower.h b/src/include/dynpower.h
index f3c517d0ed..ba89a968a8 100644
--- a/src/include/dynpower.h
+++ b/src/include/dynpower.h
@@ -1,5 +1,3 @@
-#if defined(TARGET_TX)
-
#pragma once
#include
@@ -7,6 +5,9 @@
#include
#include
#include
+
+#if defined(TARGET_TX)
+
#include
#define DYNPOWER_UPDATE_NOUPDATE -128
@@ -19,4 +20,11 @@ void DynamicPower_Update(uint32_t now);
// Call DynamicPower_TelemetryUpdate from ISR with DYNPOWER_UPDATE_MISSED or ScaledSNR value
void DynamicPower_TelemetryUpdate(int8_t snrScaled);
-#endif
\ No newline at end of file
+#endif // TARGET_TX
+
+#if defined(TARGET_RX)
+
+// Call DynamicPower_UpdateRx from loop()
+void DynamicPower_UpdateRx(bool initialize);
+
+#endif // TARGET_RX
\ No newline at end of file
diff --git a/src/include/hardware.h b/src/include/hardware.h
index 3fcd694883..cf66b894c3 100644
--- a/src/include/hardware.h
+++ b/src/include/hardware.h
@@ -18,6 +18,7 @@ typedef enum {
HARDWARE_radio_nss,
HARDWARE_radio_nss_2,
HARDWARE_radio_rst,
+ HARDWARE_radio_rst_2,
HARDWARE_radio_sck,
HARDWARE_radio_dcdc,
HARDWARE_radio_rfo_hf,
@@ -46,6 +47,8 @@ typedef enum {
HARDWARE_power_control,
HARDWARE_power_values,
+ HARDWARE_power_values2,
+ HARDWARE_power_values_dual,
// Input
HARDWARE_joystick,
@@ -69,7 +72,7 @@ typedef enum {
HARDWARE_led_green_red,
HARDWARE_led_red,
HARDWARE_led_red_invert,
- HARDWARE_led_reg_green,
+ HARDWARE_led_red_green,
HARDWARE_led_rgb,
// GPIO_PIN_LED_WS2812_FAST // stm32
HARDWARE_led_rgb_isgrb,
@@ -135,6 +138,8 @@ typedef enum {
HARDWARE_vtx_sck,
HARDWARE_vtx_amp_vpd_25mW,
HARDWARE_vtx_amp_vpd_100mW,
+ HARDWARE_vtx_amp_pwm_25mW,
+ HARDWARE_vtx_amp_pwm_100mW,
HARDWARE_LAST
} nameType;
diff --git a/src/lib/helpers/helpers.h b/src/include/helpers.h
similarity index 62%
rename from src/lib/helpers/helpers.h
rename to src/include/helpers.h
index ea828fe555..2aea12c579 100644
--- a/src/lib/helpers/helpers.h
+++ b/src/include/helpers.h
@@ -1,5 +1,7 @@
#pragma once
+#include "targets.h"
+
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
#ifndef UNUSED
@@ -9,33 +11,32 @@
class NullStream : public Stream
{
public:
- int available(void)
+ int available() override
{
return 0;
}
- void flush(void)
+ void flush() override
{
- return;
}
- int peek(void)
+ int peek() override
{
return -1;
}
- int read(void)
+ int read() override
{
return -1;
}
- size_t write(uint8_t u_Data)
+ size_t write(uint8_t u_Data) override
{
UNUSED(u_Data);
return 0x01;
}
- size_t write(const uint8_t *buffer, size_t size)
+ size_t write(const uint8_t *buffer, size_t size) override
{
UNUSED(buffer);
return size;
diff --git a/src/include/native.h b/src/include/native.h
index ae0d49c494..ee23fadd02 100644
--- a/src/include/native.h
+++ b/src/include/native.h
@@ -34,7 +34,7 @@ class Stream
// Print methods
virtual size_t write(uint8_t c) = 0;
- virtual size_t write(uint8_t *s, int l) = 0;
+ virtual size_t write(const uint8_t *s, size_t l) = 0;
int print(const char *s) {return 0;}
int print(uint8_t s) {return 0;}
@@ -59,7 +59,7 @@ class HardwareSerial: public Stream {
// Print methods
size_t write(uint8_t c) {return 1;}
- size_t write(uint8_t *s, int l) {return l;}
+ size_t write(const uint8_t *s, size_t l) {return l;}
int print(const char *s) {return 0;}
int print(uint8_t s) {return 0;}
diff --git a/src/include/rxtx_common.h b/src/include/rxtx_common.h
index f3c90ebcc9..8af84db2d9 100644
--- a/src/include/rxtx_common.h
+++ b/src/include/rxtx_common.h
@@ -12,8 +12,6 @@
#include "LQCALC.h"
#include "OTA.h"
#include "POWERMGNT.h"
-#include "FIFO_GENERIC.h"
+#include "deferred.h"
void setupTargetCommon();
-void deferExecution(uint32_t ms, std::function f);
-void executeDeferredFunction(unsigned long now);
diff --git a/src/include/target/DIY_900_RX_STM32.h b/src/include/target/DIY_900_RX_STM32.h
index 456a9d9d32..fc7b4e3f7f 100644
--- a/src/include/target/DIY_900_RX_STM32.h
+++ b/src/include/target/DIY_900_RX_STM32.h
@@ -28,4 +28,4 @@
#define MinPower PWR_10mW
#define MaxPower PWR_50mW
#define DefaultPower PWR_50mW
-#define POWER_OUTPUT_VALUES {8,12,15}
+#define POWER_OUTPUT_VALUES {120,124,127}
diff --git a/src/include/target/DIY_900_RX_STM32_SX1272.h b/src/include/target/DIY_900_RX_STM32_SX1272.h
index 9ca6f091fd..73a394301e 100644
--- a/src/include/target/DIY_900_RX_STM32_SX1272.h
+++ b/src/include/target/DIY_900_RX_STM32_SX1272.h
@@ -35,4 +35,4 @@
#define MinPower PWR_10mW
#define MaxPower PWR_50mW
#define DefaultPower PWR_50mW
-#define POWER_OUTPUT_VALUES {8,12,15}
\ No newline at end of file
+#define POWER_OUTPUT_VALUES {120,124,127}
\ No newline at end of file
diff --git a/src/include/target/DIY_900_TX_STM32_SX1272.h b/src/include/target/DIY_900_TX_STM32_SX1272.h
index d9782f3706..2f95959967 100644
--- a/src/include/target/DIY_900_TX_STM32_SX1272.h
+++ b/src/include/target/DIY_900_TX_STM32_SX1272.h
@@ -37,4 +37,4 @@
#define MinPower PWR_10mW
#define MaxPower PWR_50mW
#define DefaultPower PWR_50mW
-#define POWER_OUTPUT_VALUES {8,12,15}
+#define POWER_OUTPUT_VALUES {120,124,127}
diff --git a/src/include/target/FM30_RX_MINI.h b/src/include/target/FM30_RX_MINI.h
index 96a4e7c29a..95fc272ee5 100644
--- a/src/include/target/FM30_RX_MINI.h
+++ b/src/include/target/FM30_RX_MINI.h
@@ -12,7 +12,7 @@
#endif
#endif
-#define USE_SX1280_DCDC
+#define USE_HARDWARE_DCDC
// GPIO pin definitions
#define GPIO_PIN_NSS PA15
diff --git a/src/include/target/FM30_TX.h b/src/include/target/FM30_TX.h
index 8025c8e4c3..1a69dab3bb 100644
--- a/src/include/target/FM30_TX.h
+++ b/src/include/target/FM30_TX.h
@@ -5,7 +5,7 @@
// There is some special handling for this target
#define TARGET_TX_FM30
#define USE_SX1280_DCDC
-//#define CRITICAL_FLASH
+#define CRITICAL_FLASH
// GPIO pin definitions
#define GPIO_PIN_NSS PB12
diff --git a/src/include/target/Frsky_RX_R9M.h b/src/include/target/Frsky_RX_R9M.h
index fe54fd2233..1f0690809e 100644
--- a/src/include/target/Frsky_RX_R9M.h
+++ b/src/include/target/Frsky_RX_R9M.h
@@ -71,7 +71,7 @@ Credit to Jacob Walser (jaxxzer) for the pinout!!!
#define GPIO_PIN_BUTTON PC13 // pullup e.g. LOW when pressed
#endif
-#define POWER_OUTPUT_FIXED 15 //MAX power for 900 RXes
+#define POWER_OUTPUT_FIXED 127 //MAX power for 900 RXes
// External pads
// #define R9m_Ch1 PA8
// #define R9m_Ch2 PA11
diff --git a/src/include/target/Frsky_TX_R9M_LITE.h b/src/include/target/Frsky_TX_R9M_LITE.h
index d61ca313cd..e42c339d89 100644
--- a/src/include/target/Frsky_TX_R9M_LITE.h
+++ b/src/include/target/Frsky_TX_R9M_LITE.h
@@ -31,4 +31,4 @@
// Output Power
#define MinPower PWR_10mW
#define MaxPower PWR_50mW
-#define POWER_OUTPUT_VALUES {8,11,15}
+#define POWER_OUTPUT_VALUES {120,124,127}
diff --git a/src/include/target/NamimnoRC_VOYAGER_900_RX.h b/src/include/target/NamimnoRC_VOYAGER_900_RX.h
index 15d985cc74..55e376c7cd 100644
--- a/src/include/target/NamimnoRC_VOYAGER_900_RX.h
+++ b/src/include/target/NamimnoRC_VOYAGER_900_RX.h
@@ -18,4 +18,4 @@
#define GPIO_PIN_RCSIGNAL_TX PA9
// Output Power - default for SX127x
-#define POWER_OUTPUT_FIXED 15 //MAX power for 900 RXes
+#define POWER_OUTPUT_FIXED 127 //MAX power for 900 RXes
diff --git a/src/include/target/Unified_ESP32_TX.h b/src/include/target/Unified_ESP32_TX.h
index 845b049915..20f5c8501f 100644
--- a/src/include/target/Unified_ESP32_TX.h
+++ b/src/include/target/Unified_ESP32_TX.h
@@ -52,9 +52,10 @@
#define GPIO_PIN_NSS hardware_pin(HARDWARE_radio_nss)
#define GPIO_PIN_NSS_2 hardware_pin(HARDWARE_radio_nss_2)
#define GPIO_PIN_RST hardware_pin(HARDWARE_radio_rst)
+#define GPIO_PIN_RST_2 hardware_pin(HARDWARE_radio_rst_2)
#define GPIO_PIN_SCK hardware_pin(HARDWARE_radio_sck)
-#define USE_SX1280_DCDC
-#define OPT_USE_SX1280_DCDC hardware_flag(HARDWARE_radio_dcdc)
+#define USE_HARDWARE_DCDC
+#define OPT_USE_HARDWARE_DCDC hardware_flag(HARDWARE_radio_dcdc)
#define USE_SX1276_RFO_HF
#define OPT_USE_SX1276_RFO_HF hardware_flag(HARDWARE_radio_rfo_hf)
@@ -87,6 +88,8 @@
#define POWER_OUTPUT_DACWRITE (hardware_int(HARDWARE_power_control)==3)
#define POWER_OUTPUT_FIXED -99
#define POWER_OUTPUT_VALUES hardware_i16_array(HARDWARE_power_values)
+#define POWER_OUTPUT_VALUES2 hardware_i16_array(HARDWARE_power_values2)
+#define POWER_OUTPUT_VALUES_DUAL hardware_i16_array(HARDWARE_power_values_dual)
// Input
#define HAS_FIVE_WAY_BUTTON
@@ -112,7 +115,7 @@
#define GPIO_PIN_LED_GREEN_RED hardware_pin(HARDWARE_led_green_red)
#define GPIO_PIN_LED_RED hardware_pin(HARDWARE_led_red)
#define GPIO_LED_RED_INVERTED hardware_pin(HARDWARE_led_red_invert)
-#define GPIO_PIN_LED_RED_GREEN hardware_pin(HARDWARE_led_reg_green)
+#define GPIO_PIN_LED_RED_GREEN hardware_pin(HARDWARE_led_red_green)
#define GPIO_PIN_LED_WS2812 hardware_pin(HARDWARE_led_rgb)
// #define GPIO_PIN_LED_WS2812_FAST // stm32
@@ -200,6 +203,8 @@ GPIO_PIN_RF_AMP_PWM
GPIO_PIN_RF_AMP_VPD
GPIO_PIN_RF_AMP_VREF
GPIO_PIN_SPI_VTX_NSS
-VPD_VALUES_100MW
VPD_VALUES_25MW
+VPD_VALUES_100MW
+PWM_VALUES_25MW
+PWM_VALUES_100MW
*/
diff --git a/src/include/target/Unified_ESP8285_TX.h b/src/include/target/Unified_ESP8285_TX.h
index 7b1af9610f..aaa692ddd7 100644
--- a/src/include/target/Unified_ESP8285_TX.h
+++ b/src/include/target/Unified_ESP8285_TX.h
@@ -20,9 +20,10 @@
#define GPIO_PIN_NSS hardware_pin(HARDWARE_radio_nss)
#define GPIO_PIN_NSS_2 hardware_pin(HARDWARE_radio_nss_2)
#define GPIO_PIN_RST hardware_pin(HARDWARE_radio_rst)
+#define GPIO_PIN_RST_2 hardware_pin(HARDWARE_radio_rst_2)
#define GPIO_PIN_SCK hardware_pin(HARDWARE_radio_sck)
-#define USE_SX1280_DCDC
-#define OPT_USE_SX1280_DCDC hardware_flag(HARDWARE_radio_dcdc)
+#define USE_HARDWARE_DCDC
+#define OPT_USE_HARDWARE_DCDC hardware_flag(HARDWARE_radio_dcdc)
// Radio power
#define GPIO_PIN_PA_ENABLE hardware_pin(HARDWARE_power_enable)
@@ -39,6 +40,8 @@
//#define POWER_OUTPUT_DACWRITE (hardware_int(HARDWARE_power_control)==3)
#define POWER_OUTPUT_FIXED -99
#define POWER_OUTPUT_VALUES hardware_i16_array(HARDWARE_power_values)
+#define POWER_OUTPUT_VALUES2 hardware_i16_array(HARDWARE_power_values)
+#define POWER_OUTPUT_VALUES_DUAL hardware_i16_array(HARDWARE_power_values_dual)
// Input
#define GPIO_PIN_BUTTON hardware_pin(HARDWARE_button)
@@ -53,7 +56,7 @@
#define GPIO_PIN_LED_GREEN_RED hardware_pin(HARDWARE_led_green_red)
#define GPIO_PIN_LED_RED hardware_pin(HARDWARE_led_red)
#define GPIO_LED_RED_INVERTED hardware_pin(HARDWARE_led_red_invert)
-#define GPIO_PIN_LED_RED_GREEN hardware_pin(HARDWARE_led_reg_green)
+#define GPIO_PIN_LED_RED_GREEN hardware_pin(HARDWARE_led_red_green)
#define GPIO_PIN_LED_WS2812 hardware_pin(HARDWARE_led_rgb)
// #define GPIO_PIN_LED_WS2812_FAST // stm32
#define WS2812_IS_GRB
diff --git a/src/include/target/Unified_ESP_RX.h b/src/include/target/Unified_ESP_RX.h
index 5def0f0df2..4e1d7c449d 100644
--- a/src/include/target/Unified_ESP_RX.h
+++ b/src/include/target/Unified_ESP_RX.h
@@ -51,9 +51,10 @@
#define GPIO_PIN_NSS hardware_pin(HARDWARE_radio_nss)
#define GPIO_PIN_NSS_2 hardware_pin(HARDWARE_radio_nss_2)
#define GPIO_PIN_RST hardware_pin(HARDWARE_radio_rst)
+#define GPIO_PIN_RST_2 hardware_pin(HARDWARE_radio_rst_2)
#define GPIO_PIN_SCK hardware_pin(HARDWARE_radio_sck)
-#define USE_SX1280_DCDC
-#define OPT_USE_SX1280_DCDC hardware_flag(HARDWARE_radio_dcdc)
+#define USE_HARDWARE_DCDC
+#define OPT_USE_HARDWARE_DCDC hardware_flag(HARDWARE_radio_dcdc)
#define USE_SX1276_RFO_HF
#define OPT_USE_SX1276_RFO_HF hardware_flag(HARDWARE_radio_rfo_hf)
@@ -86,6 +87,8 @@
#define POWER_OUTPUT_DACWRITE (hardware_int(HARDWARE_power_control)==3)
#define POWER_OUTPUT_FIXED -99
#define POWER_OUTPUT_VALUES hardware_i16_array(HARDWARE_power_values)
+#define POWER_OUTPUT_VALUES2 hardware_i16_array(HARDWARE_power_values2)
+#define POWER_OUTPUT_VALUES_DUAL hardware_i16_array(HARDWARE_power_values_dual)
// Input
#define GPIO_PIN_BUTTON hardware_pin(HARDWARE_button)
@@ -99,7 +102,7 @@
#define GPIO_PIN_LED_GREEN_RED hardware_pin(HARDWARE_led_green_red)
#define GPIO_PIN_LED_RED hardware_pin(HARDWARE_led_red)
#define GPIO_LED_RED_INVERTED hardware_pin(HARDWARE_led_red_invert)
-#define GPIO_PIN_LED_RED_GREEN hardware_pin(HARDWARE_led_reg_green)
+#define GPIO_PIN_LED_RED_GREEN hardware_pin(HARDWARE_led_red_green)
#define GPIO_PIN_LED_WS2812 hardware_pin(HARDWARE_led_rgb)
// #define GPIO_PIN_LED_WS2812_FAST // stm32
#define WS2812_IS_GRB
@@ -125,6 +128,7 @@
#define ANALOG_VBAT_OFFSET hardware_int(HARDWARE_vbat_offset)
#define ANALOG_VBAT_SCALE hardware_int(HARDWARE_vbat_scale)
+#if defined(PLATFORM_ESP32)
// VTX
#define HAS_VTX_SPI
#define HAS_MSP_VTX
@@ -138,3 +142,6 @@
#define GPIO_PIN_SPI_VTX_SCK hardware_pin(HARDWARE_vtx_sck)
#define VPD_VALUES_25MW hardware_u16_array(HARDWARE_vtx_amp_vpd_25mW)
#define VPD_VALUES_100MW hardware_u16_array(HARDWARE_vtx_amp_vpd_100mW)
+#define PWM_VALUES_25MW hardware_u16_array(HARDWARE_vtx_amp_pwm_25mW)
+#define PWM_VALUES_100MW hardware_u16_array(HARDWARE_vtx_amp_pwm_100mW)
+#endif
\ No newline at end of file
diff --git a/src/include/targets.h b/src/include/targets.h
index 8fe5dbafed..75b958c8a0 100644
--- a/src/include/targets.h
+++ b/src/include/targets.h
@@ -11,6 +11,7 @@
/////////////////////////
#define WORD_ALIGNED_ATTR __attribute__((aligned(4)))
+#define WORD_PADDED(size) (((size)+3) & ~3)
#ifdef PLATFORM_STM32
/* ICACHE_RAM_ATTR1 is always linked into RAM */
@@ -64,6 +65,7 @@
#ifndef HAS_THERMAL
#define OPT_HAS_THERMAL false
+#define OPT_HAS_THERMAL_LM75A false
#elif !defined(OPT_HAS_THERMAL)
#define OPT_HAS_THERMAL true
#endif
@@ -93,6 +95,9 @@
#ifndef GPIO_PIN_RST
#define GPIO_PIN_RST UNDEF_PIN
#endif
+#ifndef GPIO_PIN_RST_2
+#define GPIO_PIN_RST_2 UNDEF_PIN
+#endif
#ifndef GPIO_PIN_BUSY
#define GPIO_PIN_BUSY UNDEF_PIN
#endif
@@ -237,6 +242,11 @@
#endif
#endif
+// Using these DEBUG_* imply that no SerialIO will be used so the output is readable
+#if !defined(DEBUG_CRSF_NO_OUTPUT) && defined(TARGET_RX) && (defined(DEBUG_RCVR_LINKSTATS) || defined(DEBUG_RX_SCOREBOARD) || defined(DEBUG_RCVR_SIGNAL_STATS))
+#define DEBUG_CRSF_NO_OUTPUT
+#endif
+
#if defined(DEBUG_CRSF_NO_OUTPUT)
#define OPT_CRSF_RCVR_NO_SERIAL true
#elif defined(TARGET_UNIFIED_RX)
@@ -263,18 +273,27 @@ extern bool pwmSerialDefined;
#undef Regulatory_Domain_FCC_915
#undef Regulatory_Domain_AU_433
#undef Regulatory_Domain_EU_433
+#undef Regulatory_Domain_US_433
+#undef Regulatory_Domain_US_433_WIDE
-#elif defined(RADIO_SX127X)
+#elif defined(RADIO_SX127X) || defined(RADIO_LR1121)
#if !(defined(Regulatory_Domain_AU_915) || defined(Regulatory_Domain_FCC_915) || \
defined(Regulatory_Domain_EU_868) || defined(Regulatory_Domain_IN_866) || \
defined(Regulatory_Domain_AU_433) || defined(Regulatory_Domain_EU_433) || \
+ defined(Regulatory_Domain_US_433) || defined(Regulatory_Domain_US_433_WIDE) || \
defined(UNIT_TEST))
#error "Regulatory_Domain is not defined for 900MHz device. Check user_defines.txt!"
#endif
#else
-#error "Either RADIO_SX127X or RADIO_SX128X must be defined!"
+#error "Either RADIO_SX127X, RADIO_LR1121 or RADIO_SX128X must be defined!"
#endif
#if defined(TARGET_UNIFIED_TX) || defined(TARGET_UNIFIED_RX)
+#if !defined(U0RXD_GPIO_NUM)
+#define U0RXD_GPIO_NUM (3)
+#endif
+#if !defined(U0TXD_GPIO_NUM)
+#define U0TXD_GPIO_NUM (1)
+#endif
#include "hardware.h"
#endif
diff --git a/src/lib/AnalogVbat/devAnalogVbat.cpp b/src/lib/AnalogVbat/devAnalogVbat.cpp
index 24e9d3c132..e964abcb87 100644
--- a/src/lib/AnalogVbat/devAnalogVbat.cpp
+++ b/src/lib/AnalogVbat/devAnalogVbat.cpp
@@ -43,7 +43,7 @@ static int start()
}
vbatUpdateScale = 1;
#if defined(PLATFORM_ESP32)
- analogSetWidth(12);
+ analogReadResolution(12);
int atten = hardware_int(HARDWARE_vbat_atten);
if (atten != -1)
diff --git a/src/lib/BLE/devBLE.cpp b/src/lib/BLE/devBLE.cpp
index f0fc6d9c1c..9a2d1c6e77 100644
--- a/src/lib/BLE/devBLE.cpp
+++ b/src/lib/BLE/devBLE.cpp
@@ -3,7 +3,8 @@
#if defined(PLATFORM_ESP32)
#include "common.h"
-#include "CRSF.h"
+#include "crsf_protocol.h"
+#include "handset.h"
#include "POWERMGNT.h"
#include "hwTimer.h"
#include "logging.h"
@@ -61,7 +62,7 @@ void BluetoothJoystickBegin()
POWERMGNT::setPower(MinPower);
Radio.End();
- CRSF::RCdataCallback = BluetoothJoystickUpdateValues;
+ handset->setRCDataCallback(BluetoothJoystickUpdateValues);
BleGamepadConfiguration *gamepadConfig = new BleGamepadConfiguration();
gamepadConfig->setAutoReport(false);
diff --git a/src/lib/BUTTON/button.h b/src/lib/BUTTON/button.h
index 6af2c825a9..d34f160164 100644
--- a/src/lib/BUTTON/button.h
+++ b/src/lib/BUTTON/button.h
@@ -1,7 +1,5 @@
#pragma once
-#include
-
class Button
{
private:
@@ -26,8 +24,8 @@ class Button
uint8_t _pressCount; // number of short presses before timeout
public:
// Callbacks
- std::functionOnShortPress;
- std::functionOnLongPress;
+ void (*OnShortPress)();
+ void (*OnLongPress)();
// Properties
uint8_t getCount() const { return _pressCount; }
uint8_t getLongCount() const { return _longCount; }
diff --git a/src/lib/BUTTON/devButton.cpp b/src/lib/BUTTON/devButton.cpp
index c4c8dfdb67..89f143fbd9 100644
--- a/src/lib/BUTTON/devButton.cpp
+++ b/src/lib/BUTTON/devButton.cpp
@@ -4,7 +4,8 @@
#include "logging.h"
#include "button.h"
#include "config.h"
-#include "CRSF.h"
+#include "helpers.h"
+#include "handset.h"
#ifndef GPIO_BUTTON_INVERTED
#define GPIO_BUTTON_INVERTED false
@@ -29,9 +30,11 @@ static constexpr struct {
bool pressType;
uint8_t count;
action_e action;
-} button_actions[2] = {
- {true, 3, ACTION_START_WIFI},
- {true, 7, ACTION_RESET_REBOOT}
+} button_actions[] = {
+ // half second durations + 1 (i.e. 2=1.5s)
+ {true, 2, ACTION_BIND}, // 1.5s
+ {true, 9, ACTION_START_WIFI}, // 5.0s
+ {true, 23, ACTION_RESET_REBOOT} // 12.0s
};
#endif
@@ -42,13 +45,22 @@ void registerButtonFunction(action_e action, ButtonAction_fn function)
actions[action] = function;
}
+size_t button_GetActionCnt()
+{
+#if defined(TARGET_RX)
+ return ARRAY_SIZE(button_actions);
+#else
+ return CONFIG_TX_BUTTON_ACTION_CNT;
+#endif
+}
+
static void handlePress(uint8_t button, bool longPress, uint8_t count)
{
DBGLN("handlePress(%u, %u, %u)", button, (uint8_t)longPress, count);
#if defined(TARGET_TX)
const button_action_t *button_actions = config.GetButtonActions(button)->val.actions;
#endif
- for (int i=0 ; iIsArmed())
return MS_IN_USE;
#else
if (connectionState == connected)
diff --git a/src/lib/BUTTON/devButton.h b/src/lib/BUTTON/devButton.h
index ae11c3b0bf..cf88d96dd0 100644
--- a/src/lib/BUTTON/devButton.h
+++ b/src/lib/BUTTON/devButton.h
@@ -6,11 +6,8 @@
typedef void (*ButtonAction_fn)();
#if defined(GPIO_PIN_BUTTON)
- #if defined(TARGET_TX) || \
- (defined(TARGET_RX) && (defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)))
- extern device_t Button_device;
- #define HAS_BUTTON
- #endif
+ extern device_t Button_device;
+ #define HAS_BUTTON
#include
#include
@@ -23,7 +20,8 @@ typedef void (*ButtonAction_fn)();
} action_t;
void registerButtonFunction(action_e action, ButtonAction_fn function);
-
+ size_t button_GetActionCnt();
#else
inline void registerButtonFunction(uint8_t action, ButtonAction_fn function) {}
+ inline size_t button_GetActionCnt() { return 0; }
#endif
diff --git a/src/lib/Backpack/devBackpack.cpp b/src/lib/Backpack/devBackpack.cpp
index 1f9b60677d..d1c7771b9f 100644
--- a/src/lib/Backpack/devBackpack.cpp
+++ b/src/lib/Backpack/devBackpack.cpp
@@ -1,16 +1,15 @@
#include "targets.h"
+
#include "common.h"
#include "device.h"
#include "msp.h"
#include "msptypes.h"
-#include "CRSF.h"
+#include "CRSFHandset.h"
#include "config.h"
#include "logging.h"
#define BACKPACK_TIMEOUT 20 // How often to check for backpack commands
-extern bool InBindingMode;
-extern Stream *TxBackpack;
extern char backpackVersion[];
extern bool headTrackingEnabled;
@@ -26,35 +25,60 @@ bool lastRecordingState = false;
#define PASSTHROUGH_BAUD BACKPACK_LOGGING_BAUD
#endif
+#define GPIO_PIN_BOOT0 0
+
#include "CRSF.h"
#include "hwTimer.h"
-void startPassthrough()
+[[noreturn]] void startPassthrough()
{
- // stop everyhting
+ // stop everything
devicesStop();
Radio.End();
hwTimer::stop();
- CRSF::End();
+ handset->End();
+
+ Stream *uplink = &CRSFHandset::Port;
uint32_t baud = PASSTHROUGH_BAUD == -1 ? BACKPACK_LOGGING_BAUD : PASSTHROUGH_BAUD;
// get ready for passthrough
if (GPIO_PIN_RCSIGNAL_RX == GPIO_PIN_RCSIGNAL_TX)
{
- // if we have a single S.PORT pin for RX then we assume the standard UART pins for passthrough
- CRSF::Port.begin(baud, SERIAL_8N1, 3, 1);
+ #if defined(PLATFORM_ESP32_S3)
+ // if UART0 is connected to the backpack then use the USB for the uplink
+ if (GPIO_PIN_DEBUG_RX == 44 && GPIO_PIN_DEBUG_TX == 43)
+ {
+ uplink = &Serial;
+ Serial.setTxBufferSize(1024);
+ Serial.setRxBufferSize(16384);
+ }
+ else
+ {
+ CRSFHandset::Port.begin(baud, SERIAL_8N1, 44, 43); // pins are configured as 44 and 43
+ CRSFHandset::Port.setTxBufferSize(1024);
+ CRSFHandset::Port.setRxBufferSize(16384);
+ }
+ #else
+ CRSFHandset::Port.begin(baud, SERIAL_8N1, 3, 1); // default pin configuration 3 and 1
+ CRSFHandset::Port.setTxBufferSize(1024);
+ CRSFHandset::Port.setRxBufferSize(16384);
+ #endif
}
else
{
- CRSF::Port.begin(baud, SERIAL_8N1, GPIO_PIN_RCSIGNAL_RX, GPIO_PIN_RCSIGNAL_TX);
+ CRSFHandset::Port.begin(baud, SERIAL_8N1, GPIO_PIN_RCSIGNAL_RX, GPIO_PIN_RCSIGNAL_TX);
+ CRSFHandset::Port.setTxBufferSize(1024);
+ CRSFHandset::Port.setRxBufferSize(16384);
}
disableLoopWDT();
- HardwareSerial &backpack = *(HardwareSerial*)TxBackpack;
+ const auto backpack = (HardwareSerial*)TxBackpack;
if (baud != BACKPACK_LOGGING_BAUD)
{
- backpack.begin(PASSTHROUGH_BAUD, SERIAL_8N1, GPIO_PIN_DEBUG_RX, GPIO_PIN_DEBUG_TX);
+ backpack->begin(PASSTHROUGH_BAUD, SERIAL_8N1, GPIO_PIN_DEBUG_RX, GPIO_PIN_DEBUG_TX);
}
+ backpack->setRxBufferSize(1024);
+ backpack->setTxBufferSize(16384);
// reset ESP8285 into bootloader mode
digitalWrite(GPIO_PIN_BACKPACK_BOOT, HIGH);
@@ -64,28 +88,61 @@ void startPassthrough()
digitalWrite(GPIO_PIN_BACKPACK_EN, HIGH);
delay(50);
- CRSF::Port.flush();
- backpack.flush();
+ uplink->flush();
+ backpack->flush();
uint8_t buf[64];
- while (backpack.available())
- backpack.readBytes(buf, sizeof(buf));
+ while (backpack->available())
+ {
+ backpack->readBytes(buf, sizeof(buf));
+ }
// go hard!
for (;;)
{
- int r = CRSF::Port.available();
- if (r > sizeof(buf))
- r = sizeof(buf);
- r = CRSF::Port.readBytes(buf, r);
- backpack.write(buf, r);
-
- r = backpack.available();
- if (r > sizeof(buf))
- r = sizeof(buf);
- r = backpack.readBytes(buf, r);
- CRSF::Port.write(buf, r);
+ int available_bytes = uplink->available();
+ if (available_bytes > sizeof(buf))
+ available_bytes = sizeof(buf);
+ auto bytes_read = uplink->readBytes(buf, available_bytes);
+ backpack->write(buf, bytes_read);
+
+ available_bytes = backpack->available();
+ if (available_bytes > sizeof(buf))
+ available_bytes = sizeof(buf);
+ bytes_read = backpack->readBytes(buf, available_bytes);
+ uplink->write(buf, bytes_read);
+ }
+}
+#endif
+
+#if defined(GPIO_PIN_BACKPACK_EN)
+
+static int debouncedRead(int pin) {
+ static const uint8_t min_matches = 100;
+
+ static int last_state = -1;
+ static uint8_t matches = 0;
+
+ int current_state;
+
+ current_state = digitalRead(pin);
+ if (current_state == last_state) {
+ matches = min(min_matches, (uint8_t)(matches + 1));
+ } else {
+ // We are bouncing. Reset the match counter.
+ matches = 0;
+ DBGLN("Bouncing!, current state: %d, last_state: %d, matches: %d", current_state, last_state, matches);
+ }
+
+ if (matches == min_matches) {
+ // We have a stable state and report it.
+ return current_state;
}
+
+ last_state = current_state;
+
+ // We don't have a definitive state we could report.
+ return -1;
}
#endif
@@ -94,11 +151,33 @@ void checkBackpackUpdate()
#if defined(GPIO_PIN_BACKPACK_EN)
if (GPIO_PIN_BACKPACK_EN != UNDEF_PIN)
{
- if (!digitalRead(0))
+ if (debouncedRead(GPIO_PIN_BOOT0) == 0)
{
startPassthrough();
}
}
+#if defined(PLATFORM_ESP32_S3)
+ // Start passthrough mode if an Espressif resync packet is detected on the USB port
+ static const uint8_t resync[] = {
+ 0xc0,0x00,0x08,0x24,0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x12,0x20,0x55,0x55,0x55,0x55,
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55, 0x55,0x55,
+ 0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0xc0
+ };
+ static int resync_pos = 0;
+ while(Serial.available())
+ {
+ int byte = Serial.read();
+ if (byte == resync[resync_pos])
+ {
+ resync_pos++;
+ if (resync_pos == sizeof(resync)) startPassthrough();
+ }
+ else
+ {
+ resync_pos = 0;
+ }
+ }
+#endif
#endif
}
@@ -130,12 +209,8 @@ void BackpackBinding()
packet.reset();
packet.makeCommand();
packet.function = MSP_ELRS_BIND;
- packet.addByte(MasterUID[0]);
- packet.addByte(MasterUID[1]);
- packet.addByte(MasterUID[2]);
- packet.addByte(MasterUID[3]);
- packet.addByte(MasterUID[4]);
- packet.addByte(MasterUID[5]);
+ for (unsigned b=0; b CRSF_MAX_PACKET_LEN)
+ {
+ ERRLN("CRSF frame exceeds max length");
+ return;
+ }
+
+ for (uint8_t i = 0; i < size; ++i)
+ {
+ packet.addByte(data[i]);
+ }
+
+ MSP::sendPacket(&packet, TxBackpack); // send to tx-backpack as MSP
+}
+
static void initialize()
{
#if defined(GPIO_PIN_BACKPACK_EN)
if (GPIO_PIN_BACKPACK_EN != UNDEF_PIN)
{
- pinMode(0, INPUT); // setup so we can detect pinchange for passthrough mode
+ pinMode(GPIO_PIN_BOOT0, INPUT); // setup so we can detect pinchange for passthrough mode
pinMode(GPIO_PIN_BACKPACK_BOOT, OUTPUT);
pinMode(GPIO_PIN_BACKPACK_EN, OUTPUT);
// Shut down the backpack via EN pin and hold it there until the first event()
@@ -206,8 +299,7 @@ static void initialize()
// Rely on event() to boot
}
#endif
-
- CRSF::RCdataCallback = AuxStateToMSPOut;
+ handset->setRCDataCallback(AuxStateToMSPOut);
}
static int start()
diff --git a/src/lib/Backpack/devBackpack.h b/src/lib/Backpack/devBackpack.h
index 41e8836c68..c67609cd5c 100644
--- a/src/lib/Backpack/devBackpack.h
+++ b/src/lib/Backpack/devBackpack.h
@@ -3,6 +3,7 @@
#include "device.h"
void checkBackpackUpdate();
+void crsfTelemToMSPOut(uint8_t *data);
extern bool HTEnableFlagReadyToSend;
extern device_t Backpack_device;
diff --git a/src/lib/Baro/baro_base.cpp b/src/lib/Baro/baro_base.cpp
index 636d587a1c..f1eed97201 100644
--- a/src/lib/Baro/baro_base.cpp
+++ b/src/lib/Baro/baro_base.cpp
@@ -1,7 +1,10 @@
#include
+#include
#include "baro_base.h"
+uint8_t BaroI2CBase::m_address = 0;
+
/**
* @brief: Return altitude in cm from pressure in deci-Pascals
**/
@@ -11,3 +14,21 @@ int32_t BaroBase::pressureToAltitude(uint32_t pressuredPa)
return 4433000 * (1.0 - pow(pressuredPa / seaLeveldPa, 0.1903));
}
+void BaroI2CBase::readRegister(uint8_t reg, uint8_t *data, size_t size)
+{
+ Wire.beginTransmission(m_address);
+ Wire.write(reg);
+ if (Wire.endTransmission() == 0)
+ {
+ Wire.requestFrom(m_address, size);
+ Wire.readBytes(data, size);
+ }
+}
+
+void BaroI2CBase::writeRegister(uint8_t reg, uint8_t *data, size_t size)
+{
+ Wire.beginTransmission(m_address);
+ Wire.write(reg);
+ Wire.write(data, size);
+ Wire.endTransmission();
+}
diff --git a/src/lib/Baro/baro_base.h b/src/lib/Baro/baro_base.h
index d0f8c7a329..f991ef55d0 100644
--- a/src/lib/Baro/baro_base.h
+++ b/src/lib/Baro/baro_base.h
@@ -1,6 +1,7 @@
#pragma once
#include
+#include
class BaroBase
{
@@ -35,3 +36,11 @@ class BaroBase
bool m_initialized;
int32_t m_altitudeHome;
};
+
+class BaroI2CBase : public BaroBase
+{
+protected:
+ static uint8_t m_address;
+ static void readRegister(uint8_t reg, uint8_t *data, size_t size);
+ static void writeRegister(uint8_t reg, uint8_t *data, size_t size);
+};
diff --git a/src/lib/Baro/baro_bmp085.cpp b/src/lib/Baro/baro_bmp085.cpp
new file mode 100644
index 0000000000..a318eefa0d
--- /dev/null
+++ b/src/lib/Baro/baro_bmp085.cpp
@@ -0,0 +1,110 @@
+#if 0 // UNTESTED
+#include "baro_bmp085.h"
+
+/****
+ * Calculations used in this code taken from Adafruit BMP085 calculation
+ * https://github.com/adafruit/Adafruit-BMP085-Library/blob/master/Adafruit_BMP085.cpp
+ *
+ * Code reused under GPL License, see the Adafruit project for full license information
+ * https://github.com/adafruit/Adafruit-BMP085-Library/
+ ****/
+
+void BMP085::initialize()
+{
+ if (m_initialized)
+ return;
+
+ // All the calibration registers are in order of our struct and are 2 bytes long too,
+ // so just read them directly into our calib data
+ readRegister(BMP085_CALIB_COEFFS_START, (uint8_t *)&m_calib, sizeof(m_calib));
+}
+
+uint8_t BMP085::getPressureDuration()
+{
+ switch (OVERSAMPLING_PRESSURE)
+ {
+ case 0: return 5; // 4.5ms
+ case 1: return 8; // 7.5ms
+ case 2: return 14; // 13.5ms
+ default: // fallthrough
+ case 3: return 26; // 25.5ms
+ }
+}
+
+void BMP085::startPressure()
+{
+ uint8_t reg_value = BMP085_CMD_MEAS_PRESSURE | (OVERSAMPLING_PRESSURE << 6);
+ writeRegister(BMP085_REG_CONTROL, ®_value, sizeof(reg_value));
+}
+
+int32_t BMP085::computeB5(int32_t UT) const
+{
+ int32_t X1 = (UT - (int32_t)m_calib.ac6) * ((int32_t)m_calib.ac5) >> 15;
+ int32_t X2 = ((int32_t)m_calib.mc << 11) / (X1 + (int32_t)m_calib.md);
+ return X1 + X2;
+}
+
+uint32_t BMP085::getPressure()
+{
+ uint8_t buf[3];
+ readRegister(BMP085_REG_PRESSURE_DATA, buf, sizeof(buf));
+ uint32_t raw = ((buf[0] << 16) | (buf[1] << 8) | buf[2]) >> OVERSAMPLING_PRESSURE;
+
+ int32_t B3, B5, B6, X1, X2, X3, p;
+ uint32_t B4, B7;
+
+ B5 = computeB5(m_temperatureLast);
+
+ B6 = B5 - 4000;
+ X1 = ((int32_t)m_calib.b2 * ((B6 * B6) >> 12)) >> 11;
+ X2 = ((int32_t)m_calib.ac2 * B6) >> 11;
+ X3 = X1 + X2;
+ B3 = ((((int32_t)m_calib.ac1 * 4 + X3) << OVERSAMPLING_PRESSURE) + 2) / 4;
+
+ X1 = ((int32_t)m_calib.ac3 * B6) >> 13;
+ X2 = ((int32_t)m_calib.b1 * ((B6 * B6) >> 12)) >> 16;
+ X3 = ((X1 + X2) + 2) >> 2;
+ B4 = ((uint32_t)m_calib.ac4 * (uint32_t)(X3 + 32768)) >> 15;
+ B7 = ((uint32_t)raw - B3) * (uint32_t)(50000UL >> OVERSAMPLING_PRESSURE);
+
+ if (B7 < 0x80000000) {
+ p = (B7 * 2) / B4;
+ } else {
+ p = (B7 / B4) * 2;
+ }
+ X1 = (p >> 8) * (p >> 8);
+ X1 = (X1 * 3038) >> 16;
+ X2 = (-7357 * p) >> 16;
+
+ p = p + ((X1 + X2 + (int32_t)3791) >> 4);
+ return p;
+}
+
+uint8_t BMP085::getTemperatureDuration()
+{
+ // Max. conversion time 4.5ms
+ return 5;
+}
+
+void BMP085::startTemperature()
+{
+ uint8_t reg_value = BMP085_CMD_MEAS_TEMPERATURE;
+ writeRegister(BMP085_REG_CONTROL, ®_value, sizeof(reg_value));
+}
+
+int32_t BMP085::getTemperature()
+{
+ m_temperatureLast = 0;
+ readRegister(BMP085_REG_TEMPERATURE_DATA, (uint8_t *)&m_temperatureLast, sizeof(m_temperatureLast));
+ return m_temperatureLast;
+}
+
+bool BMP085::detect()
+{
+ // Assumes Wire.begin() has already been called
+ uint8_t chipid = 0;
+ m_address = BMP085_I2C_ADDR;
+ readRegister(BMP085_REG_CHIPID, &chipid, sizeof(chipid));
+ return chipid == BMP085_CHIPID;
+}
+#endif
\ No newline at end of file
diff --git a/src/lib/Baro/baro_bmp085.h b/src/lib/Baro/baro_bmp085.h
new file mode 100644
index 0000000000..d77ab91df4
--- /dev/null
+++ b/src/lib/Baro/baro_bmp085.h
@@ -0,0 +1,53 @@
+#if 0
+// BRY: I implemented this entire unit thinking I had a BMP085 on hand
+// but I do not so this code is completely untested and never has run
+// but it compiles, so ship it!
+#pragma once
+
+#include "baro_base.h"
+#include "baro_bmp085_regs.h"
+
+class BMP085 : public BaroI2CBase
+{
+public:
+ // Detect if chip is present
+ static bool detect();
+
+ // BaroBase methods
+ void initialize();
+ uint8_t getPressureDuration();
+ void startPressure();
+ uint32_t getPressure();
+ uint8_t getTemperatureDuration();
+ void startTemperature();
+ int32_t getTemperature();
+
+protected:
+ // 3x pressure + temperature = 30.5ms per update
+ // 0x=4.5ms, 1x=7.5ms, 2x=13.5ms, 3x=25.5ms
+ // +3 +9 +21
+ const uint8_t OVERSAMPLING_PRESSURE = 3;
+
+ // calibration data, if initialized
+ // Packed to be able to read all at once
+ struct tagCalibrationData
+ {
+ int16_t ac1;
+ int16_t ac2;
+ int16_t ac3;
+ uint16_t ac4;
+ uint16_t ac5;
+ uint16_t ac6;
+ int16_t b1;
+ int16_t b2;
+ int16_t mb;
+ int16_t mc;
+ int16_t md;
+ } __attribute__((packed)) m_calib;
+
+ uint16_t m_temperatureLast;
+
+private:
+ int32_t computeB5(int32_t UT) const;
+};
+#endif
\ No newline at end of file
diff --git a/src/lib/Baro/baro_bmp085_regs.h b/src/lib/Baro/baro_bmp085_regs.h
new file mode 100644
index 0000000000..22fc12b6a6
--- /dev/null
+++ b/src/lib/Baro/baro_bmp085_regs.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#define BMP085_I2C_ADDR 0x77
+#define BMP085_CHIPID 0x55
+
+// Registers
+#define BMP085_CALIB_COEFFS_START 0xAA
+#define BMP085_CALIB_COEFFS_END 0xBE
+#define BMP085_REG_CHIPID 0xD0
+#define BMP085_REG_CONTROL 0xF4
+#define BMP085_REG_TEMPERATURE_DATA 0xF6 // uses same scratchpad as press
+#define BMP085_REG_PRESSURE_DATA 0xF6 // uses same scratchpad as temp
+#define BMP085_CMD_MEAS_TEMPERATURE 0x2E
+#define BMP085_CMD_MEAS_PRESSURE 0x34
+
+#define BMP085_CALIB_COEFFS_LEN (BMP085_CALIB_COEFFS_END - BMP085_CALIB_COEFFS_START + 1)
diff --git a/src/lib/Baro/baro_bmp280.cpp b/src/lib/Baro/baro_bmp280.cpp
new file mode 100644
index 0000000000..6bf9bd0dd1
--- /dev/null
+++ b/src/lib/Baro/baro_bmp280.cpp
@@ -0,0 +1,106 @@
+#include "baro_bmp280.h"
+#include
+#include "logging.h"
+
+void BMP280::initialize()
+{
+ if (m_initialized)
+ return;
+
+ // All the calibration registers are in order of our struct and are 2 bytes long too,
+ // so just read them directly into our calib data
+ readRegister(BMP280_REG_CALIB_COEFFS_START, (uint8_t *)&m_calib, sizeof(m_calib));
+
+ // configure IIR filter, 0.5ms standby time (freerunning)
+ constexpr uint8_t BMP280_FILTER = FILTER_COEFF << 2;
+ writeRegister(BMP280_REG_CONFIG, (uint8_t *)&BMP280_FILTER, sizeof(BMP280_FILTER));
+
+ m_initialized = true;
+}
+
+uint8_t BMP280::getTemperatureDuration()
+{
+ // Calculate in microseconds, then divide by 1000 for ms, rounding up
+ constexpr uint32_t duration = 1250U +
+ (2300U * ((1U << OVERSAMPLING_TEMPERATURE) >> 1)) +
+ (2300U * ((1U << OVERSAMPLING_PRESSURE) >> 1) + 575U);
+ return (FREERUNNING_NUM_SAMPLES * duration + 999U) / 1000U;
+}
+
+void BMP280::startTemperature()
+{
+ // Measure both Pressure and Temperature, freerunning to allow internal IIR to smooth extra samples for us
+ constexpr uint8_t SAMPLING_MODE = (FREERUNNING_NUM_SAMPLES != 0) ? BMP280_MODE_NORMAL : BMP280_MODE_FORCED;
+ constexpr uint8_t BMP280_MODE = (OVERSAMPLING_PRESSURE << 2 | OVERSAMPLING_TEMPERATURE << 5 | SAMPLING_MODE);
+ writeRegister(BMP280_REG_CTRL_MEAS, (uint8_t *)&BMP280_MODE, sizeof(BMP280_MODE));
+}
+
+uint32_t BMP280::getPressure()
+{
+ return m_pressureLast;
+}
+
+int32_t BMP280::getTemperature()
+{
+ // uint8_t status = 0xff;
+ // readRegister(BMP280_REG_STATUS, &status, sizeof(status));
+ // if (status & bit(3))
+ // {
+ // DBGLN("not ready");
+ // }
+
+ uint8_t buf[BMP280_LEN_TEMP_PRESS_DATA] = {0};
+ readRegister(BMP280_REG_PRESSURE_MSB, buf, sizeof(buf));
+
+ int32_t adc_P = ((uint32_t)buf[0] << 12) | ((uint32_t)buf[1] << 4) | ((uint32_t)buf[2] >> 4);
+ int32_t adc_T = ((uint32_t)buf[3] << 12) | ((uint32_t)buf[4] << 4) | ((uint32_t)buf[5] >> 4);
+
+ // BME280_compensate_T_int32()
+ int32_t var01, var02;
+ var01 = ((((adc_T >> 3) - ((int32_t)m_calib.dig_T1 << 1))) * ((int32_t)m_calib.dig_T2)) >> 11;
+ var02 = (((((adc_T >> 4) - ((int32_t)m_calib.dig_T1)) * ((adc_T >> 4) - ((int32_t)m_calib.dig_T1))) >> 12) * ((int32_t)m_calib.dig_T3)) >> 14;
+ int32_t t_fine = var01 + var02;
+
+ // BME280_compensate_P_int64()
+ int64_t var1, var2, p;
+ var1 = ((int64_t)t_fine) - 128000;
+ var2 = var1 * var1 * (int64_t)m_calib.dig_P6;
+ var2 = var2 + ((var1*(int64_t)m_calib.dig_P5) << 17);
+ var2 = var2 + (((int64_t)m_calib.dig_P4) << 35);
+ var1 = ((var1 * var1 * (int64_t)m_calib.dig_P3) >> 8) + ((var1 * (int64_t)m_calib.dig_P2) << 12);
+ var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)m_calib.dig_P1) >> 33;
+ if (var1 == 0)
+ return BaroBase::PRESSURE_INVALID;
+
+ p = 1048576 - adc_P;
+ p = (((p << 31) - var2) * 3125) / var1;
+ var1 = (((int64_t)m_calib.dig_P9) * (p >> 13) * (p >> 13)) >> 25;
+ var2 = (((int64_t)m_calib.dig_P8) * p) >> 19;
+ p = ((p + var1 + var2) >> 8) + (((int64_t)m_calib.dig_P7) << 4);
+ // p is pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits).
+ // Output value of "24674867" represents 24674867/256 = 96386.2 Pa = 963.862 hPa
+ m_pressureLast = ((uint32_t)p) >> 8;
+
+ int32_t temperature = (t_fine * 5 + 128) >> 8;
+ //DBGLN("%u t=%d p=%u", millis(), temperature, m_pressureLast);
+
+ return temperature;
+}
+
+bool BMP280::detect()
+{
+ // Assumes Wire.begin() has already been called
+ uint8_t chipid = 0;
+
+ // BMP280 can have two addresses based on the SDO pin.
+ // Adafruit breakout has it tied high, Aliexpress boards have it tied low
+ for (uint8_t addr : { BMP280_I2C_ADDR, BMP280_I2C_ADDR_ALT })
+ {
+ m_address = addr;
+ readRegister(BMP280_REG_CHIPID, &chipid, sizeof(chipid));
+ if (chipid == BMP280_CHIPID || chipid == BME280_CHIPID)
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/lib/Baro/baro_bmp280.h b/src/lib/Baro/baro_bmp280.h
new file mode 100644
index 0000000000..91e18124d2
--- /dev/null
+++ b/src/lib/Baro/baro_bmp280.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "baro_base.h"
+#include "baro_bmp280_regs.h"
+
+class BMP280 : public BaroI2CBase
+{
+public:
+ BMP280() : BaroI2CBase(), m_calib{0} {}
+
+ // Detect if chip is present
+ static bool detect();
+
+ // BaroBase methods
+ void initialize();
+ uint8_t getTemperatureDuration();
+ void startTemperature();
+ int32_t getTemperature();
+ // Temperature and Pressure are measured at the same time
+ uint8_t getPressureDuration() { return 0; }
+ void startPressure() {}
+ uint32_t getPressure();
+
+protected:
+ // Filter 4: 1x=1.2PaRMS 2x=1.0PaRMS 4x=0.8PaRMS 8x=0.6PaRMS 16x=0.5PaRMS
+ // Filter 8: 1x=0.9PaRMS 2x=0.6PaRMS 4x=0.5PaRMS 8x=0.4PaRMS 16x=0.4PaRMS
+ // 16x pressure 1x Temp = 40.925ms
+ // 8x pressure 1x Temp = 22.525ms
+ static const uint8_t FILTER_COEFF = BMP280_FILTER_COEFF_8; // 8 in iNav
+ static const uint8_t OVERSAMPLING_PRESSURE = BMP280_OVERSAMP_8X; // 8x in iNav
+ static const uint8_t OVERSAMPLING_TEMPERATURE = BMP280_OVERSAMP_1X; // 1x in iNav
+ static const uint8_t FREERUNNING_NUM_SAMPLES = 4; // Report after this number of samples complete
+
+ // calibration data, if initialized
+ // Packed to be able to read all at once
+ struct tagCalibrationData
+ {
+ uint16_t dig_T1;
+ int16_t dig_T2;
+ int16_t dig_T3;
+ uint16_t dig_P1;
+ int16_t dig_P2;
+ int16_t dig_P3;
+ int16_t dig_P4;
+ int16_t dig_P5;
+ int16_t dig_P6;
+ int16_t dig_P7;
+ int16_t dig_P8;
+ int16_t dig_P9;
+ } __attribute__((packed)) m_calib;
+
+ uint32_t m_pressureLast;
+};
diff --git a/src/lib/Baro/baro_bmp280_regs.h b/src/lib/Baro/baro_bmp280_regs.h
new file mode 100644
index 0000000000..83c6931bed
--- /dev/null
+++ b/src/lib/Baro/baro_bmp280_regs.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#define BMP280_I2C_ADDR 0x76
+#define BMP280_I2C_ADDR_ALT 0x77
+#define BMP280_CHIPID 0x58 // Pressure,Temp
+#define BME280_CHIPID 0x60 // Pressure,Temp,Humid
+
+// Registers
+#define BMP280_REG_CALIB_COEFFS_START 0x88
+#define BMP280_REG_CHIPID 0xD0
+#define BMP280_REG_STATUS 0xF3
+#define BMP280_REG_CTRL_MEAS 0xF4
+#define BMP280_REG_CONFIG 0xF5
+#define BMP280_REG_PRESSURE_MSB 0xF7
+#define BMP280_REG_PRESSURE_LSB 0xF8
+#define BMP280_REG_PRESSURE_XLSB 0xF9
+#define BMP280_REG_TEMPERATURE_MSB 0xFA
+#define BMP280_REG_TEMPERATURE_LSB 0xFB
+#define BMP280_REG_TEMPERATURE_XLSB 0xFC
+
+
+#define BMP280_LEN_TEMP_PRESS_DATA 6
+#define BMP280_MODE_SLEEP 0x00
+#define BMP280_MODE_FORCED 0x01
+#define BMP280_MODE_NORMAL 0x03 // Freerunning mode
+
+#define BMP280_OVERSAMP_SKIPPED 0x00
+#define BMP280_OVERSAMP_1X 0x01
+#define BMP280_OVERSAMP_2X 0x02
+#define BMP280_OVERSAMP_4X 0x03
+#define BMP280_OVERSAMP_8X 0x04
+#define BMP280_OVERSAMP_16X 0x05
+
+#define BMP280_FILTER_COEFF_OFF 0x00
+#define BMP280_FILTER_COEFF_2 0x01
+#define BMP280_FILTER_COEFF_4 0x02
+#define BMP280_FILTER_COEFF_8 0x03
+#define BMP280_FILTER_COEFF_16 0x04
\ No newline at end of file
diff --git a/src/lib/Baro/baro_spl06.cpp b/src/lib/Baro/baro_spl06.cpp
index 5175933e60..7a98602270 100644
--- a/src/lib/Baro/baro_spl06.cpp
+++ b/src/lib/Baro/baro_spl06.cpp
@@ -9,26 +9,6 @@
****/
#include "baro_spl06.h"
-#include "baro_spl06_regs.h"
-
-void SPL06::writeRegister(uint8_t reg, uint8_t *data, size_t size)
-{
- Wire.beginTransmission((uint16_t)SPL06_I2C_ADDR);
- Wire.write(reg);
- Wire.write(data, size);
- Wire.endTransmission();
-}
-
-void SPL06::readRegister(uint8_t reg, uint8_t *data, size_t size)
-{
- Wire.beginTransmission((uint16_t)SPL06_I2C_ADDR);
- Wire.write(reg);
- if (Wire.endTransmission() == 0)
- {
- Wire.requestFrom((uint16_t)SPL06_I2C_ADDR, size);
- Wire.readBytes(data, size);
- }
-}
uint8_t SPL06::oversampleToRegVal(const uint8_t oversamples) const
{
@@ -172,8 +152,16 @@ bool SPL06::detect()
{
// Assumes Wire.begin() has already been called
uint8_t chipid = 0;
+
+ // SPL06 can have two addresses based on the SDO pin.
+ // check primary address
+ m_address = SPL06_I2C_ADDR;
+ readRegister(SPL06_CHIP_ID_REG, &chipid, sizeof(chipid));
+ if (chipid == SPL06_DEFAULT_CHIP_ID)
+ return true;
+
+ // check alternate address
+ m_address = SPL06_I2C_ADDR_ALT;
readRegister(SPL06_CHIP_ID_REG, &chipid, sizeof(chipid));
return chipid == SPL06_DEFAULT_CHIP_ID;
}
-
-
diff --git a/src/lib/Baro/baro_spl06.h b/src/lib/Baro/baro_spl06.h
index 8805955a08..e30fc93ab5 100644
--- a/src/lib/Baro/baro_spl06.h
+++ b/src/lib/Baro/baro_spl06.h
@@ -1,15 +1,17 @@
#pragma once
#include "baro_base.h"
+#include "baro_spl06_regs.h"
-class SPL06 : public BaroBase
+class SPL06 : public BaroI2CBase
{
public:
- SPL06() : BaroBase(), m_calib{0} {}
+ SPL06() : BaroI2CBase(), m_calib{0} {}
// Detect if chip is present
static bool detect();
+ // BaroBase methods
void initialize();
uint8_t getPressureDuration();
void startPressure();
@@ -23,9 +25,6 @@ class SPL06 : public BaroBase
const uint8_t OVERSAMPLING_PRESSURE = 32;
const uint8_t OVERSAMPLING_TEMPERATURE = 8;
- static void readRegister(uint8_t reg, uint8_t *data, size_t size);
- static void writeRegister(uint8_t reg, uint8_t *data, size_t size);
-
uint8_t oversampleToRegVal(const uint8_t oversamples) const;
int32_t oversampleToScaleFactor(const uint8_t oversamples) const;
float m_temperatureLast; // last uncompensated temperature value
diff --git a/src/lib/Baro/baro_spl06_regs.h b/src/lib/Baro/baro_spl06_regs.h
index 003a115638..5be2e99964 100644
--- a/src/lib/Baro/baro_spl06_regs.h
+++ b/src/lib/Baro/baro_spl06_regs.h
@@ -1,6 +1,7 @@
#pragma once
#define SPL06_I2C_ADDR 0x76
+#define SPL06_I2C_ADDR_ALT 0x77
#define SPL06_DEFAULT_CHIP_ID 0x10
#define SPL06_PRESSURE_START_REG 0x00
diff --git a/src/lib/Baro/devBaro.cpp b/src/lib/Baro/devBaro.cpp
index 1edfb57f7c..6c1b2bc08c 100644
--- a/src/lib/Baro/devBaro.cpp
+++ b/src/lib/Baro/devBaro.cpp
@@ -2,9 +2,12 @@
#if defined(HAS_BARO)
+#include "CRSF.h"
#include "logging.h"
#include "telemetry.h"
#include "baro_spl06.h"
+#include "baro_bmp280.h"
+//#include "baro_bmp085.h"
#define BARO_STARTUP_INTERVAL 100
@@ -12,14 +15,16 @@
extern Telemetry telemetry;
/* Local statics */
-static SPL06 *baro;
+static BaroBase *baro;
static eBaroReadState BaroReadState;
+extern bool i2c_enabled;
+
static bool Baro_Detect()
{
// I2C Baros
#if defined(USE_I2C)
- if (GPIO_PIN_SCL != UNDEF_PIN && GPIO_PIN_SDA != UNDEF_PIN)
+ if (i2c_enabled)
{
if (SPL06::detect())
{
@@ -27,10 +32,22 @@ static bool Baro_Detect()
baro = new SPL06();
return true;
}
+ if (BMP280::detect())
+ {
+ DBGLN("Detected baro: BMP280");
+ baro = new BMP280();
+ return true;
+ }
+ // Untested
+ // if (BMP085::detect())
+ // {
+ // DBGLN("Detected baro: BMP085");
+ // baro = new BMP085();
+ // return true;
+ // }
+ // DBGLN("No baro detected");
} // I2C
#endif
-
- //DBGLN("No baro detected");
return false;
}
@@ -93,8 +110,12 @@ static void Baro_PublishPressure(uint32_t pressuredPa)
crsfBaro.p.verticalspd = htobe16(verticalspd_smoothed);
//DBGLN("diff=%d smooth=%d dT=%u", altitude_diff_cm, verticalspd_smoothed, dT_ms);
- CRSF::SetHeaderAndCrc((uint8_t *)&crsfBaro, CRSF_FRAMETYPE_BARO_ALTITUDE, CRSF_FRAME_SIZE(sizeof(crsf_sensor_baro_vario_t)), CRSF_ADDRESS_CRSF_TRANSMITTER);
- telemetry.AppendTelemetryPackage((uint8_t *)&crsfBaro);
+ // if no external vario is connected output internal Vspd on CRSF_FRAMETYPE_BARO_ALTITUDE packet
+ if (!telemetry.GetCrsfBaroSensorDetected())
+ {
+ CRSF::SetHeaderAndCrc((uint8_t *)&crsfBaro, CRSF_FRAMETYPE_BARO_ALTITUDE, CRSF_FRAME_SIZE(sizeof(crsf_sensor_baro_vario_t)), CRSF_ADDRESS_CRSF_TRANSMITTER);
+ telemetry.AppendTelemetryPackage((uint8_t *)&crsfBaro);
+ }
}
static int start()
@@ -128,10 +149,20 @@ static int timeout()
int32_t temp = baro->getTemperature();
if (temp == BaroBase::TEMPERATURE_INVALID)
return DURATION_IMMEDIATELY;
- baro->startPressure();
+ }
+ // fallthrough
+
+ case brsReadPres:
+ {
+ uint8_t pressDuration = baro->getPressureDuration();
BaroReadState = brsWaitingPress;
- return baro->getPressureDuration();
+ if (pressDuration != 0)
+ {
+ baro->startPressure();
+ return pressDuration;
+ }
}
+ // fallthrough
case brsWaitingPress:
{
@@ -143,9 +174,17 @@ static int timeout()
// fallthrough
case brsReadTemp:
- BaroReadState = brsWaitingTemp;
- baro->startTemperature();
- return baro->getTemperatureDuration();
+ {
+ uint8_t tempDuration = baro->getTemperatureDuration();
+ if (tempDuration == 0)
+ {
+ BaroReadState = brsReadPres;
+ return DURATION_IMMEDIATELY;
+ }
+ BaroReadState = brsWaitingTemp;
+ baro->startTemperature();
+ return tempDuration;
+ }
}
}
diff --git a/src/lib/Baro/devBaro.h b/src/lib/Baro/devBaro.h
index 58e43c6d38..7b50eaec97 100644
--- a/src/lib/Baro/devBaro.h
+++ b/src/lib/Baro/devBaro.h
@@ -2,12 +2,11 @@
#include "common.h"
#include "device.h"
-#include "devCRSF.h"
#if defined(USE_ANALOG_VBAT)
#include "devAnalogVbat.h"
#endif
-#if defined(TARGET_UNIFIED_RX) && defined(PLATFORM_ESP32)
+#if defined(TARGET_UNIFIED_RX)
#define HAS_BARO
#endif
@@ -19,6 +18,7 @@ enum eBaroReadState : uint8_t
brsUninitialized,
brsReadTemp,
brsWaitingTemp,
+ brsReadPres,
brsWaitingPress
};
diff --git a/src/lib/CONFIG/config.cpp b/src/lib/CONFIG/config.cpp
index b5694f6ac6..fd12505f77 100644
--- a/src/lib/CONFIG/config.cpp
+++ b/src/lib/CONFIG/config.cpp
@@ -47,7 +47,7 @@ template static const uint32_t Model_to_U32(T const * const model)
static uint8_t RateV6toV7(uint8_t rateV6)
{
-#if defined(RADIO_SX127X)
+#if defined(RADIO_SX127X) || defined(RADIO_LR1121)
if (rateV6 == 0)
{
// 200Hz stays same
@@ -179,9 +179,11 @@ void TxConfig::Load()
// backpackdisable was actually added after 7, but if not found will default to 0 (enabled)
if (nvs_get_u8(handle, "backpackdisable", &value8) == ESP_OK)
m_config.backpackDisable = value8;
+ if (nvs_get_u8(handle, "backpacktlmen", &value8) == ESP_OK)
+ m_config.backpackTlmEnabled = value8;
}
- for(unsigned i=0; i<64; i++)
+ for(unsigned i=0; i RX_CONFIG_VERSION)
{
SetDefaults(true);
+ CheckUpdateFlashedUid(true);
return;
}
@@ -706,11 +723,32 @@ void RxConfig::Load()
UpgradeEepromV4();
UpgradeEepromV5();
UpgradeEepromV6();
+ UpgradeEepromV7V8();
m_config.version = RX_CONFIG_VERSION | RX_CONFIG_MAGIC;
m_modified = true;
Commit();
}
+void RxConfig::CheckUpdateFlashedUid(bool skipDescrimCheck)
+{
+ // No binding phrase flashed, nothing to do
+ if (!firmwareOptions.hasUID)
+ return;
+ // If already copied binding info, do not replace
+ if (!skipDescrimCheck && m_config.flash_discriminator == firmwareOptions.flash_discriminator)
+ return;
+
+ // Save the new UID along with this discriminator to prevent resetting every boot
+ SetUID(firmwareOptions.uid);
+ m_config.flash_discriminator = firmwareOptions.flash_discriminator;
+ // Reset the power on counter because this is following a flash, may have taken a few boots to flash
+ m_config.powerOnCounter = 0;
+ // SetUID should set this but just in case that gets removed, flash_discriminator needs to be saved
+ m_modified = true;
+
+ Commit();
+}
+
// ========================================================
// V4 Upgrade
@@ -728,15 +766,15 @@ void RxConfig::UpgradeEepromV4()
if ((v4Config.version & ~CONFIG_MAGIC_MASK) == 4)
{
- m_config.isBound = v4Config.isBound;
+ UpgradeUid(nullptr, v4Config.isBound ? v4Config.uid : nullptr);
m_config.modelId = v4Config.modelId;
- memcpy(m_config.uid, v4Config.uid, sizeof(v4Config.uid));
-
+ #if defined(GPIO_PIN_PWM_OUTPUTS)
// OG PWMP had only 8 channels
for (unsigned ch=0; ch<8; ++ch)
{
PwmConfigV4(&v4Config.pwmChannels[ch], &m_config.pwmChannels[ch]);
}
+ #endif
}
}
@@ -760,26 +798,29 @@ void RxConfig::UpgradeEepromV5()
{
v5_rx_config_t v5Config;
m_eeprom->Get(0, v5Config);
+
if ((v5Config.version & ~CONFIG_MAGIC_MASK) == 5)
{
- memcpy(m_config.uid, v5Config.uid, sizeof(v5Config.uid));
- m_config.vbatScale = v5Config.vbatScale;
- m_config.isBound = v5Config.isBound;
+ UpgradeUid(v5Config.onLoan ? v5Config.loanUID : nullptr, v5Config.isBound ? v5Config.uid : nullptr);
+ m_config.vbat.scale = v5Config.vbatScale;
m_config.power = v5Config.power;
m_config.antennaMode = v5Config.antennaMode;
m_config.forceTlmOff = v5Config.forceTlmOff;
m_config.rateInitialIdx = v5Config.rateInitialIdx;
m_config.modelId = v5Config.modelId;
+ #if defined(GPIO_PIN_PWM_OUTPUTS)
for (unsigned ch=0; ch<16; ++ch)
{
PwmConfigV5(&v5Config.pwmChannels[ch], &m_config.pwmChannels[ch]);
}
+ #endif
}
}
// ========================================================
// V6 Upgrade
+
static void PwmConfigV6(v6_rx_config_pwm_t const * const v6, rx_config_pwm_t * const current)
{
current->val.failsafe = v6->val.failsafe;
@@ -793,79 +834,113 @@ void RxConfig::UpgradeEepromV6()
{
v6_rx_config_t v6Config;
m_eeprom->Get(0, v6Config);
+
if ((v6Config.version & ~CONFIG_MAGIC_MASK) == 6)
{
- memcpy(m_config.uid, v6Config.uid, sizeof(v6Config.uid));
- m_config.vbatScale = v6Config.vbatScale;
- m_config.isBound = v6Config.isBound;
+ UpgradeUid(v6Config.onLoan ? v6Config.loanUID : nullptr, v6Config.isBound ? v6Config.uid : nullptr);
+ m_config.vbat.scale = v6Config.vbatScale;
m_config.power = v6Config.power;
m_config.antennaMode = v6Config.antennaMode;
m_config.forceTlmOff = v6Config.forceTlmOff;
m_config.rateInitialIdx = v6Config.rateInitialIdx;
m_config.modelId = v6Config.modelId;
+ #if defined(GPIO_PIN_PWM_OUTPUTS)
for (unsigned ch=0; ch<16; ++ch)
{
PwmConfigV6(&v6Config.pwmChannels[ch], &m_config.pwmChannels[ch]);
}
+ #endif
}
}
// ========================================================
+// V7/V8 Upgrade
-void
-RxConfig::Commit()
+void RxConfig::UpgradeEepromV7V8()
{
- if (!m_modified)
+ v7_rx_config_t v7Config;
+ m_eeprom->Get(0, v7Config);
+
+ bool isV8 = (v7Config.version & ~CONFIG_MAGIC_MASK) == 8;
+ if (isV8 || (v7Config.version & ~CONFIG_MAGIC_MASK) == 7)
{
- // No changes
- return;
- }
+ UpgradeUid(v7Config.onLoan ? v7Config.loanUID : nullptr, v7Config.isBound ? v7Config.uid : nullptr);
- // Write the struct to eeprom
- m_eeprom->Put(0, m_config);
- m_eeprom->Commit();
+ m_config.vbat.scale = v7Config.vbatScale;
+ m_config.power = v7Config.power;
+ m_config.antennaMode = v7Config.antennaMode;
+ m_config.forceTlmOff = v7Config.forceTlmOff;
+ m_config.rateInitialIdx = v7Config.rateInitialIdx;
+ m_config.modelId = v7Config.modelId;
+ m_config.serialProtocol = v7Config.serialProtocol;
+ m_config.failsafeMode = v7Config.failsafeMode;
- m_modified = false;
+#if defined(GPIO_PIN_PWM_OUTPUTS)
+ for (unsigned ch=0; ch<16; ++ch)
+ {
+ m_config.pwmChannels[ch].raw = v7Config.pwmChannels[ch].raw;
+ if (!isV8 && m_config.pwmChannels[ch].val.mode > somOnOff)
+ m_config.pwmChannels[ch].val.mode += 1;
+ }
+#endif
+ }
}
-// Setters
-void
-RxConfig::SetIsBound(bool isBound)
+void RxConfig::UpgradeUid(uint8_t *onLoanUid, uint8_t *boundUid)
{
- if (m_config.isBound != isBound)
+ // Convert to traditional binding
+ // On loan? Now you own
+ if (onLoanUid)
{
- m_config.isBound = isBound;
- m_modified = true;
+ memcpy(m_config.uid, onLoanUid, UID_LEN);
+ }
+ // Compiled in UID? Bind to that
+ else if (firmwareOptions.hasUID)
+ {
+ memcpy(m_config.uid, firmwareOptions.uid, UID_LEN);
+ m_config.flash_discriminator = firmwareOptions.flash_discriminator;
+ }
+ else if (boundUid)
+ {
+ // keep binding
+ memcpy(m_config.uid, boundUid, UID_LEN);
+ }
+ else
+ {
+ // No bind
+ memset(m_config.uid, 0, UID_LEN);
}
}
-void
-RxConfig::SetUID(uint8_t* uid)
+bool RxConfig::GetIsBound() const
{
- for (uint8_t i = 0; i < UID_LEN; ++i)
- {
- m_config.uid[i] = uid[i];
- }
- m_modified = true;
+ return !m_config.volatileBind && UID_IS_BOUND(m_config.uid);
}
void
-RxConfig::SetOnLoan(bool isLoaned)
+RxConfig::Commit()
{
- if (m_config.onLoan != isLoaned)
+ if (!m_modified)
{
- m_config.onLoan = isLoaned;
- m_modified = true;
+ // No changes
+ return;
}
+
+ // Write the struct to eeprom
+ m_eeprom->Put(0, m_config);
+ m_eeprom->Commit();
+
+ m_modified = false;
}
+// Setters
void
-RxConfig::SetOnLoanUID(uint8_t* uid)
+RxConfig::SetUID(uint8_t* uid)
{
for (uint8_t i = 0; i < UID_LEN; ++i)
{
- m_config.loanUID[i] = uid[i];
+ m_config.uid[i] = uid[i];
}
m_modified = true;
}
@@ -928,11 +1003,28 @@ RxConfig::SetDefaults(bool commit)
m_config.antennaMode = 0; // 0 is diversity for dual radio
#if defined(GPIO_PIN_PWM_OUTPUTS)
- for (unsigned int ch=0; chptrStartChannel; }
@@ -146,6 +151,7 @@ class TxConfig
void SetDvrStopDelay(uint8_t dvrStopDelay);
void SetButtonActions(uint8_t button, tx_button_color_t actions[2]);
void SetBackpackDisable(bool backpackDisable);
+ void SetBackpackTlmEnabled(bool enabled);
void SetPTRStartChannel(uint8_t ptrStartChannel);
void SetPTREnableChannel(uint8_t ptrEnableChannel);
@@ -184,18 +190,23 @@ typedef union {
inverted:1, // invert channel output
mode:4, // Output mode (eServoOutputMode)
narrow:1, // Narrow output mode (half pulse width)
- unused:12; // FUTURE: When someone complains "everyone" uses inverted polarity PWM or something :/
+ failsafeMode:2, // failsafe output mode (eServoOutputFailsafeMode)
+ unused:10; // FUTURE: When someone complains "everyone" uses inverted polarity PWM or something :/
} val;
uint32_t raw;
} rx_config_pwm_t;
-typedef struct {
+typedef struct __attribute__((packed)) {
uint32_t version;
uint8_t uid[UID_LEN];
- uint8_t loanUID[UID_LEN];
- uint16_t vbatScale; // FUTURE: Override compiled vbat scale
- uint8_t isBound:1,
- onLoan:1,
+ uint8_t unused_padding[2];
+ uint32_t flash_discriminator;
+ struct __attribute__((packed)) {
+ uint16_t scale; // FUTURE: Override compiled vbat scale
+ int16_t offset; // FUTURE: Override comiled vbat offset
+ } vbat;
+ uint8_t volatileBind:1, // 0=Persistent 1=Volatile
+ unused_onLoan:1,
power:4,
antennaMode:2; // 0=0, 1=1, 2=Diversity
uint8_t powerOnCounter:3,
@@ -205,7 +216,10 @@ typedef struct {
uint8_t serialProtocol:4,
failsafeMode:2,
unused:2;
- rx_config_pwm_t pwmChannels[PWM_MAX_CHANNELS];
+ rx_config_pwm_t pwmChannels[PWM_MAX_CHANNELS] __attribute__((aligned(4)));
+ uint8_t teamraceChannel:4,
+ teamracePosition:3,
+ teamracePitMode:1; // FUTURE: Enable pit mode when disabling model
} rx_config_t;
class RxConfig
@@ -217,10 +231,8 @@ class RxConfig
void Commit();
// Getters
- bool GetIsBound() const { return firmwareOptions.hasUID || m_config.isBound; }
+ bool GetIsBound() const;
const uint8_t* GetUID() const { return m_config.uid; }
- bool GetOnLoan() const { return m_config.onLoan; }
- const uint8_t* GetOnLoanUID() const { return m_config.loanUID; }
uint8_t GetPowerOnCounter() const { return m_config.powerOnCounter; }
uint8_t GetModelId() const { return m_config.modelId; }
uint8_t GetPower() const { return m_config.power; }
@@ -232,13 +244,13 @@ class RxConfig
bool GetForceTlmOff() const { return m_config.forceTlmOff; }
uint8_t GetRateInitialIdx() const { return m_config.rateInitialIdx; }
eSerialProtocol GetSerialProtocol() const { return (eSerialProtocol)m_config.serialProtocol; }
+ uint8_t GetTeamraceChannel() const { return m_config.teamraceChannel; }
+ uint8_t GetTeamracePosition() const { return m_config.teamracePosition; }
eFailsafeMode GetFailsafeMode() const { return (eFailsafeMode)m_config.failsafeMode; }
+ bool GetVolatileBind() const { return m_config.volatileBind; }
// Setters
- void SetIsBound(bool isBound);
void SetUID(uint8_t* uid);
- void SetOnLoan(bool loaned);
- void SetOnLoanUID(uint8_t* uid);
void SetPowerOnCounter(uint8_t powerOnCounter);
void SetModelId(uint8_t modelId);
void SetPower(uint8_t power);
@@ -252,12 +264,18 @@ class RxConfig
void SetForceTlmOff(bool forceTlmOff);
void SetRateInitialIdx(uint8_t rateInitialIdx);
void SetSerialProtocol(eSerialProtocol serialProtocol);
+ void SetTeamraceChannel(uint8_t teamraceChannel);
+ void SetTeamracePosition(uint8_t teamracePosition);
void SetFailsafeMode(eFailsafeMode failsafeMode);
+ void SetVolatileBind(bool value);
private:
+ void CheckUpdateFlashedUid(bool skipDescrimCheck);
+ void UpgradeUid(uint8_t *onLoanUid, uint8_t *boundUid);
void UpgradeEepromV4();
void UpgradeEepromV5();
void UpgradeEepromV6();
+ void UpgradeEepromV7V8();
rx_config_t m_config;
ELRS_EEPROM *m_eeprom;
diff --git a/src/lib/CONFIG/config_legacy.h b/src/lib/CONFIG/config_legacy.h
index 743b22bae5..91ccd5e90d 100644
--- a/src/lib/CONFIG/config_legacy.h
+++ b/src/lib/CONFIG/config_legacy.h
@@ -136,3 +136,24 @@ typedef struct {
uint8_t modelId;
v6_rx_config_pwm_t pwmChannels[16];
} v6_rx_config_t;
+
+typedef struct {
+ uint32_t version;
+ uint8_t uid[UID_LEN];
+ uint8_t loanUID[UID_LEN];
+ uint16_t vbatScale; // FUTURE: Override compiled vbat scale
+ uint8_t isBound:1,
+ onLoan:1,
+ power:4,
+ antennaMode:2; // 0=0, 1=1, 2=Diversity
+ uint8_t powerOnCounter:3,
+ forceTlmOff:1,
+ rateInitialIdx:4; // Rate to start rateCycling at on boot
+ uint8_t modelId;
+ uint8_t serialProtocol:4,
+ failsafeMode:2,
+ unused:2;
+ v6_rx_config_pwm_t pwmChannels[16];
+} v7_rx_config_t;
+
+// V8 is just V7 except PWM config inserted 10khz PWM in the middle
diff --git a/src/lib/CRSF/CRSF.h b/src/lib/CRSF/CRSF.h
deleted file mode 100644
index 76ae8dc120..0000000000
--- a/src/lib/CRSF/CRSF.h
+++ /dev/null
@@ -1,144 +0,0 @@
-#ifndef H_CRSF
-#define H_CRSF
-
-#include "targets.h"
-#include "crsf_protocol.h"
-#ifndef TARGET_NATIVE
-#include "HardwareSerial.h"
-#endif
-#include "common.h"
-#include "msp.h"
-#include "msptypes.h"
-#include "LowPassFilter.h"
-#include "../CRC/crc.h"
-#include "telemetry_protocol.h"
-
-#ifdef PLATFORM_ESP32
-#include "esp32-hal-uart.h"
-#include
-#include "freertos/FreeRTOS.h"
-#include "freertos/task.h"
-#include "driver/uart.h"
-#include "driver/gpio.h"
-#endif
-
-class CRSF
-{
-
-public:
- /////Variables/////
-
- #ifdef CRSF_TX_MODULE
- static HardwareSerial Port;
- static Stream *PortSecondary; // A second UART used to mirror telemetry out on the TX, not read from
-
- static void (*disconnected)();
- static void (*connected)();
-
- static void (*RecvModelUpdate)();
- static void (*RecvParameterUpdate)(uint8_t type, uint8_t index, uint8_t arg);
- static void (*RCdataCallback)();
-
- // The model ID as received from the Transmitter
- static uint8_t modelId;
- static bool ForwardDevicePings; // true if device pings should be forwarded OTA
- static bool elrsLUAmode;
-
- /// UART Handling ///
- static uint32_t GoodPktsCountResult; // need to latch the results
- static uint32_t BadPktsCountResult; // need to latch the results
- #endif
-
- static volatile crsfPayloadLinkstatistics_s LinkStatistics; // Link Statisitics Stored as Struct
-
- static void Begin(); //setup timers etc
- static void End(); //stop timers etc
-
- static void GetDeviceInformation(uint8_t *frame, uint8_t fieldCount);
- static void SetMspV2Request(uint8_t *frame, uint16_t function, uint8_t *payload, uint8_t payloadLength);
- static void SetHeaderAndCrc(uint8_t *frame, uint8_t frameType, uint8_t frameSize, uint8_t destAddr);
- static void SetExtendedHeaderAndCrc(uint8_t *frame, uint8_t frameType, uint8_t frameSize, uint8_t senderAddr, uint8_t destAddr);
- static uint32_t VersionStrToU32(const char *verStr);
-
- #ifdef CRSF_TX_MODULE
- static bool IsArmed() { return CRSF_to_BIT(ChannelData[4]); } // AUX1
- static void ICACHE_RAM_ATTR sendLinkStatisticsToTX();
- static void ICACHE_RAM_ATTR sendTelemetryToTX(uint8_t *data);
-
- static void packetQueueExtended(uint8_t type, void *data, uint8_t len);
-
- ///// Variables for OpenTX Syncing //////////////////////////
- #define OpenTXsyncPacketInterval 200 // in ms
- static void ICACHE_RAM_ATTR setSyncParams(int32_t PacketInterval);
- static void ICACHE_RAM_ATTR JustSentRFpacket();
- static void ICACHE_RAM_ATTR sendSyncPacketToTX();
- static void disableOpentxSync();
- static void enableOpentxSync();
-
- static void handleUARTin();
-
- static uint8_t getModelID() { return modelId; }
-
- static void GetMspMessage(uint8_t **data, uint8_t *len);
- static void UnlockMspMessage();
- static void AddMspMessage(const uint8_t length, uint8_t* data);
- static void AddMspMessage(mspPacket_t* packet);
- static void ResetMspQueue();
- static uint32_t OpenTXsyncLastSent;
- static uint8_t GetMaxPacketBytes() { return maxPacketBytes; }
- static uint32_t GetCurrentBaudRate() { return UARTrequestedBaud; }
-
- static uint32_t ICACHE_RAM_ATTR GetRCdataLastRecv();
- static void ICACHE_RAM_ATTR RcPacketToChannelsData();
- #endif
-
- /////////////////////////////////////////////////////////////
- static bool CRSFstate;
-
-private:
- static inBuffer_U inBuffer;
-
-#if CRSF_TX_MODULE
- /// OpenTX mixer sync ///
- static int32_t RequestedRCpacketInterval;
- static volatile uint32_t RCdataLastRecv;
- static volatile uint32_t dataLastRecv;
- static volatile int32_t OpenTXsyncOffset;
- static volatile int32_t OpenTXsyncWindow;
- static volatile int32_t OpenTXsyncWindowSize;
- static uint32_t OpenTXsyncOffsetSafeMargin;
- static bool OpentxSyncActive;
- static uint8_t CRSFoutBuffer[CRSF_MAX_PACKET_LEN];
-
- /// UART Handling ///
- static uint8_t SerialInPacketLen; // length of the CRSF packet as measured
- static uint8_t SerialInPacketPtr; // index where we are reading/writing
- static bool CRSFframeActive; //since we get a copy of the serial data use this flag to know when to ignore it
- static uint32_t GoodPktsCount;
- static uint32_t BadPktsCount;
- static uint32_t UARTwdtLastChecked;
- static uint8_t maxPacketBytes;
- static uint8_t maxPeriodBytes;
- static uint32_t TxToHandsetBauds[7];
- static uint8_t UARTcurrentBaudIdx;
- static uint32_t UARTrequestedBaud;
- static uint8_t MspData[ELRS_MSP_BUFFER];
- static uint8_t MspDataLength;
- #if defined(PLATFORM_ESP32)
- static bool UARTinverted;
- #endif
-
- static void ICACHE_RAM_ATTR adjustMaxPacketSize();
- static void duplex_set_RX();
- static void duplex_set_TX();
- static bool ProcessPacket();
- static void handleUARTout();
- static bool UARTwdt();
- static uint32_t autobaud();
- static void flush_port_input(void);
-#endif
-};
-
-extern GENERIC_CRC8 crsf_crc;
-
-#endif
diff --git a/src/lib/CRSF/devCRSF.h b/src/lib/CRSF/devCRSF.h
deleted file mode 100644
index 568bec2379..0000000000
--- a/src/lib/CRSF/devCRSF.h
+++ /dev/null
@@ -1,6 +0,0 @@
-#pragma once
-
-#include "device.h"
-#include "CRSF.h"
-
-extern device_t CRSF_device;
diff --git a/src/lib/CRSF/devCRSF_tx.cpp b/src/lib/CRSF/devCRSF_tx.cpp
deleted file mode 100644
index fe7aa8010b..0000000000
--- a/src/lib/CRSF/devCRSF_tx.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-#include "targets.h"
-#include "devCRSF.h"
-
-#ifdef CRSF_TX_MODULE
-
-#include "../POWERMGNT/POWERMGNT.h"
-
-static int start()
-{
- CRSF::Begin();
-#if defined(DEBUG_TX_FREERUN)
- CRSF::CRSFstate = true;
- if (CRSF::connected)
- {
- CRSF::connected();
- }
- else
- {
- ERRLN("CRSF::connected has not been initialised");
- }
-#endif
- return DURATION_IMMEDIATELY;
-}
-
-static int timeout()
-{
- CRSF::handleUARTin();
- return DURATION_IMMEDIATELY;
-}
-
-static int event()
-{
- // An event should be generated every time the TX power changes
- CRSF::LinkStatistics.uplink_TX_Power = powerToCrsfPower(PowerLevelContainer::currPower());
- return DURATION_IGNORE;
-}
-
-device_t CRSF_device = {
- .initialize = nullptr,
- .start = start,
- .event = event,
- .timeout = timeout
-};
-#endif
diff --git a/src/lib/CRSF2MSP/crsf2msp.cpp b/src/lib/CRSF2MSP/crsf2msp.cpp
index 13ef4820a2..b9ffa3869e 100644
--- a/src/lib/CRSF2MSP/crsf2msp.cpp
+++ b/src/lib/CRSF2MSP/crsf2msp.cpp
@@ -62,8 +62,10 @@ void CROSSFIRE2MSP::parse(const uint8_t *data)
outBuffer[idx] = getChecksum(outBuffer + 3, pktLen, MSPvers); // +3 because the header isn't in checksum
frameComplete = true;
+ FIFOout.lock();
FIFOout.pushSize(idx + 1);
FIFOout.pushBytes(outBuffer, idx + 1);
+ FIFOout.unlock();
}
}
diff --git a/src/lib/CRSF2MSP/crsf2msp.h b/src/lib/CRSF2MSP/crsf2msp.h
index efd103c7a7..e4f82a45b6 100644
--- a/src/lib/CRSF2MSP/crsf2msp.h
+++ b/src/lib/CRSF2MSP/crsf2msp.h
@@ -1,7 +1,7 @@
#pragma once
#include
-#include "FIFO_GENERIC.h"
+#include "FIFO.h"
#include "crsfmsp_common.h"
#include "crc.h"
#include "logging.h"
@@ -33,7 +33,7 @@ class CROSSFIRE2MSP
public:
CROSSFIRE2MSP();
- FIFO_GENERIC FIFOout;
+ FIFO FIFOout;
void parse(const uint8_t *data); // accept crsf frame input
bool isFrameReady();
const uint8_t *getFrame();
diff --git a/src/lib/CRSF2MSP/msp2crsf.cpp b/src/lib/CRSF2MSP/msp2crsf.cpp
index 3410f867f1..3b7fc41ba3 100644
--- a/src/lib/CRSF2MSP/msp2crsf.cpp
+++ b/src/lib/CRSF2MSP/msp2crsf.cpp
@@ -2,7 +2,7 @@
extern GENERIC_CRC8 crsf_crc;
-MSP2CROSSFIRE::MSP2CROSSFIRE() {} // empty constructor
+MSP2CROSSFIRE::MSP2CROSSFIRE() {}
void MSP2CROSSFIRE::setSeqNumber(uint8_t &data, uint8_t seqNumber)
{
@@ -191,9 +191,11 @@ void MSP2CROSSFIRE::parse(const uint8_t *data, uint32_t frameLen, uint8_t src, u
uint8_t crc = crsf_crc.calc(&header[3], 4, 0x00); // don't include the MSP header
crc = crsf_crc.calc(&data[startIdx], CRSFpktLen, crc);
+ FIFOout.lock();
FIFOout.pushBytes(header, sizeof(header));
FIFOout.pushBytes(&data[startIdx], CRSFpktLen);
FIFOout.push(crc);
+ FIFOout.unlock();
}
}
diff --git a/src/lib/CRSF2MSP/msp2crsf.h b/src/lib/CRSF2MSP/msp2crsf.h
index 629c735a86..86cf47a921 100644
--- a/src/lib/CRSF2MSP/msp2crsf.h
+++ b/src/lib/CRSF2MSP/msp2crsf.h
@@ -1,7 +1,7 @@
#pragma once
#include
-#include "FIFO_GENERIC.h"
+#include "FIFO.h"
#include "crsfmsp_common.h"
#include "crsf_protocol.h"
#include "crc.h"
@@ -28,7 +28,7 @@ class MSP2CROSSFIRE
public:
MSP2CROSSFIRE();
- FIFO_GENERIC FIFOout;
+ FIFO FIFOout;
void parse(const uint8_t *data, uint32_t frameLen, uint8_t src = CRSF_ADDRESS_CRSF_RECEIVER, uint8_t dest = CRSF_ADDRESS_FLIGHT_CONTROLLER);
bool validate(const uint8_t *data, uint32_t expectLen);
};
diff --git a/src/lib/CrsfProtocol/crsf_protocol.h b/src/lib/CrsfProtocol/crsf_protocol.h
index e54346e724..698ce56892 100644
--- a/src/lib/CrsfProtocol/crsf_protocol.h
+++ b/src/lib/CrsfProtocol/crsf_protocol.h
@@ -5,10 +5,6 @@
#include "crc.h"
#include "options.h"
-#if TARGET_TX && PLATFORM_STM32
-#define CRSF_TX_MODULE_STM32 1
-#endif
-
#if TARGET_TX
#define CRSF_TX_MODULE 1
#elif TARGET_RX || defined(UNIT_TEST)
@@ -62,7 +58,7 @@
#define CRSF_MSP_RESP_PAYLOAD_SIZE 58
#define CRSF_MSP_MAX_PAYLOAD_SIZE (CRSF_MSP_REQ_PAYLOAD_SIZE > CRSF_MSP_RESP_PAYLOAD_SIZE ? CRSF_MSP_REQ_PAYLOAD_SIZE : CRSF_MSP_RESP_PAYLOAD_SIZE)
-typedef enum
+typedef enum : uint8_t
{
CRSF_FRAMETYPE_GPS = 0x02,
CRSF_FRAMETYPE_VARIO = 0x07,
@@ -95,12 +91,13 @@ typedef enum
CRSF_FRAMETYPE_ARDUPILOT_RESP = 0x80,
} crsf_frame_type_e;
-typedef enum {
- SUBCOMMAND_CRSF = 0x10
+typedef enum : uint8_t {
+ CRSF_COMMAND_SUBCMD_RX = 0x10
} crsf_command_e;
-typedef enum {
- COMMAND_MODEL_SELECT_ID = 0x05
+typedef enum : uint8_t {
+ CRSF_COMMAND_SUBCMD_RX_BIND = 0x01,
+ CRSF_COMMAND_MODEL_SELECT_ID = 0x05
} crsf_subcommand_e;
enum {
@@ -120,7 +117,7 @@ enum {
CRSF_FRAME_GENERAL_RESP_PAYLOAD_SIZE = CRSF_EXT_FRAME_SIZE(CRSF_FRAME_TX_MSP_FRAME_SIZE)
};
-typedef enum
+typedef enum : uint8_t
{
CRSF_ADDRESS_BROADCAST = 0x00,
CRSF_ADDRESS_USB = 0x10,
@@ -140,7 +137,7 @@ typedef enum
//typedef struct crsf_addr_e asas;
-typedef enum
+typedef enum : uint8_t
{
CRSF_UINT8 = 0,
CRSF_INT8 = 1,
@@ -172,7 +169,7 @@ typedef struct crsf_header_s
{
uint8_t device_addr; // from crsf_addr_e
uint8_t frame_size; // counts size after this byte, so it must be the payload size + 2 (type and crc)
- uint8_t type; // from crsf_frame_type_e
+ crsf_frame_type_e type;
} PACKED crsf_header_t;
#define CRSF_MK_FRAME_T(payload) struct payload##_frame_s { crsf_header_t h; payload p; uint8_t crc; } PACKED
@@ -183,10 +180,11 @@ typedef struct crsf_ext_header_s
// Common header fields, see crsf_header_t
uint8_t device_addr;
uint8_t frame_size;
- uint8_t type;
+ crsf_frame_type_e type;
// Extended fields
- uint8_t dest_addr;
- uint8_t orig_addr;
+ crsf_addr_e dest_addr;
+ crsf_addr_e orig_addr;
+ uint8_t payload[0];
} PACKED crsf_ext_header_t;
/**
@@ -293,9 +291,6 @@ union inBuffer_U
// add other packet types here
};
-
-typedef struct crsf_channels_s crsf_channels_t;
-
//CRSF_FRAMETYPE_BATTERY_SENSOR
typedef struct crsf_sensor_battery_s
{
@@ -403,6 +398,21 @@ static inline uint16_t ICACHE_RAM_ATTR CRSF_to_N(uint16_t val, uint16_t cnt)
return (val - CRSF_CHANNEL_VALUE_1000) * cnt / (CRSF_CHANNEL_VALUE_2000 - CRSF_CHANNEL_VALUE_1000 + 1);
}
+static inline uint8_t ICACHE_RAM_ATTR CRSF_to_SWITCH3b(uint16_t ch)
+{
+ // AUX2-7 are Low Resolution, "7pos" 6+center (3-bit)
+ // The output is mapped evenly across 6 output values (0-5)
+ // with a special value 7 indicating the middle so it works
+ // with switches with a middle position as well as 6-position
+ const uint16_t CHANNEL_BIN_COUNT = 6;
+ const uint16_t CHANNEL_BIN_SIZE = (CRSF_CHANNEL_VALUE_MAX - CRSF_CHANNEL_VALUE_MIN) / CHANNEL_BIN_COUNT;
+ // If channel is within 1/4 a BIN of being in the middle use special value 7
+ if (ch < (CRSF_CHANNEL_VALUE_MID-CHANNEL_BIN_SIZE/4)
+ || ch > (CRSF_CHANNEL_VALUE_MID+CHANNEL_BIN_SIZE/4))
+ return CRSF_to_N(ch, CHANNEL_BIN_COUNT);
+ return 7;
+}
+
// 3b switches use 0-5 to represent 6 positions switches and "7" to represent middle
// The calculation is a bit non-linear all the way to the endpoints due to where
// Ardupilot defines its modes
diff --git a/src/lib/DEVICE/device.cpp b/src/lib/DEVICE/device.cpp
index fe2a8efe10..7c9b8a3805 100644
--- a/src/lib/DEVICE/device.cpp
+++ b/src/lib/DEVICE/device.cpp
@@ -9,6 +9,8 @@
#if defined(RADIO_SX127X)
#include "SX127xDriver.h"
+#elif defined(RADIO_LR1121)
+#include "LR1121Driver.h"
#elif defined(RADIO_SX128X)
#include "SX1280Driver.h"
#else
@@ -47,7 +49,7 @@ void devicesRegister(device_affinity_t *devices, uint8_t count)
taskSemaphore = xSemaphoreCreateBinary();
completeSemaphore = xSemaphoreCreateBinary();
disableCore0WDT();
- xTaskCreatePinnedToCore(deviceTask, "deviceTask", 3000, NULL, 0, &xDeviceTask, 0);
+ xTaskCreatePinnedToCore(deviceTask, "deviceTask", 32768, NULL, 0, &xDeviceTask, 0);
#endif
}
@@ -113,14 +115,16 @@ void devicesTriggerEvent()
#endif
}
-int devicesUpdate(unsigned long now)
+static int _devicesUpdate(unsigned long now)
{
- int32_t core = CURRENT_CORE;
+ const int32_t core = CURRENT_CORE;
+ const int32_t coreMulti = (core == -1) ? 0 : core;
- bool handleEvents = eventFired[core==-1?0:core] || lastConnectionState[core==-1?0:core] != connectionState || lastModelMatch[core==-1?0:core] != connectionHasModelMatch;
- eventFired[core==-1?0:core] = false;
- lastConnectionState[core==-1?0:core] = connectionState;
- lastModelMatch[core==-1?0:core] = connectionHasModelMatch;
+ bool newModelMatch = connectionHasModelMatch && teamraceHasModelMatch;
+ bool handleEvents = eventFired[coreMulti] || lastConnectionState[coreMulti] != connectionState || lastModelMatch[coreMulti] != newModelMatch;
+ eventFired[coreMulti] = false;
+ lastConnectionState[coreMulti] = connectionState;
+ lastModelMatch[coreMulti] = newModelMatch;
for(size_t i=0 ; i
-#if defined(RADIO_SX127X)
+#if defined(RADIO_SX127X) || defined(RADIO_LR1121)
+
+#if defined(RADIO_LR1121)
+#include "LR1121Driver.h"
+#else
#include "SX127xDriver.h"
+#endif
const fhss_config_t domains[] = {
{"AU915", FREQ_HZ_TO_REG_VAL(915500000), FREQ_HZ_TO_REG_VAL(926900000), 20},
@@ -12,8 +17,17 @@ const fhss_config_t domains[] = {
{"EU868", FREQ_HZ_TO_REG_VAL(865275000), FREQ_HZ_TO_REG_VAL(869575000), 13},
{"IN866", FREQ_HZ_TO_REG_VAL(865375000), FREQ_HZ_TO_REG_VAL(866950000), 4},
{"AU433", FREQ_HZ_TO_REG_VAL(433420000), FREQ_HZ_TO_REG_VAL(434420000), 3},
- {"EU433", FREQ_HZ_TO_REG_VAL(433100000), FREQ_HZ_TO_REG_VAL(434450000), 3}
+ {"EU433", FREQ_HZ_TO_REG_VAL(433100000), FREQ_HZ_TO_REG_VAL(434450000), 3},
+ {"US433", FREQ_HZ_TO_REG_VAL(433250000), FREQ_HZ_TO_REG_VAL(438000000), 8},
+ {"US433W", FREQ_HZ_TO_REG_VAL(423500000), FREQ_HZ_TO_REG_VAL(438000000), 20},
};
+
+#if defined(RADIO_LR1121)
+const fhss_config_t domainsDualBand[] = {
+ {"ISM2G4", FREQ_HZ_TO_REG_VAL(2400400000), FREQ_HZ_TO_REG_VAL(2479400000), 80}
+};
+#endif
+
#elif defined(RADIO_SX128X)
#include "SX1280Driver.h"
@@ -30,18 +44,62 @@ const fhss_config_t domains[] = {
// Our table of FHSS frequencies. Define a regulatory domain to select the correct set for your location and radio
const fhss_config_t *FHSSconfig;
+const fhss_config_t *FHSSconfigDualBand;
// Actual sequence of hops as indexes into the frequency list
-uint8_t FHSSsequence[256];
+uint8_t FHSSsequence[FHSS_SEQUENCE_LEN];
+uint8_t FHSSsequence_DualBand[FHSS_SEQUENCE_LEN];
+
// Which entry in the sequence we currently are on
uint8_t volatile FHSSptr;
+
// Channel for sync packets and initial connection establishment
uint_fast8_t sync_channel;
+uint_fast8_t sync_channel_DualBand;
+
// Offset from the predefined frequency determined by AFC on Team900 (register units)
int32_t FreqCorrection;
int32_t FreqCorrection_2;
+// Frequency hop separation
uint32_t freq_spread;
+uint32_t freq_spread_DualBand;
+
+// Variable for Dual Band radios
+bool FHSSusePrimaryFreqBand = true;
+bool FHSSuseDualBand = false;
+
+uint16_t primaryBandCount;
+uint16_t secondaryBandCount;
+
+void FHSSrandomiseFHSSsequence(const uint32_t seed)
+{
+ FHSSconfig = &domains[firmwareOptions.domain];
+ sync_channel = (FHSSconfig->freq_count / 2) + 1;
+ freq_spread = (FHSSconfig->freq_stop - FHSSconfig->freq_start) * FREQ_SPREAD_SCALE / (FHSSconfig->freq_count - 1);
+ primaryBandCount = (FHSS_SEQUENCE_LEN / FHSSconfig->freq_count) * FHSSconfig->freq_count;
+
+ DBGLN("Setting %s Mode", FHSSconfig->domain);
+ DBGLN("Number of FHSS frequencies = %u", FHSSconfig->freq_count);
+ DBGLN("Sync channel = %u", sync_channel);
+
+ FHSSrandomiseFHSSsequenceBuild(seed, FHSSconfig->freq_count, sync_channel, FHSSsequence);
+
+#if defined(RADIO_LR1121)
+ FHSSconfigDualBand = &domainsDualBand[0];
+ sync_channel_DualBand = (FHSSconfigDualBand->freq_count / 2) + 1;
+ freq_spread_DualBand = (FHSSconfigDualBand->freq_stop - FHSSconfigDualBand->freq_start) * FREQ_SPREAD_SCALE / (FHSSconfigDualBand->freq_count - 1);
+ secondaryBandCount = (FHSS_SEQUENCE_LEN / FHSSconfigDualBand->freq_count) * FHSSconfigDualBand->freq_count;
+
+ DBGLN("Setting Dual Band %s Mode", FHSSconfigDualBand->domain);
+ DBGLN("Number of FHSS frequencies = %u", FHSSconfigDualBand->freq_count);
+ DBGLN("Sync channel Dual Band = %u", sync_channel_DualBand);
+
+ FHSSusePrimaryFreqBand = false;
+ FHSSrandomiseFHSSsequenceBuild(seed, FHSSconfigDualBand->freq_count, sync_channel_DualBand, FHSSsequence_DualBand);
+ FHSSusePrimaryFreqBand = true;
+#endif
+}
/**
Requirements:
@@ -56,17 +114,8 @@ uint32_t freq_spread;
another random entry, excluding the sync channel.
*/
-void FHSSrandomiseFHSSsequence(const uint32_t seed)
+void FHSSrandomiseFHSSsequenceBuild(const uint32_t seed, uint32_t freqCount, uint_fast8_t syncChannel, uint8_t *inSequence)
{
- FHSSconfig = &domains[firmwareOptions.domain];
- DBGLN("Setting %s Mode", FHSSconfig->domain);
- DBGLN("Number of FHSS frequencies = %u", FHSSconfig->freq_count);
-
- sync_channel = (FHSSconfig->freq_count / 2) + 1;
- DBGLN("Sync channel = %u", sync_channel);
-
- freq_spread = (FHSSconfig->freq_stop - FHSSconfig->freq_start) * FREQ_SPREAD_SCALE / (FHSSconfig->freq_count - 1);
-
// reset the pointer (otherwise the tests fail)
FHSSptr = 0;
rngSeed(seed);
@@ -74,34 +123,34 @@ void FHSSrandomiseFHSSsequence(const uint32_t seed)
// initialize the sequence array
for (uint16_t i = 0; i < FHSSgetSequenceCount(); i++)
{
- if (i % FHSSconfig->freq_count == 0) {
- FHSSsequence[i] = sync_channel;
- } else if (i % FHSSconfig->freq_count == sync_channel) {
- FHSSsequence[i] = 0;
+ if (i % freqCount == 0) {
+ inSequence[i] = syncChannel;
+ } else if (i % freqCount == syncChannel) {
+ inSequence[i] = 0;
} else {
- FHSSsequence[i] = i % FHSSconfig->freq_count;
+ inSequence[i] = i % freqCount;
}
}
- for (uint16_t i=0; i < FHSSgetSequenceCount(); i++)
+ for (uint16_t i = 0; i < FHSSgetSequenceCount(); i++)
{
// if it's not the sync channel
- if (i % FHSSconfig->freq_count != 0)
+ if (i % freqCount != 0)
{
- uint8_t offset = (i / FHSSconfig->freq_count) * FHSSconfig->freq_count; // offset to start of current block
- uint8_t rand = rngN(FHSSconfig->freq_count-1)+1; // random number between 1 and FHSS_FREQ_CNT
+ uint8_t offset = (i / freqCount) * freqCount; // offset to start of current block
+ uint8_t rand = rngN(freqCount - 1) + 1; // random number between 1 and FHSS_FREQ_CNT
// switch this entry and another random entry in the same block
- uint8_t temp = FHSSsequence[i];
- FHSSsequence[i] = FHSSsequence[offset+rand];
- FHSSsequence[offset+rand] = temp;
+ uint8_t temp = inSequence[i];
+ inSequence[i] = inSequence[offset+rand];
+ inSequence[offset+rand] = temp;
}
}
// output FHSS sequence
for (uint16_t i=0; i < FHSSgetSequenceCount(); i++)
{
- DBG("%u ",FHSSsequence[i]);
+ DBG("%u ",inSequence[i]);
if (i % 10 == 9)
DBGCR;
}
diff --git a/src/lib/FHSS/FHSS.h b/src/lib/FHSS/FHSS.h
index e26bf53836..bd82d41035 100644
--- a/src/lib/FHSS/FHSS.h
+++ b/src/lib/FHSS/FHSS.h
@@ -5,13 +5,22 @@
#if defined(RADIO_SX127X)
#define FreqCorrectionMax ((int32_t)(100000/FREQ_STEP))
+#elif defined(RADIO_LR1121)
+#define FreqCorrectionMax ((int32_t)(100000/FREQ_STEP)) // TODO - This needs checking !!!
#elif defined(RADIO_SX128X)
#define FreqCorrectionMax ((int32_t)(200000/FREQ_STEP))
#endif
#define FreqCorrectionMin (-FreqCorrectionMax)
+#if defined(RADIO_LR1121)
+#define FREQ_HZ_TO_REG_VAL(freq) (freq)
+#define FREQ_SPREAD_SCALE 1
+#else
#define FREQ_HZ_TO_REG_VAL(freq) ((uint32_t)((double)freq/(double)FREQ_STEP))
#define FREQ_SPREAD_SCALE 256
+#endif
+
+#define FHSS_SEQUENCE_LEN 256
typedef struct {
const char *domain;
@@ -21,32 +30,88 @@ typedef struct {
} fhss_config_t;
extern volatile uint8_t FHSSptr;
+extern int32_t FreqCorrection; // Only used for the SX1276
+extern int32_t FreqCorrection_2; // Only used for the SX1276
+
+// Primary Band
+extern uint16_t primaryBandCount;
extern uint32_t freq_spread;
-extern int32_t FreqCorrection;
-extern int32_t FreqCorrection_2;
extern uint8_t FHSSsequence[];
extern uint_fast8_t sync_channel;
extern const fhss_config_t *FHSSconfig;
+// DualBand Variables
+extern bool FHSSusePrimaryFreqBand;
+extern bool FHSSuseDualBand;
+extern uint16_t secondaryBandCount;
+extern uint32_t freq_spread_DualBand;
+extern uint8_t FHSSsequence_DualBand[];
+extern uint_fast8_t sync_channel_DualBand;
+extern const fhss_config_t *FHSSconfigDualBand;
+
// create and randomise an FHSS sequence
void FHSSrandomiseFHSSsequence(uint32_t seed);
+void FHSSrandomiseFHSSsequenceBuild(uint32_t seed, uint32_t freqCount, uint_fast8_t sync_channel, uint8_t *sequence);
+
+static inline uint32_t FHSSgetMinimumFreq(void)
+{
+ return FHSSconfig->freq_start;
+}
+
+static inline uint32_t FHSSgetMaximumFreq(void)
+{
+ return FHSSconfig->freq_stop;
+}
// The number of frequencies for this regulatory domain
static inline uint32_t FHSSgetChannelCount(void)
{
- return FHSSconfig->freq_count;
+ if (FHSSusePrimaryFreqBand)
+ {
+ return FHSSconfig->freq_count;
+ }
+ else
+ {
+ return FHSSconfigDualBand->freq_count;
+ }
}
// get the number of entries in the FHSS sequence
static inline uint16_t FHSSgetSequenceCount()
{
- return (256 / FHSSconfig->freq_count) * FHSSconfig->freq_count;
+ if (FHSSuseDualBand) // Use the smaller of the 2 bands as not to go beyond the max index for each sequence.
+ {
+ if (primaryBandCount < secondaryBandCount)
+ {
+ return primaryBandCount;
+ }
+ else
+ {
+ return secondaryBandCount;
+ }
+ }
+
+ if (FHSSusePrimaryFreqBand)
+ {
+ return primaryBandCount;
+ }
+ else
+ {
+ return secondaryBandCount;
+ }
}
// get the initial frequency, which is also the sync channel
-static inline uint32_t GetInitialFreq()
+static inline uint32_t FHSSgetInitialFreq()
{
- return FHSSconfig->freq_start + (sync_channel * freq_spread / FREQ_SPREAD_SCALE) - FreqCorrection;
+ if (FHSSusePrimaryFreqBand)
+ {
+ return FHSSconfig->freq_start + (sync_channel * freq_spread / FREQ_SPREAD_SCALE) - FreqCorrection;
+ }
+ else
+ {
+ return FHSSconfigDualBand->freq_start + (sync_channel_DualBand * freq_spread_DualBand / FREQ_SPREAD_SCALE);
+ }
}
// Get the current sequence pointer
@@ -55,6 +120,19 @@ static inline uint8_t FHSSgetCurrIndex()
return FHSSptr;
}
+// Is the current frequency the sync frequency
+static inline uint8_t FHSSonSyncChannel()
+{
+ if (FHSSusePrimaryFreqBand)
+ {
+ return FHSSsequence[FHSSptr] == sync_channel;
+ }
+ else
+ {
+ return FHSSsequence_DualBand[FHSSptr] == sync_channel_DualBand;
+ }
+}
+
// Set the sequence pointer, used by RX on SYNC
static inline void FHSSsetCurrIndex(const uint8_t value)
{
@@ -65,30 +143,83 @@ static inline void FHSSsetCurrIndex(const uint8_t value)
static inline uint32_t FHSSgetNextFreq()
{
FHSSptr = (FHSSptr + 1) % FHSSgetSequenceCount();
- uint32_t freq = FHSSconfig->freq_start + (freq_spread * FHSSsequence[FHSSptr] / FREQ_SPREAD_SCALE) - FreqCorrection;
- return freq;
+
+ if (FHSSusePrimaryFreqBand)
+ {
+ return FHSSconfig->freq_start + (freq_spread * FHSSsequence[FHSSptr] / FREQ_SPREAD_SCALE) - FreqCorrection;
+ }
+ else
+ {
+ return FHSSconfigDualBand->freq_start + (freq_spread_DualBand * FHSSsequence_DualBand[FHSSptr] / FREQ_SPREAD_SCALE);
+ }
}
-static inline const char *getRegulatoryDomain()
+static inline const char *FHSSgetRegulatoryDomain()
{
- return FHSSconfig->domain;
+ if (FHSSusePrimaryFreqBand)
+ {
+ return FHSSconfig->domain;
+ }
+ else
+ {
+ return FHSSconfigDualBand->domain;
+ }
}
// Get frequency offset by half of the domain frequency range
static inline uint32_t FHSSGeminiFreq(uint8_t FHSSsequenceIdx)
{
+ uint32_t freq;
uint32_t numfhss = FHSSgetChannelCount();
- uint8_t offSetIdx = (FHSSsequenceIdx + (numfhss / 2)) % numfhss;
- uint32_t freq = FHSSconfig->freq_start + (freq_spread * offSetIdx / FREQ_SPREAD_SCALE) - FreqCorrection_2;
+ uint8_t offSetIdx = (FHSSsequenceIdx + (numfhss / 2)) % numfhss;
+
+ if (FHSSusePrimaryFreqBand)
+ {
+ freq = FHSSconfig->freq_start + (freq_spread * offSetIdx / FREQ_SPREAD_SCALE) - FreqCorrection_2;
+ }
+ else
+ {
+ freq = FHSSconfigDualBand->freq_start + (freq_spread_DualBand * offSetIdx / FREQ_SPREAD_SCALE);
+ }
+
return freq;
}
static inline uint32_t FHSSgetGeminiFreq()
{
- return FHSSGeminiFreq(FHSSsequence[FHSSgetCurrIndex()]);
+ if (FHSSuseDualBand)
+ {
+ // When using Dual Band there is no need to calculate an offset frequency. Unlike Gemini with 2 frequencies in the same band.
+ return FHSSconfigDualBand->freq_start + (FHSSsequence_DualBand[FHSSptr] * freq_spread_DualBand / FREQ_SPREAD_SCALE);
+ }
+ else
+ {
+ if (FHSSusePrimaryFreqBand)
+ {
+ return FHSSGeminiFreq(FHSSsequence[FHSSgetCurrIndex()]);
+ }
+ else
+ {
+ return FHSSGeminiFreq(FHSSsequence_DualBand[FHSSgetCurrIndex()]);
+ }
+ }
}
static inline uint32_t FHSSgetInitialGeminiFreq()
{
- return FHSSGeminiFreq(sync_channel);
+ if (FHSSuseDualBand)
+ {
+ return FHSSconfigDualBand->freq_start + (sync_channel_DualBand * freq_spread_DualBand / FREQ_SPREAD_SCALE);
+ }
+ else
+ {
+ if (FHSSusePrimaryFreqBand)
+ {
+ return FHSSGeminiFreq(sync_channel);
+ }
+ else
+ {
+ return FHSSGeminiFreq(sync_channel_DualBand);
+ }
+ }
}
diff --git a/src/lib/FIFO/FIFO.cpp b/src/lib/FIFO/FIFO.cpp
deleted file mode 100644
index 3fb30fa599..0000000000
--- a/src/lib/FIFO/FIFO.cpp
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * FIFO Buffer
- * Implementation uses arrays to conserve memory
- *
- * The MIT License (MIT)
- *
- * Copyright (c) 2015 Daniel Eisterhold
- *
- * 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.
- *
- *
- * Modified/Ammended by Alessandro Carcione 2020
- */
-#include "FIFO.h"
-#include "logging.h"
-
-#if defined(PLATFORM_ESP32)
-#define ENTER_CRITICAL portENTER_CRITICAL(&mux)
-#define EXIT_CRITICAL portEXIT_CRITICAL(&mux)
-#elif defined(PLATFORM_ESP8266)
-#define ENTER_CRITICAL noInterrupts()
-#define EXIT_CRITICAL interrupts()
-#elif defined(PLATFORM_STM32)
-#define ENTER_CRITICAL noInterrupts()
-#define EXIT_CRITICAL interrupts()
-#else
-#define ENTER_CRITICAL
-#define EXIT_CRITICAL
-#endif
-
-FIFO::FIFO()
-{
- head = 0;
- tail = 0;
- numElements = 0;
-}
-
-FIFO::~FIFO()
-{
-}
-
-void ICACHE_RAM_ATTR FIFO::push(const uint8_t data)
-{
- if (numElements == FIFO_SIZE)
- {
- ERRLN("Buffer full, will flush");
- flush();
- return;
- }
- else
- {
- ENTER_CRITICAL;
- numElements++;
- buffer[tail] = data;
- tail = (tail + 1) % FIFO_SIZE;
- EXIT_CRITICAL;
- }
-}
-
-void ICACHE_RAM_ATTR FIFO::pushBytes(const uint8_t *data, uint8_t len)
-{
- if (numElements + len > FIFO_SIZE)
- {
- ERRLN("Buffer full, will flush");
- flush();
- return;
- }
- ENTER_CRITICAL;
- for (int i = 0; i < len; i++)
- {
- buffer[tail] = data[i];
- tail = (tail + 1) % FIFO_SIZE;
- }
- numElements += len;
- EXIT_CRITICAL;
-}
-
-uint8_t ICACHE_RAM_ATTR FIFO::pop()
-{
- if (numElements == 0)
- {
- // DBGLN(F("Buffer empty"));
- return 0;
- }
- ENTER_CRITICAL;
- numElements--;
- uint8_t data = buffer[head];
- head = (head + 1) % FIFO_SIZE;
- EXIT_CRITICAL;
- return data;
-}
-
-void ICACHE_RAM_ATTR FIFO::popBytes(uint8_t *data, uint8_t len)
-{
- if (numElements < len)
- {
- // DBGLN(F("Buffer underrun"));
- flush();
- return;
- }
- ENTER_CRITICAL;
- numElements -= len;
- for (int i = 0; i < len; i++)
- {
- data[i] = buffer[head];
- head = (head + 1) % FIFO_SIZE;
- }
- EXIT_CRITICAL;
-}
-
-uint8_t ICACHE_RAM_ATTR FIFO::peek()
-{
- if (numElements == 0)
- {
- // DBGLN(F("Buffer empty"));
- return 0;
- }
- uint8_t data = buffer[head];
- return data;
-}
-
-uint16_t ICACHE_RAM_ATTR FIFO::size()
-{
- return numElements;
-}
-
-void ICACHE_RAM_ATTR FIFO::flush()
-{
- ENTER_CRITICAL;
- head = 0;
- tail = 0;
- numElements = 0;
- EXIT_CRITICAL;
-}
-
-bool ICACHE_RAM_ATTR FIFO::ensure(uint8_t requiredSize)
-{
- if(requiredSize > FIFO_SIZE)
- {
- return false;
- }
- ENTER_CRITICAL;
- while(!available(requiredSize))
- {
- uint8_t len = pop();
- head = (head + len) % FIFO_SIZE;
- numElements -= len;
- }
- EXIT_CRITICAL;
- return true;
-}
\ No newline at end of file
diff --git a/src/lib/FIFO/FIFO.h b/src/lib/FIFO/FIFO.h
index 043c38f26e..6acc65b2a8 100644
--- a/src/lib/FIFO/FIFO.h
+++ b/src/lib/FIFO/FIFO.h
@@ -32,53 +32,276 @@
#pragma once
#include "targets.h"
+#include "logging.h"
+/**
+ * @brief A FIFO which can be made thread/SMP safe using coarse-grained locking via `lock`/`unlock` methods.
+ *
+ * The FIFO also has helper methods for pushing/popping 16-bit size prefixes to the FIFO. This is useful
+ * for FIFOs that are used to hold "packets" of data.
+ *
+ * @tparam FIFO_SIZE size of the FIFO in bytes
+ */
+template
class FIFO
{
-public:
- // If this is 256 then the compiler can optimise out the modulo because the head/tail are uint8_t types
- static const uint16_t FIFO_SIZE = 256;
-
private:
- uint8_t head;
- uint8_t tail;
- uint16_t numElements;
uint8_t buffer[FIFO_SIZE] = {0};
+ uint32_t head = 0;
+ uint32_t tail = 0;
+ uint32_t numElements = 0;
#if defined(PLATFORM_ESP32)
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
#endif
public:
- FIFO();
- ~FIFO();
+ /**
+ * @brief lock the FIFO so no other code should interact with the FIFO.
+ * Assumes that critical blocks are wrapped in lock/unlock semantics
+ */
+ ICACHE_RAM_ATTR void inline lock()
+ {
+ #if defined(PLATFORM_ESP32)
+ portENTER_CRITICAL(&mux);
+ #elif defined(PLATFORM_ESP8266)
+ noInterrupts();
+ #elif defined(PLATFORM_STM32)
+ noInterrupts();
+ #else
+ #endif
+ }
+
+ /**
+ * @brief unlock the FIFO
+ */
+ ICACHE_RAM_ATTR void inline unlock()
+ {
+ #if defined(PLATFORM_ESP32)
+ portEXIT_CRITICAL(&mux);
+ #elif defined(PLATFORM_ESP8266)
+ interrupts();
+ #elif defined(PLATFORM_STM32)
+ interrupts();
+ #else
+ #endif
+ }
+
+ /**
+ * @brief Push a single byte to the FIFO, FIFO is flushed if this byte will not fit and the byte is not pushed
+ *
+ * @param data
+ */
+ ICACHE_RAM_ATTR void inline push(const uint8_t data)
+ {
+ if (numElements == FIFO_SIZE)
+ {
+ ERRLN("Buffer full, will flush");
+ flush();
+ return;
+ }
+ else
+ {
+ numElements++;
+ buffer[tail] = data;
+ tail = (tail + 1) % FIFO_SIZE;
+ }
+ }
+
+ /**
+ * @brief Push all bytes to FIFO, if all the bytes will not fit then the FIFO is flushed and no bytes are pushed
+ *
+ * @param data pointer to the bytes to be pushed onto the FIFO
+ * @param len number of bytes in `data` to push
+ */
+ ICACHE_RAM_ATTR void inline pushBytes(const uint8_t *data, uint16_t len)
+ {
+ if (numElements + len > FIFO_SIZE)
+ {
+ ERRLN("Buffer full, will flush");
+ flush();
+ return;
+ }
+ for (int i = 0; i < len; i++)
+ {
+ buffer[tail] = data[i];
+ tail = (tail + 1) % FIFO_SIZE;
+ }
+ numElements += len;
+ }
+
+ /**
+ * @brief Push all bytes to FIFO, if all the bytes will not fit then the FIFO is flushed and no bytes are pushed.
+ * This is performed under locking so the whole call is atomic.
+ *
+ * @param data pointer to the bytes to be pushed onto the FIFO
+ * @param len number of bytes in `data` to push
+ */
+ ICACHE_RAM_ATTR void inline atomicPushBytes(const uint8_t *data, uint16_t len)
+ {
+ lock();
+ pushBytes(data, len);
+ unlock();
+ }
+
+ /**
+ * @brief Pop a single byte (returns 0 if no bytes left)
+ * @return the byte on the head of FIFO
+ */
+ ICACHE_RAM_ATTR uint8_t inline pop()
+ {
+ if (numElements == 0)
+ {
+ // DBGLN(F("Buffer empty"));
+ return 0;
+ }
+ numElements--;
+ uint8_t data = buffer[head];
+ head = (head + 1) % FIFO_SIZE;
+ return data;
+ }
+
+ /**
+ * @brief Pops `len` bytes into the buffer pointed to by `data`.
+ * If there are not enough bytes in the FIFO then the FIFO is flushed and the bytes are not read
+ *
+ * @param data pointer to a buffer where the bytes are popped into
+ * @param len number of bytes to pop from teh FIFO
+ */
+ ICACHE_RAM_ATTR void inline popBytes(uint8_t *data, uint16_t len)
+ {
+ if (numElements < len)
+ {
+ // DBGLN(F("Buffer underrun"));
+ flush();
+ return;
+ }
+ numElements -= len;
+ for (int i = 0; i < len; i++)
+ {
+ data[i] = buffer[head];
+ head = (head + 1) % FIFO_SIZE;
+ }
+ }
- // Push a single byte to the FIFO, FIFO is flushed if this byte will not fit and the byte is not pushed
- void push(const uint8_t data);
+ /**
+ * @brief return the first byte in the FIFO without removing it from the FIFO
+ * Safe to call without locking
+ *
+ * @return uint8_t the fist byte in the FIFO
+ */
+ ICACHE_RAM_ATTR uint8_t inline peek()
+ {
+ if (numElements == 0)
+ {
+ // DBGLN(F("Buffer empty"));
+ return 0;
+ }
+ uint8_t data = buffer[head];
+ return data;
+ }
- // Push all bytes to FIFO, if all the bytes will not fit then the FIFO is flushed and no bytes are pushed
- void pushBytes(const uint8_t *data, uint8_t len);
+ /**
+ * @brief return the number of bytes in the FIFO
+ * Safe to call without locking
+ *
+ * @return number of bytes in the FIFO
+ */
+ ICACHE_RAM_ATTR uint16_t inline size()
+ {
+ return numElements;
+ }
- // Pop a single byte (returns 0 if no bytes left)
- uint8_t pop();
+ /**
+ * @brief return the number of bytes free in the FIFO
+ * Safe to call without locking
+ *
+ * @return number of bytes free in the FIFO
+ */
+ ICACHE_RAM_ATTR uint16_t inline free()
+ {
+ return FIFO_SIZE - numElements;
+ }
- // Pops 'len' bytes into the buffer pointed to by 'data'. If there are not enough bytes in the FIFO
- // then the FIFO is flush and the bytes are not read
- void popBytes(uint8_t *data, uint8_t len);
+ /**
+ * @brief push a 16-bit size prefix onto the FIFO
+ *
+ * @param size the size prefix to be pushed to the FIFO
+ */
+ ICACHE_RAM_ATTR void inline pushSize(uint16_t size)
+ {
+ push(size & 0xFF);
+ push((size >> 8) & 0xFF);
+ }
- // return the first byte in the FIFO without removing it
- uint8_t peek();
+ /**
+ * @brief return the size prefix from the head of the FIFO, without removing it from the FIFO
+ *
+ * @param size the size prefix from the head of the FIFO
+ */
+ ICACHE_RAM_ATTR uint16_t inline peekSize()
+ {
+ if (size() > 1)
+ {
+ return (uint16_t)buffer[head] + ((uint16_t)buffer[(head + 1) % FIFO_SIZE] << 8);
+ }
+ return 0;
+ }
- // return the number of bytes in the FIFO
- uint16_t size();
+ /**
+ * @brief return the size prefix from the head of the FIFO, also removing it from the FIFO
+ *
+ * @param size the size prefix from the head of the FIFO
+ */
+ ICACHE_RAM_ATTR uint16_t inline popSize()
+ {
+ if (size() > 1)
+ {
+ return (uint16_t)pop() + ((uint16_t)pop() << 8);
+ }
+ return 0;
+ }
- // reset the FIFO back to empty
- void flush();
+ /**
+ * @brief reset the FIFO back to empty
+ */
+ ICACHE_RAM_ATTR void inline flush()
+ {
+ head = 0;
+ tail = 0;
+ numElements = 0;
+ }
- // returns true if the number of bytes requested is available in the FIFO
- bool available(uint8_t requiredSize) { return (numElements + requiredSize) < FIFO_SIZE; }
+ /**
+ * @brief Check to see if the FIFO can accept the number of bytes in the parameter
+ *
+ * @return true if the FIFO can accept the number of bytes requested
+ */
+ ICACHE_RAM_ATTR bool inline available(uint16_t requiredSize)
+ {
+ return (numElements + requiredSize) < FIFO_SIZE;
+ }
- // Ensure that there is enough room in the FIFO for the requestedSize in bytes.
- // "packets" are popped from the head of the FIFO until there is enough room available.
- // This method assumes that on the FIFO contains length-prefixed data packets.
- bool ensure(uint8_t requiredSize);
+ /**
+ * @brief Ensure that there is enough room in the FIFO for the requestedSize in bytes.
+ *
+ * "packets" are popped from the head of the FIFO until there is enough room available.
+ * This method assumes that on the FIFO contains 8-bit length-prefixed data packets.
+ *
+ * @param requiredSize the number of bytes required to be available
+ * @return true if the required amount of bytes will fit in the FIFO
+ */
+ ICACHE_RAM_ATTR bool inline ensure(uint16_t requiredSize)
+ {
+ if(requiredSize > FIFO_SIZE)
+ {
+ return false;
+ }
+ while(!available(requiredSize))
+ {
+ uint8_t len = pop();
+ head = (head + len) % FIFO_SIZE;
+ numElements -= len;
+ }
+ return true;
+ }
};
\ No newline at end of file
diff --git a/src/lib/FIFO_GENERIC/FIFO_GENERIC.h b/src/lib/FIFO_GENERIC/FIFO_GENERIC.h
deleted file mode 100644
index 453b7461c5..0000000000
--- a/src/lib/FIFO_GENERIC/FIFO_GENERIC.h
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * FIFO Buffer
- * Implementation uses arrays to conserve memory
- *
- * The MIT License (MIT)
- *
- * Copyright (c) 2015 Daniel Eisterhold
- *
- * 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.
- *
- *
- *
- * Modified/Ammended by Alessandro Carcione 2020
- */
-
-#pragma once
-#include "targets.h"
-#include "logging.h"
-
-template
-class FIFO_GENERIC
-{
-private:
- uint32_t head;
- uint32_t tail;
- uint32_t numElements;
- uint8_t buffer[FIFO_SIZE] = {0};
-#if defined(PLATFORM_ESP32)
- portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
-#define ENTER_CRITICAL portENTER_CRITICAL(&mux)
-#define EXIT_CRITICAL portEXIT_CRITICAL(&mux)
-#elif defined(PLATFORM_ESP8266)
-#define ENTER_CRITICAL noInterrupts()
-#define EXIT_CRITICAL interrupts()
-#elif defined(PLATFORM_STM32)
-#define ENTER_CRITICAL noInterrupts()
-#define EXIT_CRITICAL interrupts()
-#else
-#define ENTER_CRITICAL
-#define EXIT_CRITICAL
-#endif
-
-public:
- FIFO_GENERIC()
- {
- head = 0;
- tail = 0;
- numElements = 0;
- memset(buffer, 0, FIFO_SIZE);
- }
-
- void push(const uint8_t data)
- {
- if (numElements == FIFO_SIZE)
- {
- ERRLN("Buffer full, will flush");
- flush();
- return;
- }
- ENTER_CRITICAL;
- buffer[tail] = data;
- tail = (tail + 1) % FIFO_SIZE;
- numElements++;
- EXIT_CRITICAL;
- }
-
- void pushBytes(const uint8_t *data, uint32_t len)
- {
- if (numElements + len > FIFO_SIZE)
- {
- ERRLN("Buffer full, will flush");
- flush();
- return;
- }
- ENTER_CRITICAL;
- for (uint32_t i = 0; i < len; i++)
- {
- buffer[tail] = data[i];
- tail = (tail + 1) % FIFO_SIZE;
- }
- numElements += len;
- EXIT_CRITICAL;
- }
-
- uint8_t pop()
- {
- if (numElements == 0)
- {
- // DBGLN(F("Buffer empty"));
- return 0;
- }
- ENTER_CRITICAL;
- uint8_t data = buffer[head];
- head = (head + 1) % FIFO_SIZE;
- numElements--;
- EXIT_CRITICAL;
- return data;
- }
-
- void popBytes(uint8_t *data, uint32_t len)
- {
- if (numElements < len)
- {
- // DBGLN(F("Buffer underrun"));
- flush();
- return;
- }
- ENTER_CRITICAL;
- for (uint32_t i = 0; i < len; i++)
- {
- data[i] = buffer[head];
- head = (head + 1) % FIFO_SIZE;
- }
- numElements -= len;
- EXIT_CRITICAL;
- }
-
- uint8_t peek()
- {
- if (numElements == 0)
- {
- // DBGLN(F("Buffer empty"));
- return 0;
- }
- else
- {
- uint8_t data = buffer[head];
- return data;
- }
- }
-
- uint16_t free()
- {
- return FIFO_SIZE - numElements;
- }
-
- uint16_t size()
- {
- return numElements;
- }
-
- void pushSize(uint16_t size)
- {
- push(size & 0xFF);
- push((size >> 8) & 0xFF);
- }
-
- uint16_t peekSize()
- {
- if (size() > 1)
- {
- return (uint16_t)buffer[head] + ((uint16_t)buffer[(head + 1) % FIFO_SIZE] << 8);
- }
- return 0;
- }
-
- uint16_t popSize()
- {
- if (size() > 1)
- {
- return (uint16_t)pop() + ((uint16_t)pop() << 8);
- }
- return 0;
- }
-
- void flush()
- {
- ENTER_CRITICAL;
- head = 0;
- tail = 0;
- numElements = 0;
- EXIT_CRITICAL;
- }
-};
\ No newline at end of file
diff --git a/src/lib/GSENSOR/devGsensor.cpp b/src/lib/GSENSOR/devGsensor.cpp
index d146a2f34d..445ec641b7 100644
--- a/src/lib/GSENSOR/devGsensor.cpp
+++ b/src/lib/GSENSOR/devGsensor.cpp
@@ -9,6 +9,7 @@
#include "devGsensor.h"
#include
+#include "handset.h"
#include "gsensor.h"
#include "POWERMGNT.h"
#include "config.h"
@@ -24,14 +25,6 @@ Gsensor gsensor;
static int system_quiet_state = GSENSOR_SYSTEM_STATE_MOVING;
static int system_quiet_pre_state = GSENSOR_SYSTEM_STATE_MOVING;
-static unsigned int bumps = 0;
-static unsigned long lastBumpTime = 0;
-static unsigned long lastBumpCommand = 0;
-
-extern bool IsArmed();
-extern void SendRxLoanOverMSP();
-extern void EnterBindingMode();
-extern void deferExecution(uint32_t ms, std::function f);
#define GSENSOR_DURATION 10
#define GSENSOR_SYSTEM_IDLE_INTERVAL 1000U
@@ -62,34 +55,6 @@ static int timeout()
{
static unsigned long lastIdleCheckMs = 0;
unsigned long now = millis();
- if (config.GetMotionMode() == 1 && gsensor.hasTriggered(now) && (now - lastBumpCommand) > BUMP_COMMAND_IDLE_TIME)
- {
- lastBumpTime = now;
- bumps++;
- }
- if (bumps > 0 && (now - lastBumpTime > MULTIPLE_BUMP_INTERVAL))
- {
- float x, y, z;
- gsensor.getGSensorData(&x, &y, &z);
- // Single bump while holding the radio antenna up and NOT armed is Loan/Bind
- if (!CRSF::IsArmed() && bumps == 1 && fabs(x) < 0.5 && y < -0.8 && fabs(z) < 0.5)
- {
- lastBumpCommand = now;
- if (connectionState == connected)
- {
- DBGLN("Loaning model");
- SendRxLoanOverMSP();
- }
- else
- {
- DBGLN("Borrowing model");
- // defer this calling `EnterBindingMode` for 2 seconds
- deferExecution(2000, EnterBindingMode);
- }
- }
- DBGLN("Bumps %d : %f %f %f", bumps, x, y, z);
- bumps = 0;
- }
if (now - lastIdleCheckMs > GSENSOR_SYSTEM_IDLE_INTERVAL)
{
gsensor.handle();
@@ -98,7 +63,7 @@ static int timeout()
//When system is idle, set power to minimum
if(config.GetMotionMode() == 1)
{
- if((system_quiet_state == GSENSOR_SYSTEM_STATE_QUIET) && (system_quiet_pre_state == GSENSOR_SYSTEM_STATE_MOVING) && !CRSF::IsArmed())
+ if((system_quiet_state == GSENSOR_SYSTEM_STATE_QUIET) && (system_quiet_pre_state == GSENSOR_SYSTEM_STATE_MOVING) && !handset->IsArmed())
{
POWERMGNT::setPower(MinPower);
}
diff --git a/src/lib/HWTIMER/ESP32_hwTimer.cpp b/src/lib/HWTIMER/ESP32_hwTimer.cpp
index b9844a8ef3..468b57c158 100644
--- a/src/lib/HWTIMER/ESP32_hwTimer.cpp
+++ b/src/lib/HWTIMER/ESP32_hwTimer.cpp
@@ -1,35 +1,34 @@
-
-#ifdef PLATFORM_ESP32
-#include "ESP32_hwTimer.h"
+#if defined(PLATFORM_ESP32)
+#include "hwTimer.h"
#include "logging.h"
-static hw_timer_t *timer = NULL;
-static portMUX_TYPE isrMutex = portMUX_INITIALIZER_UNLOCKED;
-
-void hwTimer::nullCallback(void) {}
+void (*hwTimer::callbackTick)() = nullptr;
+void (*hwTimer::callbackTock)() = nullptr;
-#if defined(TARGET_RX)
-void (*hwTimer::callbackTick)() = &nullCallback;
-#endif
-void (*hwTimer::callbackTock)() = &nullCallback;
-
-volatile uint32_t hwTimer::HWtimerInterval = TimerIntervalUSDefault;
volatile bool hwTimer::running = false;
-
-#if defined(TARGET_RX)
volatile bool hwTimer::isTick = false;
+
+volatile uint32_t hwTimer::HWtimerInterval = TimerIntervalUSDefault;
volatile int32_t hwTimer::PhaseShift = 0;
volatile int32_t hwTimer::FreqOffset = 0;
+// Internal implementation specific variables
+static hw_timer_t *timer = NULL;
+static portMUX_TYPE isrMutex = portMUX_INITIALIZER_UNLOCKED;
+
+#if defined(TARGET_RX)
#define HWTIMER_TICKS_PER_US 5
#else
#define HWTIMER_TICKS_PER_US 1
#endif
-void ICACHE_RAM_ATTR hwTimer::init()
+void ICACHE_RAM_ATTR hwTimer::init(void (*callbackTick)(), void (*callbackTock)())
{
+
if (!timer)
{
+ hwTimer::callbackTick = callbackTick;
+ hwTimer::callbackTock = callbackTock;
timer = timerBegin(0, (APB_CLK_FREQ / 1000000 / HWTIMER_TICKS_PER_US), true);
timerAttachInterrupt(timer, hwTimer::callback, true);
DBGLN("hwTimer Init");
@@ -69,41 +68,23 @@ void ICACHE_RAM_ATTR hwTimer::resume()
void ICACHE_RAM_ATTR hwTimer::updateInterval(uint32_t time)
{
+ // timer should not be running when updateInterval() is called
HWtimerInterval = time * HWTIMER_TICKS_PER_US;
-#if defined(TARGET_TX)
if (timer)
{
DBGLN("hwTimer interval: %d", time);
timerAlarmWrite(timer, HWtimerInterval, true);
}
-#endif
-}
-
-#if defined(TARGET_RX)
-void ICACHE_RAM_ATTR hwTimer::resetFreqOffset()
-{
- FreqOffset = 0;
-}
-
-void ICACHE_RAM_ATTR hwTimer::incFreqOffset()
-{
- FreqOffset++;
-}
-
-void ICACHE_RAM_ATTR hwTimer::decFreqOffset()
-{
- FreqOffset--;
}
void ICACHE_RAM_ATTR hwTimer::phaseShift(int32_t newPhaseShift)
{
- int32_t minVal = -(hwTimer::HWtimerInterval >> 2);
- int32_t maxVal = (hwTimer::HWtimerInterval >> 2);
+ int32_t minVal = -(HWtimerInterval >> 2);
+ int32_t maxVal = (HWtimerInterval >> 2);
// phase shift is in microseconds
- hwTimer::PhaseShift = constrain(newPhaseShift, minVal, maxVal) * HWTIMER_TICKS_PER_US;
+ PhaseShift = constrain(newPhaseShift, minVal, maxVal) * HWTIMER_TICKS_PER_US;
}
-#endif
void ICACHE_RAM_ATTR hwTimer::callback(void)
{
@@ -113,7 +94,7 @@ void ICACHE_RAM_ATTR hwTimer::callback(void)
#if defined(TARGET_TX)
callbackTock();
#else
- uint32_t NextInterval = (hwTimer::HWtimerInterval >> 1) + FreqOffset;
+ uint32_t NextInterval = (HWtimerInterval >> 1) + FreqOffset;
if (hwTimer::isTick)
{
timerAlarmWrite(timer, NextInterval, true);
@@ -121,9 +102,9 @@ void ICACHE_RAM_ATTR hwTimer::callback(void)
}
else
{
- NextInterval += hwTimer::PhaseShift;
+ NextInterval += PhaseShift;
timerAlarmWrite(timer, NextInterval, true);
- hwTimer::PhaseShift = 0;
+ PhaseShift = 0;
hwTimer::callbackTock();
}
hwTimer::isTick = !hwTimer::isTick;
diff --git a/src/lib/HWTIMER/ESP32_hwTimer.h b/src/lib/HWTIMER/ESP32_hwTimer.h
deleted file mode 100644
index a0aef44134..0000000000
--- a/src/lib/HWTIMER/ESP32_hwTimer.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#pragma once
-
-#include
-#include "targets.h"
-
-#if defined(TARGET_TX)
-#define TimerIntervalUSDefault 4000
-#else
-#define TimerIntervalUSDefault 20000
-#endif
-
-class hwTimer
-{
-public:
- static volatile uint32_t HWtimerInterval;
- static volatile bool running;
-#if defined(TARGET_RX)
- static volatile bool isTick;
- static volatile int32_t PhaseShift;
- static volatile int32_t FreqOffset;
- static uint32_t NextTimeout;
-#endif
-
- static void init();
- static void stop();
- static void resume();
- static void callback();
- static void updateInterval(uint32_t time = TimerIntervalUSDefault);
-#if defined(TARGET_RX)
- static void resetFreqOffset();
- static void incFreqOffset();
- static void decFreqOffset();
- static void phaseShift(int32_t newPhaseShift);
-#endif
-
- static void nullCallback(void);
-#if defined(TARGET_RX)
- static void (*callbackTick)();
-#endif
- static void (*callbackTock)();
-};
\ No newline at end of file
diff --git a/src/lib/HWTIMER/ESP8266_hwTimer.cpp b/src/lib/HWTIMER/ESP8266_hwTimer.cpp
index 243bcfb59c..b6113a8530 100644
--- a/src/lib/HWTIMER/ESP8266_hwTimer.cpp
+++ b/src/lib/HWTIMER/ESP8266_hwTimer.cpp
@@ -1,23 +1,26 @@
-#ifdef PLATFORM_ESP8266
-#include "ESP8266_hwTimer.h"
+#if defined(PLATFORM_ESP8266)
+#include "hwTimer.h"
void (*hwTimer::callbackTick)() = nullptr;
void (*hwTimer::callbackTock)() = nullptr;
-volatile uint32_t hwTimer::HWtimerInterval = TimerIntervalUSDefault;
-bool hwTimer::running = false;
-
+volatile bool hwTimer::running = false;
volatile bool hwTimer::isTick = false;
+
+volatile uint32_t hwTimer::HWtimerInterval = TimerIntervalUSDefault;
volatile int32_t hwTimer::PhaseShift = 0;
volatile int32_t hwTimer::FreqOffset = 0;
-uint32_t hwTimer::NextTimeout;
+
+// Internal implementation specific variables
+static uint32_t NextTimeout;
#define HWTIMER_TICKS_PER_US 5
#define HWTIMER_PRESCALER (clockCyclesPerMicrosecond() / HWTIMER_TICKS_PER_US)
-
-void hwTimer::init()
+void hwTimer::init(void (*callbackTick)(), void (*callbackTock)())
{
+ hwTimer::callbackTick = callbackTick;
+ hwTimer::callbackTock = callbackTock;
timer0_isr_init();
running = false;
}
@@ -40,8 +43,12 @@ void ICACHE_RAM_ATTR hwTimer::resume()
// The STM32 timer fires tock() ASAP after enabling, so mimic that behavior
// tock() should always be the first event to maintain consistency
isTick = false;
+#if defined(TARGET_TX)
+ NextTimeout = HWtimerInterval;
+#else
// Fire the timer in 2us to get it started close to now
NextTimeout = ESP.getCycleCount() + (2 * HWTIMER_TICKS_PER_US * HWTIMER_PRESCALER);
+#endif
timer0_write(NextTimeout);
interrupts();
@@ -52,53 +59,43 @@ void ICACHE_RAM_ATTR hwTimer::resume()
void hwTimer::updateInterval(uint32_t newTimerInterval)
{
// timer should not be running when updateInterval() is called
- hwTimer::HWtimerInterval = newTimerInterval * (HWTIMER_TICKS_PER_US * HWTIMER_PRESCALER);
-}
-
-void ICACHE_RAM_ATTR hwTimer::resetFreqOffset()
-{
- FreqOffset = 0;
-}
-
-void ICACHE_RAM_ATTR hwTimer::incFreqOffset()
-{
- FreqOffset++;
-}
-
-void ICACHE_RAM_ATTR hwTimer::decFreqOffset()
-{
- FreqOffset--;
+ HWtimerInterval = newTimerInterval * (HWTIMER_TICKS_PER_US * HWTIMER_PRESCALER);
}
void ICACHE_RAM_ATTR hwTimer::phaseShift(int32_t newPhaseShift)
{
- int32_t minVal = -(hwTimer::HWtimerInterval >> 2);
- int32_t maxVal = (hwTimer::HWtimerInterval >> 2);
+ int32_t minVal = -(HWtimerInterval >> 2);
+ int32_t maxVal = (HWtimerInterval >> 2);
// phase shift is in microseconds
- hwTimer::PhaseShift = constrain(newPhaseShift, minVal, maxVal) * (HWTIMER_TICKS_PER_US * HWTIMER_PRESCALER);
+ PhaseShift = constrain(newPhaseShift, minVal, maxVal) * (HWTIMER_TICKS_PER_US * HWTIMER_PRESCALER);
}
void ICACHE_RAM_ATTR hwTimer::callback()
{
- if (!running)
- {
- return;
- }
-
- NextTimeout += (hwTimer::HWtimerInterval >> 1) + (FreqOffset * HWTIMER_PRESCALER);
- if (hwTimer::isTick)
- {
- timer0_write(NextTimeout);
- if (callbackTick) callbackTick();
- }
- else
+ if (running)
{
- NextTimeout += hwTimer::PhaseShift;
+#if defined(TARGET_TX)
+ NextTimeout += HWtimerInterval;
timer0_write(NextTimeout);
- hwTimer::PhaseShift = 0;
- if (callbackTock) callbackTock();
+ callbackTock();
+#else
+ NextTimeout += (HWtimerInterval >> 1) + (FreqOffset * HWTIMER_PRESCALER);
+ if (hwTimer::isTick)
+ {
+ timer0_write(NextTimeout);
+ callbackTick();
+ }
+ else
+ {
+ NextTimeout += PhaseShift;
+ timer0_write(NextTimeout);
+ PhaseShift = 0;
+ callbackTock();
+ }
+ hwTimer::isTick = !hwTimer::isTick;
+#endif
}
- hwTimer::isTick = !hwTimer::isTick;
}
+
#endif
diff --git a/src/lib/HWTIMER/ESP8266_hwTimer.h b/src/lib/HWTIMER/ESP8266_hwTimer.h
deleted file mode 100644
index 426ea882eb..0000000000
--- a/src/lib/HWTIMER/ESP8266_hwTimer.h
+++ /dev/null
@@ -1,36 +0,0 @@
-#pragma once
-
-#include
-#include "targets.h"
-
-#define TimerIntervalUSDefault 20000
-
-extern "C"
-{
-#include "user_interface.h"
-}
-
-class hwTimer
-{
-public:
- static volatile uint32_t HWtimerInterval;
- static bool running;
- static volatile bool isTick;
- static volatile int32_t PhaseShift;
- static volatile int32_t FreqOffset;
- static uint32_t NextTimeout;
-
- static void init();
- static void stop();
- static void resume();
- static void callback();
- static void updateInterval(uint32_t newTimerInterval);
- static void resetFreqOffset();
- static void incFreqOffset();
- static void decFreqOffset();
- static void phaseShift(int32_t newPhaseShift);
-
- static void inline nullCallback(void);
- static void (*callbackTick)();
- static void (*callbackTock)();
-};
\ No newline at end of file
diff --git a/src/lib/HWTIMER/STM32_hwTimer.cpp b/src/lib/HWTIMER/STM32_hwTimer.cpp
index de9071c27e..14782341a0 100644
--- a/src/lib/HWTIMER/STM32_hwTimer.cpp
+++ b/src/lib/HWTIMER/STM32_hwTimer.cpp
@@ -1,30 +1,33 @@
#ifdef PLATFORM_STM32
-#include "STM32_hwTimer.h"
+#include "hwTimer.h"
-void inline hwTimer::nullCallback(void) {}
+void (*hwTimer::callbackTick)() = nullptr;
+void (*hwTimer::callbackTock)() = nullptr;
-void (*hwTimer::callbackTick)() = &nullCallback;
-void (*hwTimer::callbackTock)() = &nullCallback;
+volatile bool hwTimer::running = false;
+volatile bool hwTimer::isTick = false;
volatile uint32_t hwTimer::HWtimerInterval = TimerIntervalUSDefault;
-volatile bool hwTimer::isTick = false;
volatile int32_t hwTimer::PhaseShift = 0;
volatile int32_t hwTimer::FreqOffset = 0;
-volatile uint32_t hwTimer::PauseDuration = 0;
-bool hwTimer::running = false;
-bool hwTimer::alreadyInit = false;
+
+// Internal implementation specific variables
+static volatile uint32_t PauseDuration;
+static bool alreadyInit = false;
#if defined(TIM1)
-HardwareTimer(*hwTimer::MyTim) = new HardwareTimer(TIM1);
+static HardwareTimer *MyTim = new HardwareTimer(TIM1);
#else
// FM30_mini (STM32F373xC) no advanced timer but TIM2 is 32-bit general purpose
-HardwareTimer(*hwTimer::MyTim) = new HardwareTimer(TIM2);
+static HardwareTimer *MyTim = new HardwareTimer(TIM2);
#endif
-void hwTimer::init()
+void hwTimer::init(void (*callbackTick)(), void (*callbackTock)())
{
if (!alreadyInit)
{
+ hwTimer::callbackTick = callbackTick;
+ hwTimer::callbackTock = callbackTock;
MyTim->attachInterrupt(hwTimer::callback);
MyTim->setMode(1, TIMER_OUTPUT_COMPARE);
#if defined(TARGET_TX)
@@ -32,9 +35,9 @@ void hwTimer::init()
// to make Pause() work, the prescaler needs to be fixed to avoid
// having to ramp between prescalers
MyTim->setPrescaleFactor(MyTim->getTimerClkFreq() / 1000000); // 1us per tick
- MyTim->setOverflow(hwTimer::HWtimerInterval >> 1, TICK_FORMAT);
+ MyTim->setOverflow(HWtimerInterval >> 1, TICK_FORMAT);
#else
- MyTim->setOverflow(hwTimer::HWtimerInterval >> 1, MICROSEC_FORMAT); // 22(50Hz) to 3(500Hz) scaler
+ MyTim->setOverflow(HWtimerInterval >> 1, MICROSEC_FORMAT); // 22(50Hz) to 3(500Hz) scaler
#endif
MyTim->setPreloadEnable(false);
alreadyInit = true;
@@ -54,23 +57,26 @@ void hwTimer::stop()
* fire Duration - interval/2 after that
* 65535us max!
*/
+#if defined(TARGET_TX)
void hwTimer::pause(uint32_t duration)
{
-#if defined(TARGET_TX)
PauseDuration = duration;
- while(PauseDuration);
-#endif
+ while (PauseDuration)
+ ;
}
+#endif
void hwTimer::resume()
{
+#if defined(TARGET_RX)
isTick = false;
+#endif
running = true;
- #if defined(TARGET_TX)
- MyTim->setOverflow(hwTimer::HWtimerInterval >> 1, TICK_FORMAT);
- #else
- MyTim->setOverflow((hwTimer::HWtimerInterval >> 1), MICROSEC_FORMAT);
- #endif
+#if defined(TARGET_TX)
+ MyTim->setOverflow(HWtimerInterval >> 1, TICK_FORMAT);
+#else
+ MyTim->setOverflow((HWtimerInterval >> 1), MICROSEC_FORMAT);
+#endif
MyTim->setCount(0);
MyTim->resume();
MyTim->refresh(); // will trigger the interrupt immediately, but will update the prescaler shadow reg
@@ -79,29 +85,14 @@ void hwTimer::resume()
void hwTimer::updateInterval(uint32_t newTimerInterval)
{
// timer should not be running when updateInterval() is called
- hwTimer::HWtimerInterval = newTimerInterval;
-}
-
-void hwTimer::resetFreqOffset()
-{
- FreqOffset = 0;
-}
-
-void hwTimer::incFreqOffset()
-{
- FreqOffset++;
-}
-
-void hwTimer::decFreqOffset()
-{
- FreqOffset--;
+ HWtimerInterval = newTimerInterval;
}
void hwTimer::phaseShift(int32_t newPhaseShift)
{
- int32_t minVal = -(hwTimer::HWtimerInterval >> 2);
- int32_t maxVal = (hwTimer::HWtimerInterval >> 2);
- hwTimer::PhaseShift = constrain(newPhaseShift, minVal, maxVal);
+ int32_t minVal = -(HWtimerInterval >> 2);
+ int32_t maxVal = (HWtimerInterval >> 2);
+ PhaseShift = constrain(newPhaseShift, minVal, maxVal);
}
void hwTimer::callback(void)
@@ -111,17 +102,16 @@ void hwTimer::callback(void)
#if defined(TARGET_TX)
if (PauseDuration)
{
- MyTim->setOverflow(PauseDuration - (hwTimer::HWtimerInterval >> 1), TICK_FORMAT);
+ MyTim->setOverflow(PauseDuration - (HWtimerInterval >> 1), TICK_FORMAT);
PauseDuration = 0;
- // No tick callback
}
else
{
- MyTim->setOverflow(hwTimer::HWtimerInterval >> 1, TICK_FORMAT);
- hwTimer::callbackTick();
+ MyTim->setOverflow(HWtimerInterval >> 1, TICK_FORMAT);
}
+ // No tick callback
#else
- MyTim->setOverflow((hwTimer::HWtimerInterval >> 1), MICROSEC_FORMAT);
+ MyTim->setOverflow((HWtimerInterval >> 1), MICROSEC_FORMAT);
uint32_t adjustedInterval = MyTim->getOverflow(TICK_FORMAT) + FreqOffset;
MyTim->setOverflow(adjustedInterval, TICK_FORMAT);
hwTimer::callbackTick();
@@ -130,13 +120,13 @@ void hwTimer::callback(void)
else
{
#if defined(TARGET_TX)
- MyTim->setOverflow(hwTimer::HWtimerInterval >> 1, TICK_FORMAT);
+ MyTim->setOverflow(HWtimerInterval >> 1, TICK_FORMAT);
hwTimer::callbackTock();
#else
- MyTim->setOverflow((hwTimer::HWtimerInterval >> 1) + hwTimer::PhaseShift, MICROSEC_FORMAT);
+ MyTim->setOverflow((HWtimerInterval >> 1) + PhaseShift, MICROSEC_FORMAT);
uint32_t adjustedInterval = MyTim->getOverflow(TICK_FORMAT) + FreqOffset;
MyTim->setOverflow(adjustedInterval, TICK_FORMAT);
- hwTimer::PhaseShift = 0;
+ PhaseShift = 0;
hwTimer::callbackTock();
#endif
}
diff --git a/src/lib/HWTIMER/STM32_hwTimer.h b/src/lib/HWTIMER/STM32_hwTimer.h
deleted file mode 100644
index 3bdd2aa397..0000000000
--- a/src/lib/HWTIMER/STM32_hwTimer.h
+++ /dev/null
@@ -1,36 +0,0 @@
-#pragma once
-
-#include
-#include "targets.h"
-
-#define TimerIntervalUSDefault 20000
-
-
-class hwTimer
-{
-public:
- static HardwareTimer *MyTim;
-
- static volatile uint32_t HWtimerInterval;
- static volatile bool isTick;
- static volatile int32_t PhaseShift;
- static volatile int32_t FreqOffset;
- static volatile uint32_t PauseDuration;
- static bool running;
- static bool alreadyInit;
-
- static void init();
- static void stop();
- static void pause(uint32_t duration);
- static void resume();
- static void callback(void);
- static void updateInterval(uint32_t newTimerInterval);
- static void resetFreqOffset();
- static void incFreqOffset();
- static void decFreqOffset();
- static void phaseShift(int32_t newPhaseShift);
-
- static void inline nullCallback(void);
- static void (*callbackTick)();
- static void (*callbackTock)();
-};
\ No newline at end of file
diff --git a/src/lib/HWTIMER/hwTimer.h b/src/lib/HWTIMER/hwTimer.h
index 4063ae2ced..80a4480869 100644
--- a/src/lib/HWTIMER/hwTimer.h
+++ b/src/lib/HWTIMER/hwTimer.h
@@ -1,12 +1,119 @@
+#pragma once
-#ifdef PLATFORM_ESP8266
-#include "ESP8266_hwTimer.h"
-#endif
+#include "targets.h"
+#include
-#ifdef PLATFORM_ESP32
-#include "ESP32_hwTimer.h"
+#if defined(TARGET_TX)
+#define TimerIntervalUSDefault 4000
+#else
+#define TimerIntervalUSDefault 20000
#endif
-#ifdef PLATFORM_STM32
-#include "STM32_hwTimer.h"
+/**
+ * @brief Hardware abstraction for the hardware timer to provide precise timing
+ *
+ * The timer provides callback for "tick" and "tock" alternating at half the update
+ * interval each.
+ *
+ * The timer includes a built-in frequency offset to adjust for differences in actual
+ * timing rates between 2 timers. The offset can be incremented, decremented or reset
+ * to 0.
+ *
+ * The `phaseShift` function can be used to provide a one-time adjustment to shift the
+ * phase of the timer so two timers can be aligned on their tock phase.
+ */
+class hwTimer
+{
+public:
+ /**
+ * @brief Initialise the timer with the specified tick/tock callback functions.
+ *
+ * Only the tock callback is used for transmitter targets.
+ *
+ * The timer initialised in the stopped state and must be started with resume().
+ */
+ static void init(void (*callbackTick)(), void (*callbackTock)());
+
+ /**
+ * @brief Stop the timer.
+ *
+ * The timer is stopped and no more callbacks are performed.
+ */
+ static void stop();
+
+ /**
+ * @brief Resume the timer.
+ *
+ * The timer is started from this instant, callbacks will be called.
+ * The timer fires a tock callback immediately after it is resumed.
+ */
+ static void resume();
+
+ /**
+ * @brief Change the interval between callbacks.
+ * The time between tick and tock is half the provided interval.
+ * The timer should not be running when updateInterval() is called.
+ *
+ * @param time in microseconds for a full tick/tock.
+ */
+ static void updateInterval(uint32_t time = TimerIntervalUSDefault);
+ /**
+ * @brief Reset the frequency offset to zero microseconds
+ */
+ static ICACHE_RAM_ATTR void inline resetFreqOffset() { FreqOffset = 0; }
+
+ /**
+ * @brief Increment the frequency offset by one microsecond
+ */
+ static ICACHE_RAM_ATTR void inline incFreqOffset() { FreqOffset++; }
+
+
+ /**
+ * @brief Decrement the frequency offset by one microsecond
+ */
+ static ICACHE_RAM_ATTR void inline decFreqOffset() { FreqOffset--; }
+
+ /**
+ * @brief Get the frequency offset
+ */
+ static ICACHE_RAM_ATTR int32_t inline getFreqOffset() { return FreqOffset; }
+
+ /**
+ * @brief Provides a coarse one time adjustment to the frequency to
+ * align the phase of two timers.
+ *
+ * The phase shift is a delay that is added to the timer after the
+ * next tock callback, delaying the tick after by phaseShift microseonds.
+ *
+ * The maximum phase shift is 1/4 of the update interval.
+ *
+ * @param newPhaseShift time in microseconds to delay the timer.
+ */
+ static void phaseShift(int32_t newPhaseShift);
+
+#if defined(PLATFORM_STM32)
+ /**
+ * @brief Schedule a pause of the specified duration, in microseconds.
+ *
+ * Will pause until the TICK interrupt, then the next timer will
+ * fire after "duration - interval/2" after that
+ * 65535us max!
+ *
+ * @param duration in microseconds
+ */
+ static void pause(uint32_t duration);
#endif
+
+ static volatile bool running;
+ static volatile bool isTick;
+
+private:
+ static void callback();
+
+ static void (*callbackTick)();
+ static void (*callbackTock)();
+
+ static volatile uint32_t HWtimerInterval;
+ static volatile int32_t PhaseShift;
+ static volatile int32_t FreqOffset;
+};
diff --git a/src/lib/Handset/AutoDetect.cpp b/src/lib/Handset/AutoDetect.cpp
new file mode 100644
index 0000000000..9ae32d035a
--- /dev/null
+++ b/src/lib/Handset/AutoDetect.cpp
@@ -0,0 +1,103 @@
+#include "targets.h"
+
+#if defined(PLATFORM_ESP32) && defined(TARGET_TX)
+
+#include "AutoDetect.h"
+#include "CRSFHandset.h"
+#include "PPMHandset.h"
+#include "logging.h"
+
+#include
+
+constexpr auto RMT_TICKS_PER_US = 10;
+
+void AutoDetect::Begin()
+{
+ constexpr auto divisor = 80 / RMT_TICKS_PER_US;
+
+ rmt_config_t rmt_rx_config = RMT_DEFAULT_CONFIG_RX(static_cast(GPIO_PIN_RCSIGNAL_RX), PPM_RMT_CHANNEL);
+ rmt_rx_config.clk_div = divisor;
+ rmt_rx_config.rx_config.filter_ticks_thresh = 1;
+ rmt_rx_config.rx_config.idle_threshold = 100;
+ rmt_config(&rmt_rx_config);
+ rmt_driver_install(PPM_RMT_CHANNEL, 1000, 0);
+
+ rmt_get_ringbuf_handle(PPM_RMT_CHANNEL, &rb);
+ rmt_rx_start(PPM_RMT_CHANNEL, true);
+ input_detect = 0;
+}
+
+void AutoDetect::End()
+{
+ rmt_driver_uninstall(PPM_RMT_CHANNEL);
+}
+
+bool AutoDetect::IsArmed()
+{
+ return false;
+}
+
+void AutoDetect::migrateTo(Handset *that) const
+{
+ that->setRCDataCallback(RCdataCallback);
+ that->registerParameterUpdateCallback(RecvParameterUpdate);
+ that->registerCallbacks(connected, disconnected, RecvModelUpdate, OnBindingCommand);
+ that->Begin();
+ that->setPacketInterval(RequestedRCpacketInterval);
+ delete this;
+ handset = that;
+}
+
+void AutoDetect::startPPM() const
+{
+ migrateTo(new PPMHandset());
+}
+
+void AutoDetect::startCRSF() const
+{
+ migrateTo(new CRSFHandset());
+}
+
+void AutoDetect::handleInput()
+{
+ size_t length = 0;
+ const auto now = millis();
+
+ const auto items = static_cast(xRingbufferReceive(rb, &length, 0));
+ if (items)
+ {
+ vRingbufferReturnItem(rb, static_cast(items));
+ lastDetect = now;
+ length /= 4; // one RMT = 4 Bytes
+ if (length == 0)
+ {
+ input_detect++;
+ if (input_detect > 100)
+ {
+ DBGLN("PPM signal detected");
+ rmt_driver_uninstall(PPM_RMT_CHANNEL);
+ startPPM();
+ }
+ }
+ else
+ {
+ input_detect--;
+ if (input_detect < -100)
+ {
+ DBGLN("Serial signal detected");
+ rmt_driver_uninstall(PPM_RMT_CHANNEL);
+ startCRSF();
+ }
+ }
+ }
+ else
+ {
+ if (now - 1000 > lastDetect && input_detect != 0)
+ {
+ DBGLN("No signal detected");
+ input_detect = 0;
+ }
+ }
+}
+
+#endif
diff --git a/src/lib/Handset/AutoDetect.h b/src/lib/Handset/AutoDetect.h
new file mode 100644
index 0000000000..f1f58133ad
--- /dev/null
+++ b/src/lib/Handset/AutoDetect.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "handset.h"
+
+#include
+
+class AutoDetect final : public Handset
+{
+public:
+ void Begin() override;
+ void End() override;
+ bool IsArmed() override;
+ void handleInput() override;
+
+private:
+ void migrateTo(Handset *that) const;
+ void startPPM() const;
+ void startCRSF() const;
+
+ int input_detect = 0;
+ RingbufHandle_t rb = nullptr;
+ uint32_t lastDetect = 0;
+};
\ No newline at end of file
diff --git a/src/lib/Handset/CRSF.cpp b/src/lib/Handset/CRSF.cpp
new file mode 100644
index 0000000000..8cdaac037b
--- /dev/null
+++ b/src/lib/Handset/CRSF.cpp
@@ -0,0 +1,232 @@
+#include "CRSF.h"
+
+#include "common.h"
+#include "FIFO.h"
+
+volatile crsfPayloadLinkstatistics_s CRSF::LinkStatistics;
+GENERIC_CRC8 crsf_crc(CRSF_CRC_POLY);
+
+uint8_t CRSF::MspData[ELRS_MSP_BUFFER] = {0};
+uint8_t CRSF::MspDataLength = 0;
+
+static const auto MSP_SERIAL_OUT_FIFO_SIZE = 256U;
+static FIFO MspWriteFIFO;
+
+
+/***
+ * @brief: Convert `version` (string) to a integer version representation
+ * e.g. "2.2.15 ISM24G" => 0x0002020f
+ * Assumes all version fields are < 256, the number portion
+ * MUST be followed by a space to correctly be parsed
+ ***/
+uint32_t CRSF::VersionStrToU32(const char *verStr)
+{
+ uint32_t retVal = 0;
+#if !defined(FORCE_NO_DEVICE_VERSION)
+ uint8_t accumulator = 0;
+ char c;
+ bool trailing_data = false;
+ while ((c = *verStr))
+ {
+ ++verStr;
+ // A decimal indicates moving to a new version field
+ if (c == '.')
+ {
+ retVal = (retVal << 8) | accumulator;
+ accumulator = 0;
+ trailing_data = false;
+ }
+ // Else if this is a number add it up
+ else if (c >= '0' && c <= '9')
+ {
+ accumulator = (accumulator * 10) + (c - '0');
+ trailing_data = true;
+ }
+ // Anything except [0-9.] ends the parsing
+ else
+ {
+ break;
+ }
+ }
+ if (trailing_data)
+ {
+ retVal = (retVal << 8) | accumulator;
+ }
+ // If the version ID is < 1.0.0, we could not parse it,
+ // just use the OTA version as the major version number
+ if (retVal < 0x010000)
+ {
+ retVal = OTA_VERSION_ID << 16;
+ }
+#endif
+ return retVal;
+}
+
+void CRSF::GetDeviceInformation(uint8_t *frame, uint8_t fieldCount)
+{
+ const uint8_t size = strlen(device_name)+1;
+ auto *device = (deviceInformationPacket_t *)(frame + sizeof(crsf_ext_header_t) + size);
+ // Packet starts with device name
+ memcpy(frame + sizeof(crsf_ext_header_t), device_name, size);
+ // Followed by the device
+ device->serialNo = htobe32(0x454C5253); // ['E', 'L', 'R', 'S'], seen [0x00, 0x0a, 0xe7, 0xc6] // "Serial 177-714694" (value is 714694)
+ device->hardwareVer = 0; // unused currently by us, seen [ 0x00, 0x0b, 0x10, 0x01 ] // "Hardware: V 1.01" / "Bootloader: V 3.06"
+ device->softwareVer = htobe32(VersionStrToU32(version)); // seen [ 0x00, 0x00, 0x05, 0x0f ] // "Firmware: V 5.15"
+ device->fieldCnt = fieldCount;
+ device->parameterVersion = 0;
+}
+
+void CRSF::SetMspV2Request(uint8_t *frame, uint16_t function, uint8_t *payload, uint8_t payloadLength)
+{
+ auto *packet = (uint8_t *)(frame + sizeof(crsf_ext_header_t));
+ packet[0] = 0x50; // no error, version 2, beginning of the frame, first frame (0)
+ packet[1] = 0; // flags
+ packet[2] = function & 0xFF;
+ packet[3] = (function >> 8) & 0xFF;
+ packet[4] = payloadLength & 0xFF;
+ packet[5] = (payloadLength >> 8) & 0xFF;
+ memcpy(packet + 6, payload, payloadLength);
+ packet[6 + payloadLength] = CalcCRCMsp(packet + 1, payloadLength + 5); // crc = flags + function + length + payload
+}
+
+void CRSF::SetHeaderAndCrc(uint8_t *frame, crsf_frame_type_e frameType, uint8_t frameSize, crsf_addr_e destAddr)
+{
+ auto *header = (crsf_header_t *)frame;
+ header->device_addr = destAddr;
+ header->frame_size = frameSize;
+ header->type = frameType;
+
+ uint8_t crc = crsf_crc.calc(&frame[CRSF_FRAME_NOT_COUNTED_BYTES], frameSize - 1, 0);
+ frame[frameSize + CRSF_FRAME_NOT_COUNTED_BYTES - 1] = crc;
+}
+
+void CRSF::SetExtendedHeaderAndCrc(uint8_t *frame, crsf_frame_type_e frameType, uint8_t frameSize, crsf_addr_e senderAddr, crsf_addr_e destAddr)
+{
+ auto *header = (crsf_ext_header_t *)frame;
+ header->dest_addr = destAddr;
+ header->orig_addr = senderAddr;
+ SetHeaderAndCrc(frame, frameType, frameSize, destAddr);
+}
+
+
+void CRSF::GetMspMessage(uint8_t **data, uint8_t *len)
+{
+ *len = MspDataLength;
+ *data = (MspDataLength > 0) ? MspData : nullptr;
+}
+
+void CRSF::ResetMspQueue()
+{
+ MspWriteFIFO.flush();
+ MspDataLength = 0;
+ memset(MspData, 0, ELRS_MSP_BUFFER);
+}
+
+void CRSF::UnlockMspMessage()
+{
+ // current msp message is sent so restore next buffered write
+ if (MspWriteFIFO.size() > 0)
+ {
+ MspWriteFIFO.lock();
+ MspDataLength = MspWriteFIFO.pop();
+ MspWriteFIFO.popBytes(MspData, MspDataLength);
+ MspWriteFIFO.unlock();
+ }
+ else
+ {
+ // no msp message is ready to send currently
+ MspDataLength = 0;
+ memset(MspData, 0, ELRS_MSP_BUFFER);
+ }
+}
+
+void ICACHE_RAM_ATTR CRSF::AddMspMessage(mspPacket_t* packet, uint8_t destination)
+{
+ if (packet->payloadSize > ENCAPSULATED_MSP_MAX_PAYLOAD_SIZE)
+ {
+ return;
+ }
+
+ const uint8_t totalBufferLen = packet->payloadSize + ENCAPSULATED_MSP_HEADER_CRC_LEN + CRSF_FRAME_LENGTH_EXT_TYPE_CRC + CRSF_FRAME_NOT_COUNTED_BYTES;
+ uint8_t outBuffer[ENCAPSULATED_MSP_MAX_FRAME_LEN + CRSF_FRAME_LENGTH_EXT_TYPE_CRC + CRSF_FRAME_NOT_COUNTED_BYTES];
+
+ // CRSF extended frame header
+ outBuffer[0] = CRSF_ADDRESS_BROADCAST; // address
+ outBuffer[1] = packet->payloadSize + ENCAPSULATED_MSP_HEADER_CRC_LEN + CRSF_FRAME_LENGTH_EXT_TYPE_CRC; // length
+ outBuffer[2] = CRSF_FRAMETYPE_MSP_WRITE; // packet type
+ outBuffer[3] = destination; // destination
+ outBuffer[4] = CRSF_ADDRESS_RADIO_TRANSMITTER; // origin
+
+ // Encapsulated MSP payload
+ outBuffer[5] = 0x30; // header
+ outBuffer[6] = packet->payloadSize; // mspPayloadSize
+ outBuffer[7] = packet->function; // packet->cmd
+ for (uint16_t i = 0; i < packet->payloadSize; ++i)
+ {
+ // copy packet payload into outBuffer
+ outBuffer[8 + i] = packet->payload[i];
+ }
+ // Encapsulated MSP crc
+ outBuffer[totalBufferLen - 2] = CalcCRCMsp(&outBuffer[6], packet->payloadSize + 2);
+
+ // CRSF frame crc
+ outBuffer[totalBufferLen - 1] = crsf_crc.calc(&outBuffer[2], packet->payloadSize + ENCAPSULATED_MSP_HEADER_CRC_LEN + CRSF_FRAME_LENGTH_EXT_TYPE_CRC - 1);
+ AddMspMessage(totalBufferLen, outBuffer);
+}
+
+void ICACHE_RAM_ATTR CRSF::AddMspMessage(const uint8_t length, uint8_t* data)
+{
+ if (length > ELRS_MSP_BUFFER)
+ {
+ return;
+ }
+
+ // store next msp message
+ if (MspDataLength == 0)
+ {
+ for (uint8_t i = 0; i < length; i++)
+ {
+ MspData[i] = data[i];
+ }
+ MspDataLength = length;
+ }
+ // store all write requests since an update does send multiple writes
+ else
+ {
+ MspWriteFIFO.lock();
+ if (MspWriteFIFO.ensure(length + 1))
+ {
+ MspWriteFIFO.push(length);
+ MspWriteFIFO.pushBytes((const uint8_t *)data, length);
+ }
+ MspWriteFIFO.unlock();
+ }
+}
+
+#if defined(CRSF_RX_MODULE)
+
+bool CRSF::HasUpdatedUplinkPower = false;
+
+/***
+ * @brief: Call this when new uplinkPower from the TX is availble from OTA instead of setting directly
+ */
+void CRSF::updateUplinkPower(uint8_t uplinkPower)
+{
+ if (uplinkPower != LinkStatistics.uplink_TX_Power)
+ {
+ LinkStatistics.uplink_TX_Power = uplinkPower;
+ HasUpdatedUplinkPower = true;
+ }
+}
+
+/***
+ * @brief: Returns true if HasUpdatedUplinkPower and clears the flag
+ */
+bool CRSF::clearUpdatedUplinkPower()
+{
+ bool retVal = HasUpdatedUplinkPower;
+ HasUpdatedUplinkPower = false;
+ return retVal;
+}
+
+#endif // CRSF_RX_MODULE
diff --git a/src/lib/Handset/CRSF.h b/src/lib/Handset/CRSF.h
new file mode 100644
index 0000000000..01eb208be9
--- /dev/null
+++ b/src/lib/Handset/CRSF.h
@@ -0,0 +1,42 @@
+#ifndef H_CRSF
+#define H_CRSF
+
+#include "crsf_protocol.h"
+#include "telemetry_protocol.h"
+#include "msp.h"
+#include "msptypes.h"
+
+class CRSF
+{
+public:
+ static volatile crsfPayloadLinkstatistics_s LinkStatistics; // Link Statistics Stored as Struct
+
+ static void GetMspMessage(uint8_t **data, uint8_t *len);
+ static void UnlockMspMessage();
+ static void AddMspMessage(uint8_t length, uint8_t *data);
+ static void AddMspMessage(mspPacket_t *packet, uint8_t destination);
+ static void ResetMspQueue();
+
+ static void GetDeviceInformation(uint8_t *frame, uint8_t fieldCount);
+ static void SetMspV2Request(uint8_t *frame, uint16_t function, uint8_t *payload, uint8_t payloadLength);
+ static void SetHeaderAndCrc(uint8_t *frame, crsf_frame_type_e frameType, uint8_t frameSize, crsf_addr_e destAddr);
+ static void SetExtendedHeaderAndCrc(uint8_t *frame, crsf_frame_type_e frameType, uint8_t frameSize, crsf_addr_e senderAddr, crsf_addr_e destAddr);
+ static uint32_t VersionStrToU32(const char *verStr);
+
+#if defined(CRSF_RX_MODULE)
+public:
+ static void updateUplinkPower(uint8_t uplinkPower);
+ static bool clearUpdatedUplinkPower();
+
+private:
+ static bool HasUpdatedUplinkPower;
+#endif
+
+private:
+ static uint8_t MspData[ELRS_MSP_BUFFER];
+ static uint8_t MspDataLength;
+};
+
+extern GENERIC_CRC8 crsf_crc;
+
+#endif
\ No newline at end of file
diff --git a/src/lib/CRSF/CRSF.cpp b/src/lib/Handset/CRSFHandset.cpp
similarity index 50%
rename from src/lib/CRSF/CRSF.cpp
rename to src/lib/Handset/CRSFHandset.cpp
index bf781c3041..e1c891ca0a 100644
--- a/src/lib/CRSF/CRSF.cpp
+++ b/src/lib/Handset/CRSFHandset.cpp
@@ -1,22 +1,23 @@
#include "CRSF.h"
-#include "device.h"
+#include "CRSFHandset.h"
#include "FIFO.h"
#include "logging.h"
#include "helpers.h"
-#if defined(CRSF_TX_MODULE)
+#if defined(CRSF_TX_MODULE) && !defined(UNIT_TEST)
+#include "device.h"
+
#if defined(PLATFORM_ESP32)
+#include
#include
// UART0 is used since for DupleTX we can connect directly through IO_MUX and not the Matrix
// for better performance, and on other targets (mostly using pin 13), it always uses Matrix
-HardwareSerial CRSF::Port(0);
-portMUX_TYPE FIFOmux = portMUX_INITIALIZER_UNLOCKED;
-
+HardwareSerial CRSFHandset::Port(0);
RTC_DATA_ATTR int rtcModelId = 0;
#elif defined(PLATFORM_ESP8266)
-HardwareSerial CRSF::Port(0);
-#elif CRSF_TX_MODULE_STM32
-HardwareSerial CRSF::Port(GPIO_PIN_RCSIGNAL_RX, GPIO_PIN_RCSIGNAL_TX);
+HardwareSerial CRSFHandset::Port(0);
+#elif defined(PLATFORM_STM32)
+HardwareSerial CRSFHandset::Port(GPIO_PIN_RCSIGNAL_RX, GPIO_PIN_RCSIGNAL_TX);
#if defined(STM32F3) || defined(STM32F3xx)
#include "stm32f3xx_hal.h"
#include "stm32f3xx_hal_gpio.h"
@@ -25,93 +26,53 @@ HardwareSerial CRSF::Port(GPIO_PIN_RCSIGNAL_RX, GPIO_PIN_RCSIGNAL_TX);
#include "stm32f1xx_hal_gpio.h"
#endif
#elif defined(TARGET_NATIVE)
-HardwareSerial CRSF::Port = Serial;
-#endif
+HardwareSerial CRSFHandset::Port = Serial;
#endif
-GENERIC_CRC8 crsf_crc(CRSF_CRC_POLY);
+static constexpr int HANDSET_TELEMETRY_FIFO_SIZE = 128; // this is the smallest telemetry FIFO size in ETX with CRSF defined
/// Out FIFO to buffer messages///
-static FIFO SerialOutFIFO;
-
-inBuffer_U CRSF::inBuffer;
+static constexpr auto CRSF_SERIAL_OUT_FIFO_SIZE = 256U;
+static FIFO SerialOutFIFO;
-volatile crsfPayloadLinkstatistics_s CRSF::LinkStatistics;
-
-#if CRSF_TX_MODULE
-#define HANDSET_TELEMETRY_FIFO_SIZE 128 // this is the smallest telemetry FIFO size in ETX with CRSF defined
-
-Stream *CRSF::PortSecondary;
-static FIFO MspWriteFIFO;
-
-void (*CRSF::disconnected)() = nullptr; // called when CRSF stream is lost
-void (*CRSF::connected)() = nullptr; // called when CRSF stream is regained
-
-void (*CRSF::RecvParameterUpdate)(uint8_t type, uint8_t index, uint8_t arg) = nullptr; // called when recv parameter update req, ie from LUA
-void (*CRSF::RecvModelUpdate)() = nullptr; // called when model id cahnges, ie command from Radio
-void (*CRSF::RCdataCallback)() = nullptr; // called when there is new RC data
+Stream *CRSFHandset::PortSecondary;
/// UART Handling ///
-uint8_t CRSF::SerialInPacketLen = 0; // length of the CRSF packet as measured
-uint8_t CRSF::SerialInPacketPtr = 0; // index where we are reading/writing
-bool CRSF::CRSFframeActive = false; //since we get a copy of the serial data use this flag to know when to ignore it
-
-uint32_t CRSF::GoodPktsCountResult = 0;
-uint32_t CRSF::BadPktsCountResult = 0;
+uint32_t CRSFHandset::GoodPktsCountResult = 0;
+uint32_t CRSFHandset::BadPktsCountResult = 0;
-uint8_t CRSF::modelId = 0;
-bool CRSF::ForwardDevicePings = false;
-bool CRSF::elrsLUAmode = false;
+uint8_t CRSFHandset::modelId = 0;
+bool CRSFHandset::ForwardDevicePings = false;
+bool CRSFHandset::elrsLUAmode = false;
/// OpenTX mixer sync ///
-uint32_t CRSF::OpenTXsyncLastSent = 0;
-int32_t CRSF::RequestedRCpacketInterval = 5000; // default to 200hz as per 'normal'
-volatile uint32_t CRSF::RCdataLastRecv = 0;
-volatile int32_t CRSF::OpenTXsyncOffset = 0;
-volatile int32_t CRSF::OpenTXsyncWindow = 0;
-volatile int32_t CRSF::OpenTXsyncWindowSize = 0;
-volatile uint32_t CRSF::dataLastRecv = 0;
-bool CRSF::OpentxSyncActive = true;
-uint32_t CRSF::OpenTXsyncOffsetSafeMargin = 1000; // 100us
+static const int32_t OpenTXsyncPacketInterval = 200; // in ms
+static const int32_t OpenTXsyncOffsetSafeMargin = 1000; // 100us
/// UART Handling ///
-uint32_t CRSF::GoodPktsCount = 0;
-uint32_t CRSF::BadPktsCount = 0;
-uint32_t CRSF::UARTwdtLastChecked;
-
-uint8_t CRSF::CRSFoutBuffer[CRSF_MAX_PACKET_LEN] = {0};
-uint8_t CRSF::maxPacketBytes = CRSF_MAX_PACKET_LEN;
-uint8_t CRSF::maxPeriodBytes = CRSF_MAX_PACKET_LEN;
-uint32_t CRSF::TxToHandsetBauds[] = {400000, 115200, 5250000, 3750000, 1870000, 921600, 2250000};
-uint8_t CRSF::UARTcurrentBaudIdx = 0;
-uint32_t CRSF::UARTrequestedBaud = 5250000;
-#if defined(PLATFORM_ESP32)
-bool CRSF::UARTinverted = false;
-#endif
-
-bool CRSF::CRSFstate = false;
+static const int32_t TxToHandsetBauds[] = {400000, 115200, 5250000, 3750000, 1870000, 921600, 2250000};
+uint8_t CRSFHandset::UARTcurrentBaudIdx = 6; // only used for baud-cycling, initialized to the end so the next one we try is the first in the list
+uint32_t CRSFHandset::UARTrequestedBaud = 5250000;
// for the UART wdt, every 1000ms we change bauds when connect is lost
-#define UARTwdtInterval 1000
-
-uint8_t CRSF::MspData[ELRS_MSP_BUFFER] = {0};
-uint8_t CRSF::MspDataLength = 0;
-#endif // CRSF_TX_MODULE
+static const int UARTwdtInterval = 1000;
-void CRSF::Begin()
+void CRSFHandset::Begin()
{
DBGLN("About to start CRSF task...");
-#if CRSF_TX_MODULE
UARTwdtLastChecked = millis() + UARTwdtInterval; // allows a delay before the first time the UARTwdt() function is called
#if defined(PLATFORM_ESP32)
portDISABLE_INTERRUPTS();
- UARTinverted = firmwareOptions.uart_inverted;
- CRSF::Port.begin(TxToHandsetBauds[UARTcurrentBaudIdx], SERIAL_8N1,
+ if (GPIO_PIN_RCSIGNAL_RX != GPIO_PIN_RCSIGNAL_TX)
+ {
+ UARTinverted = false; // on a full UART we will start uninverted checking first
+ }
+ CRSFHandset::Port.begin(UARTrequestedBaud, SERIAL_8N1,
GPIO_PIN_RCSIGNAL_RX, GPIO_PIN_RCSIGNAL_TX,
false, 500);
- CRSF::duplex_set_RX();
+ CRSFHandset::duplex_set_RX();
portENABLE_INTERRUPTS();
flush_port_input();
if (esp_reset_reason() != ESP_RST_POWERON)
@@ -121,7 +82,7 @@ void CRSF::Begin()
}
#elif defined(PLATFORM_ESP8266)
// Uses default UART pins
- CRSF::Port.begin(TxToHandsetBauds[UARTcurrentBaudIdx]);
+ CRSFHandset::Port.begin(UARTrequestedBaud);
// Invert RX/TX (not done, connection is full duplex uninverted)
//USC0(UART0) |= BIT(UCRXI) | BIT(UCTXI);
// No log message because this is our only UART
@@ -129,108 +90,78 @@ void CRSF::Begin()
#elif defined(PLATFORM_STM32)
DBGLN("Start STM32 R9M TX CRSF UART");
- CRSF::Port.setTx(GPIO_PIN_RCSIGNAL_TX);
- CRSF::Port.setRx(GPIO_PIN_RCSIGNAL_RX);
+ CRSFHandset::Port.setTx(GPIO_PIN_RCSIGNAL_TX);
+ CRSFHandset::Port.setRx(GPIO_PIN_RCSIGNAL_RX);
#if defined(GPIO_PIN_BUFFER_OE) && (GPIO_PIN_BUFFER_OE != UNDEF_PIN)
pinMode(GPIO_PIN_BUFFER_OE, OUTPUT);
digitalWrite(GPIO_PIN_BUFFER_OE, LOW ^ GPIO_PIN_BUFFER_OE_INVERTED); // RX mode default
#elif (GPIO_PIN_RCSIGNAL_TX == GPIO_PIN_RCSIGNAL_RX)
- CRSF::Port.setHalfDuplex();
+ CRSFHandset::Port.setHalfDuplex();
#endif
- CRSF::Port.begin(TxToHandsetBauds[UARTcurrentBaudIdx]);
+ CRSFHandset::Port.begin(UARTrequestedBaud);
#if defined(TARGET_TX_GHOST)
USART1->CR1 &= ~USART_CR1_UE;
USART1->CR3 |= USART_CR3_HDSEL;
USART1->CR2 |= USART_CR2_RXINV | USART_CR2_TXINV | USART_CR2_SWAP; //inverted/swapped
USART1->CR1 |= USART_CR1_UE;
-#endif
-#if defined(TARGET_TX_FM30_MINI)
+#elif defined(TARGET_TX_FM30_MINI)
LL_GPIO_SetPinPull(GPIOA, GPIO_PIN_2, LL_GPIO_PULL_DOWN); // default is PULLUP
USART2->CR1 &= ~USART_CR1_UE;
USART2->CR2 |= USART_CR2_RXINV | USART_CR2_TXINV; //inverted
USART2->CR1 |= USART_CR1_UE;
#endif
DBGLN("STM32 CRSF UART LISTEN TASK STARTED");
- CRSF::Port.flush();
+ CRSFHandset::Port.flush();
flush_port_input();
#endif
-
-#endif // CRSF_TX_MODULE
-
- //The master module requires that the serial communication is bidirectional
- //The Reciever uses seperate rx and tx pins
}
-void CRSF::End()
+void CRSFHandset::End()
{
-#if CRSF_TX_MODULE
uint32_t startTime = millis();
while (SerialOutFIFO.peek() > 0)
{
- handleUARTin();
+ handleInput();
if (millis() - startTime > 1000)
{
break;
}
}
- //CRSF::Port.end(); // don't call seria.end(), it causes some sort of issue with the 900mhz hardware using gpio2 for serial
+ //CRSFHandset::Port.end(); // don't call serial.end(), it causes some sort of issue with the 900mhz hardware using gpio2 for serial
DBGLN("CRSF UART END");
-#endif // CRSF_TX_MODULE
}
-#if CRSF_TX_MODULE
-void CRSF::flush_port_input(void)
+void CRSFHandset::flush_port_input()
{
// Make sure there is no garbage on the UART at the start
- while (CRSF::Port.available())
+ while (CRSFHandset::Port.available())
{
- CRSF::Port.read();
+ CRSFHandset::Port.read();
}
}
-void ICACHE_RAM_ATTR CRSF::sendLinkStatisticsToTX()
+void CRSFHandset::makeLinkStatisticsPacket(uint8_t buffer[LinkStatisticsFrameLength + 4])
{
- if (!CRSF::CRSFstate)
+ buffer[0] = CRSF_ADDRESS_RADIO_TRANSMITTER;
+ buffer[1] = LinkStatisticsFrameLength + 2;
+ buffer[2] = CRSF_FRAMETYPE_LINK_STATISTICS;
+ for (uint8_t i = 0; i < LinkStatisticsFrameLength; i++)
{
- return;
+ buffer[i + 3] = ((uint8_t *)&CRSF::LinkStatistics)[i];
}
-
- constexpr uint8_t outBuffer[4] = {
- LinkStatisticsFrameLength + 4,
- CRSF_ADDRESS_RADIO_TRANSMITTER,
- LinkStatisticsFrameLength + 2,
- CRSF_FRAMETYPE_LINK_STATISTICS
- };
-
- uint8_t crc = crsf_crc.calc(outBuffer[3]);
- crc = crsf_crc.calc((byte *)&LinkStatistics, LinkStatisticsFrameLength, crc);
-
-#ifdef PLATFORM_ESP32
- portENTER_CRITICAL(&FIFOmux);
-#endif
- if (SerialOutFIFO.ensure(outBuffer[0] + 1))
- {
- SerialOutFIFO.pushBytes(outBuffer, sizeof(outBuffer));
- SerialOutFIFO.pushBytes((byte *)&LinkStatistics, LinkStatisticsFrameLength);
- SerialOutFIFO.push(crc);
- }
-#ifdef PLATFORM_ESP32
- portEXIT_CRITICAL(&FIFOmux);
-#endif
+ uint8_t crc = crsf_crc.calc(buffer[2]);
+ buffer[LinkStatisticsFrameLength + 3] = crsf_crc.calc((byte *)&CRSF::LinkStatistics, LinkStatisticsFrameLength, crc);
}
/**
* Build a an extended type packet and queue it in the SerialOutFIFO
* This is just a regular packet with 2 extra bytes with the sub src and target
**/
-void CRSF::packetQueueExtended(uint8_t type, void *data, uint8_t len)
+void CRSFHandset::packetQueueExtended(uint8_t type, void *data, uint8_t len)
{
- if (!CRSF::CRSFstate)
- return;
-
uint8_t buf[6] = {
(uint8_t)(len + 6),
CRSF_ADDRESS_RADIO_TRANSMITTER,
@@ -244,23 +175,19 @@ void CRSF::packetQueueExtended(uint8_t type, void *data, uint8_t len)
uint8_t crc = crsf_crc.calc(&buf[3], sizeof(buf)-3);
crc = crsf_crc.calc((byte *)data, len, crc);
-#ifdef PLATFORM_ESP32
- portENTER_CRITICAL(&FIFOmux);
-#endif
+ SerialOutFIFO.lock();
if (SerialOutFIFO.ensure(buf[0] + 1))
{
SerialOutFIFO.pushBytes(buf, sizeof(buf));
SerialOutFIFO.pushBytes((byte *)data, len);
SerialOutFIFO.push(crc);
}
-#ifdef PLATFORM_ESP32
- portEXIT_CRITICAL(&FIFOmux);
-#endif
+ SerialOutFIFO.unlock();
}
-void ICACHE_RAM_ATTR CRSF::sendTelemetryToTX(uint8_t *data)
+void CRSFHandset::sendTelemetryToTX(uint8_t *data)
{
- if (CRSF::CRSFstate)
+ if (controllerConnected)
{
uint8_t size = CRSF_FRAME_SIZE(data[CRSF_TELEMETRY_LENGTH_INDEX]);
if (size > CRSF_MAX_PACKET_LEN)
@@ -270,48 +197,39 @@ void ICACHE_RAM_ATTR CRSF::sendTelemetryToTX(uint8_t *data)
}
data[0] = CRSF_ADDRESS_RADIO_TRANSMITTER;
-#ifdef PLATFORM_ESP32
- portENTER_CRITICAL(&FIFOmux);
-#endif
+ SerialOutFIFO.lock();
if (SerialOutFIFO.ensure(size + 1))
{
SerialOutFIFO.push(size); // length
SerialOutFIFO.pushBytes(data, size);
}
-#ifdef PLATFORM_ESP32
- portEXIT_CRITICAL(&FIFOmux);
-#endif
+ SerialOutFIFO.unlock();
}
}
-void ICACHE_RAM_ATTR CRSF::setSyncParams(int32_t PacketInterval)
+void ICACHE_RAM_ATTR CRSFHandset::setPacketInterval(int32_t PacketInterval)
{
- CRSF::RequestedRCpacketInterval = PacketInterval;
- CRSF::OpenTXsyncOffset = 0;
- CRSF::OpenTXsyncWindow = 0;
- CRSF::OpenTXsyncWindowSize = std::max((int32_t)1, (int32_t)(20000/CRSF::RequestedRCpacketInterval));
- CRSF::OpenTXsyncLastSent -= OpenTXsyncPacketInterval;
+ RequestedRCpacketInterval = PacketInterval;
+ OpenTXsyncOffset = 0;
+ OpenTXsyncWindow = 0;
+ OpenTXsyncWindowSize = std::max((int32_t)1, (int32_t)(20000/RequestedRCpacketInterval));
+ OpenTXsyncLastSent -= OpenTXsyncPacketInterval;
adjustMaxPacketSize();
}
-uint32_t ICACHE_RAM_ATTR CRSF::GetRCdataLastRecv()
-{
- return CRSF::RCdataLastRecv;
-}
-
-void ICACHE_RAM_ATTR CRSF::JustSentRFpacket()
+void ICACHE_RAM_ATTR CRSFHandset::JustSentRFpacket()
{
// read them in this order to prevent a potential race condition
- uint32_t last = CRSF::dataLastRecv;
+ uint32_t last = dataLastRecv;
uint32_t m = micros();
- int32_t delta = (int32_t)(m - last);
+ auto delta = (int32_t)(m - last);
- if (delta >= (int32_t)CRSF::RequestedRCpacketInterval)
+ if (delta >= (int32_t)RequestedRCpacketInterval)
{
// missing/late packet, force resync
- CRSF::OpenTXsyncOffset = -(delta % CRSF::RequestedRCpacketInterval) * 10;
- CRSF::OpenTXsyncWindow = 0;
- CRSF::OpenTXsyncLastSent -= OpenTXsyncPacketInterval;
+ OpenTXsyncOffset = -(delta % RequestedRCpacketInterval) * 10;
+ OpenTXsyncWindow = 0;
+ OpenTXsyncLastSent -= OpenTXsyncPacketInterval;
#ifdef DEBUG_OPENTX_SYNC
DBGLN("Missed packet, forced resync (%d)!", delta);
#endif
@@ -320,28 +238,18 @@ void ICACHE_RAM_ATTR CRSF::JustSentRFpacket()
{
// The number of packets in the sync window is how many will fit in 20ms.
// This gives quite quite coarse changes for 50Hz, but more fine grained changes at 1000Hz.
- CRSF::OpenTXsyncWindow = std::min(CRSF::OpenTXsyncWindow + 1, (int32_t)CRSF::OpenTXsyncWindowSize);
- CRSF::OpenTXsyncOffset = ((CRSF::OpenTXsyncOffset * (CRSF::OpenTXsyncWindow-1)) + delta * 10) / CRSF::OpenTXsyncWindow;
+ OpenTXsyncWindow = std::min(OpenTXsyncWindow + 1, (int32_t)OpenTXsyncWindowSize);
+ OpenTXsyncOffset = ((OpenTXsyncOffset * (OpenTXsyncWindow-1)) + delta * 10) / OpenTXsyncWindow;
}
}
-void CRSF::disableOpentxSync()
-{
- OpentxSyncActive = false;
-}
-
-void CRSF::enableOpentxSync()
-{
- OpentxSyncActive = true;
-}
-
-void ICACHE_RAM_ATTR CRSF::sendSyncPacketToTX() // in values in us.
+void CRSFHandset::sendSyncPacketToTX() // in values in us.
{
uint32_t now = millis();
- if (CRSF::CRSFstate && (now - OpenTXsyncLastSent) >= OpenTXsyncPacketInterval)
+ if (controllerConnected && (now - OpenTXsyncLastSent) >= OpenTXsyncPacketInterval)
{
- int32_t packetRate = CRSF::RequestedRCpacketInterval * 10; //convert from us to right format
- int32_t offset = CRSF::OpenTXsyncOffset - CRSF::OpenTXsyncOffsetSafeMargin; // offset so that opentx always has some headroom
+ int32_t packetRate = RequestedRCpacketInterval * 10; //convert from us to right format
+ int32_t offset = OpenTXsyncOffset - OpenTXsyncOffsetSafeMargin; // offset so that opentx always has some headroom
#ifdef DEBUG_OPENTX_SYNC
DBGLN("Offset %d", offset); // in 10ths of us (OpenTX sync unit)
#endif
@@ -353,7 +261,7 @@ void ICACHE_RAM_ATTR CRSF::sendSyncPacketToTX() // in values in us.
} PACKED;
uint8_t buffer[sizeof(otxSyncData)];
- struct otxSyncData * const sync = (struct otxSyncData * const)buffer;
+ auto * const sync = (struct otxSyncData * const)buffer;
sync->extendedType = CRSF_FRAMETYPE_OPENTX_SYNC;
sync->rate = htobe32(packetRate);
@@ -365,12 +273,12 @@ void ICACHE_RAM_ATTR CRSF::sendSyncPacketToTX() // in values in us.
}
}
-void ICACHE_RAM_ATTR CRSF::RcPacketToChannelsData() // data is packed as 11 bits per channel
+void CRSFHandset::RcPacketToChannelsData() // data is packed as 11 bits per channel
{
// for monitoring arming state
uint32_t prev_AUX1 = ChannelData[4];
- uint8_t const * const payload = (uint8_t const * const)&CRSF::inBuffer.asRCPacket_t.channels;
+ auto payload = (uint8_t const * const)&inBuffer.asRCPacket_t.channels;
constexpr unsigned srcBits = 11;
constexpr unsigned dstBits = 11;
constexpr unsigned inputChannelMask = (1 << srcBits) - 1;
@@ -380,7 +288,7 @@ void ICACHE_RAM_ATTR CRSF::RcPacketToChannelsData() // data is packed as 11 bits
uint8_t bitsMerged = 0;
uint32_t readValue = 0;
unsigned readByteIndex = 0;
- for (unsigned n = 0; n < CRSF_NUM_CHANNELS; n++)
+ for (uint32_t & n : ChannelData)
{
while (bitsMerged < srcBits)
{
@@ -389,66 +297,45 @@ void ICACHE_RAM_ATTR CRSF::RcPacketToChannelsData() // data is packed as 11 bits
bitsMerged += 8;
}
//printf("rv=%x(%x) bm=%u\n", readValue, (readValue & inputChannelMask), bitsMerged);
- ChannelData[n] = (readValue & inputChannelMask) << precisionShift;
+ n = (readValue & inputChannelMask) << precisionShift;
readValue >>= srcBits;
bitsMerged -= srcBits;
}
if (prev_AUX1 != ChannelData[4])
{
- #if defined(PLATFORM_ESP32)
+ #if defined(PLATFORM_ESP32)
devicesTriggerEvent();
- #endif
+ #endif
}
}
-bool ICACHE_RAM_ATTR CRSF::ProcessPacket()
+bool CRSFHandset::processInternalCrsfPackage(uint8_t *package)
{
- bool packetReceived = false;
-
- CRSF::dataLastRecv = micros();
+ const crsf_ext_header_t *header = (crsf_ext_header_t *)package;
+ const crsf_frame_type_e packetType = (crsf_frame_type_e)header->type;
- if (CRSFstate == false)
+ // Enter Binding Mode
+ if (packetType == CRSF_FRAMETYPE_COMMAND
+ && header->frame_size >= 6 // official CRSF is 7 bytes with two CRCs
+ && header->dest_addr == CRSF_ADDRESS_CRSF_TRANSMITTER
+ && header->orig_addr == CRSF_ADDRESS_RADIO_TRANSMITTER
+ && header->payload[0] == CRSF_COMMAND_SUBCMD_RX
+ && header->payload[1] == CRSF_COMMAND_SUBCMD_RX_BIND)
{
- CRSFstate = true;
- DBGLN("CRSF UART Connected");
- if (connected) connected();
- }
-
- const uint8_t packetType = CRSF::inBuffer.asRCPacket_t.header.type;
- uint8_t *SerialInBuffer = CRSF::inBuffer.asUint8_t;
-
- if (packetType == CRSF_FRAMETYPE_RC_CHANNELS_PACKED)
- {
- CRSF::RCdataLastRecv = micros();
- RcPacketToChannelsData();
- packetReceived = true;
- }
- // check for all extended frames that are a broadcast or a message to the FC
- else if (packetType >= CRSF_FRAMETYPE_DEVICE_PING &&
- (SerialInBuffer[3] == CRSF_ADDRESS_FLIGHT_CONTROLLER || SerialInBuffer[3] == CRSF_ADDRESS_BROADCAST || SerialInBuffer[3] == CRSF_ADDRESS_CRSF_RECEIVER))
- {
- // Some types trigger telemburst to attempt a connection even with telm off
- // but for pings (which are sent when the user loads Lua) do not forward
- // unless connected
- if (ForwardDevicePings || packetType != CRSF_FRAMETYPE_DEVICE_PING)
- {
- const uint8_t length = CRSF::inBuffer.asRCPacket_t.header.frame_size + 2;
- AddMspMessage(length, SerialInBuffer);
- }
- packetReceived = true;
+ if (OnBindingCommand) OnBindingCommand();
+ return true;
}
- // always execute this check since broadcast needs to be handled in all cases
if (packetType >= CRSF_FRAMETYPE_DEVICE_PING &&
- (SerialInBuffer[3] == CRSF_ADDRESS_CRSF_TRANSMITTER || SerialInBuffer[3] == CRSF_ADDRESS_BROADCAST) &&
- (SerialInBuffer[4] == CRSF_ADDRESS_RADIO_TRANSMITTER || SerialInBuffer[4] == CRSF_ADDRESS_ELRS_LUA))
+ (header->dest_addr == CRSF_ADDRESS_CRSF_TRANSMITTER || header->dest_addr == CRSF_ADDRESS_BROADCAST) &&
+ (header->orig_addr == CRSF_ADDRESS_RADIO_TRANSMITTER || header->orig_addr == CRSF_ADDRESS_ELRS_LUA))
{
- elrsLUAmode = SerialInBuffer[4] == CRSF_ADDRESS_ELRS_LUA;
+ elrsLUAmode = header->orig_addr == CRSF_ADDRESS_ELRS_LUA;
- if (packetType == CRSF_FRAMETYPE_COMMAND && SerialInBuffer[5] == SUBCOMMAND_CRSF && SerialInBuffer[6] == COMMAND_MODEL_SELECT_ID)
+ if (packetType == CRSF_FRAMETYPE_COMMAND && header->payload[0] == CRSF_COMMAND_SUBCMD_RX && header->payload[1] == CRSF_COMMAND_MODEL_SELECT_ID)
{
- modelId = SerialInBuffer[7];
+ modelId = header->payload[2];
#if defined(PLATFORM_ESP32)
rtcModelId = modelId;
#endif
@@ -456,120 +343,71 @@ bool ICACHE_RAM_ATTR CRSF::ProcessPacket()
}
else
{
- if (RecvParameterUpdate) RecvParameterUpdate(packetType, SerialInBuffer[5], SerialInBuffer[6]);
+ if (RecvParameterUpdate) RecvParameterUpdate(packetType, header->payload[0], header->payload[1]);
}
- packetReceived = true;
+ return true;
}
- return packetReceived;
-}
-
-void CRSF::GetMspMessage(uint8_t **data, uint8_t *len)
-{
- *len = MspDataLength;
- *data = (MspDataLength > 0) ? MspData : nullptr;
-}
-
-void CRSF::ResetMspQueue()
-{
- MspWriteFIFO.flush();
- MspDataLength = 0;
- memset(MspData, 0, ELRS_MSP_BUFFER);
-}
-
-void CRSF::UnlockMspMessage()
-{
- // current msp message is sent so restore next buffered write
- if (MspWriteFIFO.peek() > 0)
- {
- uint8_t length = MspWriteFIFO.pop();
- MspDataLength = length;
- MspWriteFIFO.popBytes(MspData, length);
- }
- else
- {
- // no msp message is ready to send currently
- MspDataLength = 0;
- memset(MspData, 0, ELRS_MSP_BUFFER);
- }
+ return false;
}
-void ICACHE_RAM_ATTR CRSF::AddMspMessage(mspPacket_t* packet)
+bool CRSFHandset::ProcessPacket()
{
- if (packet->payloadSize > ENCAPSULATED_MSP_MAX_PAYLOAD_SIZE)
- {
- return;
- }
-
- const uint8_t totalBufferLen = packet->payloadSize + ENCAPSULATED_MSP_HEADER_CRC_LEN + CRSF_FRAME_LENGTH_EXT_TYPE_CRC + CRSF_FRAME_NOT_COUNTED_BYTES;
- uint8_t outBuffer[ENCAPSULATED_MSP_MAX_FRAME_LEN + CRSF_FRAME_LENGTH_EXT_TYPE_CRC + CRSF_FRAME_NOT_COUNTED_BYTES];
+ bool packetReceived = false;
- // CRSF extended frame header
- outBuffer[0] = CRSF_ADDRESS_BROADCAST; // address
- outBuffer[1] = packet->payloadSize + ENCAPSULATED_MSP_HEADER_CRC_LEN + CRSF_FRAME_LENGTH_EXT_TYPE_CRC; // length
- outBuffer[2] = CRSF_FRAMETYPE_MSP_WRITE; // packet type
- outBuffer[3] = CRSF_ADDRESS_FLIGHT_CONTROLLER; // destination
- outBuffer[4] = CRSF_ADDRESS_RADIO_TRANSMITTER; // origin
+ CRSFHandset::dataLastRecv = micros();
- // Encapsulated MSP payload
- outBuffer[5] = 0x30; // header
- outBuffer[6] = packet->payloadSize; // mspPayloadSize
- outBuffer[7] = packet->function; // packet->cmd
- for (uint8_t i = 0; i < packet->payloadSize; ++i)
+ if (!controllerConnected)
{
- // copy packet payload into outBuffer
- outBuffer[8 + i] = packet->payload[i];
+ controllerConnected = true;
+ DBGLN("CRSF UART Connected");
+ if (connected) connected();
}
- // Encapsulated MSP crc
- outBuffer[totalBufferLen - 2] = CalcCRCMsp(&outBuffer[6], packet->payloadSize + 2);
- // CRSF frame crc
- outBuffer[totalBufferLen - 1] = crsf_crc.calc(&outBuffer[2], packet->payloadSize + ENCAPSULATED_MSP_HEADER_CRC_LEN + CRSF_FRAME_LENGTH_EXT_TYPE_CRC - 1);
- AddMspMessage(totalBufferLen, outBuffer);
-}
+ const uint8_t packetType = inBuffer.asRCPacket_t.header.type;
+ uint8_t *SerialInBuffer = inBuffer.asUint8_t;
-void ICACHE_RAM_ATTR CRSF::AddMspMessage(const uint8_t length, uint8_t* data)
-{
- if (length > ELRS_MSP_BUFFER)
- {
- return;
- }
-
- // store next msp message
- if (MspDataLength == 0)
+ if (packetType == CRSF_FRAMETYPE_RC_CHANNELS_PACKED)
{
- for (uint8_t i = 0; i < length; i++)
- {
- MspData[i] = data[i];
- }
- MspDataLength = length;
+ RCdataLastRecv = micros();
+ RcPacketToChannelsData();
+ packetReceived = true;
}
- // store all write requests since an update does send multiple writes
- else
+ // check for all extended frames that are a broadcast or a message to the FC
+ else if (packetType >= CRSF_FRAMETYPE_DEVICE_PING &&
+ (SerialInBuffer[3] == CRSF_ADDRESS_FLIGHT_CONTROLLER || SerialInBuffer[3] == CRSF_ADDRESS_BROADCAST || SerialInBuffer[3] == CRSF_ADDRESS_CRSF_RECEIVER))
{
- if (MspWriteFIFO.ensure(length + 1))
+ // Some types trigger telemburst to attempt a connection even with telm off
+ // but for pings (which are sent when the user loads Lua) do not forward
+ // unless connected
+ if (ForwardDevicePings || packetType != CRSF_FRAMETYPE_DEVICE_PING)
{
- MspWriteFIFO.push(length);
- MspWriteFIFO.pushBytes((const uint8_t *)data, length);
+ const uint8_t length = inBuffer.asRCPacket_t.header.frame_size + 2;
+ CRSF::AddMspMessage(length, SerialInBuffer);
}
+ packetReceived = true;
}
+
+ packetReceived |= processInternalCrsfPackage(SerialInBuffer);
+
+ return packetReceived;
}
-void ICACHE_RAM_ATTR CRSF::handleUARTin()
+void CRSFHandset::handleInput()
{
- uint8_t *SerialInBuffer = CRSF::inBuffer.asUint8_t;
+ uint8_t *SerialInBuffer = inBuffer.asUint8_t;
if (UARTwdt())
{
return;
}
- while (CRSF::Port.available())
+ while (CRSFHandset::Port.available())
{
- if (CRSFframeActive == false)
+ if (!CRSFframeActive)
{
- unsigned char const inChar = CRSF::Port.read();
+ unsigned char const inChar = CRSFHandset::Port.read();
// stage 1 wait for sync byte //
if (inChar == CRSF_ADDRESS_CRSF_TRANSMITTER ||
inChar == CRSF_SYNC_BYTE)
@@ -597,7 +435,7 @@ void ICACHE_RAM_ATTR CRSF::handleUARTin()
// special case where we save the expected pkt len to buffer //
if (SerialInPacketPtr == 1)
{
- unsigned char const inChar = CRSF::Port.read();
+ unsigned char const inChar = CRSFHandset::Port.read();
if (inChar <= CRSF_MAX_PACKET_LEN)
{
SerialInPacketLen = inChar;
@@ -615,13 +453,13 @@ void ICACHE_RAM_ATTR CRSF::handleUARTin()
int toRead = (SerialInPacketLen + 2) - SerialInPacketPtr;
#if defined(PLATFORM_ESP32)
- int count = CRSF::Port.read(&SerialInBuffer[SerialInPacketPtr], toRead);
+ auto count = (int)CRSFHandset::Port.read(&SerialInBuffer[SerialInPacketPtr], toRead);
#else
int count = 0;
- int avail = CRSF::Port.available();
+ auto avail = (int)CRSFHandset::Port.available();
while (count < toRead && count < avail)
{
- SerialInBuffer[SerialInPacketPtr + count] = CRSF::Port.read();
+ SerialInBuffer[SerialInPacketPtr + count] = CRSFHandset::Port.read();
count++;
}
#endif
@@ -629,7 +467,7 @@ void ICACHE_RAM_ATTR CRSF::handleUARTin()
if (SerialInPacketPtr >= (SerialInPacketLen + 2)) // plus 2 because the packlen is referenced from the start of the 'type' flag, IE there are an extra 2 bytes.
{
- char CalculatedCRC = crsf_crc.calc(SerialInBuffer + 2, SerialInPacketPtr - 3);
+ uint8_t CalculatedCRC = crsf_crc.calc(SerialInBuffer + 2, SerialInPacketPtr - 3);
if (CalculatedCRC == SerialInBuffer[SerialInPacketPtr-1])
{
@@ -637,7 +475,7 @@ void ICACHE_RAM_ATTR CRSF::handleUARTin()
if (ProcessPacket())
{
//delayMicroseconds(50);
- handleUARTout();
+ handleOutput();
if (RCdataCallback) RCdataCallback();
}
}
@@ -656,13 +494,22 @@ void ICACHE_RAM_ATTR CRSF::handleUARTin()
}
}
-void ICACHE_RAM_ATTR CRSF::handleUARTout()
+void CRSFHandset::handleOutput()
{
+ static uint8_t CRSFoutBuffer[CRSF_MAX_PACKET_LEN] = {0};
// both static to split up larger packages
static uint8_t packageLengthRemaining = 0;
static uint8_t sendingOffset = 0;
- if (OpentxSyncActive && packageLengthRemaining == 0 && SerialOutFIFO.size() == 0)
+ if (!controllerConnected)
+ {
+ SerialOutFIFO.lock();
+ SerialOutFIFO.flush();
+ SerialOutFIFO.unlock();
+ return;
+ }
+
+ if (packageLengthRemaining == 0 && SerialOutFIFO.size() == 0)
{
sendSyncPacketToTX(); // calculate mixer sync packet if needed
}
@@ -674,18 +521,14 @@ void ICACHE_RAM_ATTR CRSF::handleUARTout()
uint8_t periodBytesRemaining = maxPeriodBytes;
while (periodBytesRemaining)
{
-#ifdef PLATFORM_ESP32
- portENTER_CRITICAL(&FIFOmux); // stops other tasks from writing to the FIFO when we want to read it
-#endif
+ SerialOutFIFO.lock();
// no package is in transit so get new data from the fifo
if (packageLengthRemaining == 0) {
packageLengthRemaining = SerialOutFIFO.pop();
SerialOutFIFO.popBytes(CRSFoutBuffer, packageLengthRemaining);
sendingOffset = 0;
}
-#ifdef PLATFORM_ESP32
- portEXIT_CRITICAL(&FIFOmux); // stops other tasks from writing to the FIFO when we want to read it
-#endif
+ SerialOutFIFO.unlock();
// if the package is long we need to split it up so it fits in the sending interval
uint8_t writeLength;
@@ -699,9 +542,9 @@ void ICACHE_RAM_ATTR CRSF::handleUARTout()
}
// write the packet out, if it's a large package the offset holds the starting position
- CRSF::Port.write(CRSFoutBuffer + sendingOffset, writeLength);
- if (CRSF::PortSecondary)
- CRSF::PortSecondary->write(CRSFoutBuffer + sendingOffset, writeLength);
+ CRSFHandset::Port.write(CRSFoutBuffer + sendingOffset, writeLength);
+ if (CRSFHandset::PortSecondary)
+ CRSFHandset::PortSecondary->write(CRSFoutBuffer + sendingOffset, writeLength);
sendingOffset += writeLength;
packageLengthRemaining -= writeLength;
@@ -711,7 +554,7 @@ void ICACHE_RAM_ATTR CRSF::handleUARTout()
if (SerialOutFIFO.size() == 0)
break;
}
- CRSF::Port.flush();
+ CRSFHandset::Port.flush();
duplex_set_RX();
// make sure there is no garbage on the UART left over
@@ -719,7 +562,7 @@ void ICACHE_RAM_ATTR CRSF::handleUARTout()
}
}
-void ICACHE_RAM_ATTR CRSF::duplex_set_RX()
+void CRSFHandset::duplex_set_RX() const
{
#if defined(PLATFORM_ESP32)
if (GPIO_PIN_RCSIGNAL_TX == GPIO_PIN_RCSIGNAL_RX)
@@ -744,11 +587,11 @@ void ICACHE_RAM_ATTR CRSF::duplex_set_RX()
#elif defined(GPIO_PIN_BUFFER_OE) && (GPIO_PIN_BUFFER_OE != UNDEF_PIN)
digitalWrite(GPIO_PIN_BUFFER_OE, LOW ^ GPIO_PIN_BUFFER_OE_INVERTED);
#elif (GPIO_PIN_RCSIGNAL_TX == GPIO_PIN_RCSIGNAL_RX)
- CRSF::Port.enableHalfDuplexRx();
+ CRSFHandset::Port.enableHalfDuplexRx();
#endif
}
-void ICACHE_RAM_ATTR CRSF::duplex_set_TX()
+void CRSFHandset::duplex_set_TX() const
{
#if defined(PLATFORM_ESP32)
if (GPIO_PIN_RCSIGNAL_TX == GPIO_PIN_RCSIGNAL_RX)
@@ -782,7 +625,7 @@ void ICACHE_RAM_ATTR CRSF::duplex_set_TX()
#endif
}
-void ICACHE_RAM_ATTR CRSF::adjustMaxPacketSize()
+void ICACHE_RAM_ATTR CRSFHandset::adjustMaxPacketSize()
{
// baud / 10bits-per-byte / 2 windows (1RX, 1TX) / rate * 0.80 (leeway)
maxPeriodBytes = UARTrequestedBaud / 10 / 2 / (1000000/RequestedRCpacketInterval) * 80 / 100;
@@ -793,104 +636,156 @@ void ICACHE_RAM_ATTR CRSF::adjustMaxPacketSize()
DBGLN("Adjusted max packet size %u-%u", maxPacketBytes, maxPeriodBytes);
}
-#if defined(PLATFORM_ESP32)
-uint32_t CRSF::autobaud()
+#if defined(PLATFORM_ESP32_S3)
+uint32_t CRSFHandset::autobaud()
{
static enum { INIT, MEASURED, INVERTED } state;
- uint32_t *autobaud_reg = (uint32_t *)UART_AUTOBAUD_REG(0);
- uint32_t *rxd_cnt_reg = (uint32_t *)UART_RXD_CNT_REG(0);
+ if (state == MEASURED)
+ {
+ UARTinverted = !UARTinverted;
+ state = INVERTED;
+ return UARTrequestedBaud;
+ }
+ if (state == INVERTED)
+ {
+ UARTinverted = !UARTinverted;
+ state = INIT;
+ }
+
+ if (REG_GET_BIT(UART_CONF0_REG(0), UART_AUTOBAUD_EN) == 0)
+ {
+ REG_WRITE(UART_RX_FILT_REG(0), (4 << UART_GLITCH_FILT_S) | UART_GLITCH_FILT_EN); // enable, glitch filter 4
+ REG_WRITE(UART_LOWPULSE_REG(0), 4095); // reset register to max value
+ REG_WRITE(UART_HIGHPULSE_REG(0), 4095); // reset register to max value
+ REG_SET_BIT(UART_CONF0_REG(0), UART_AUTOBAUD_EN); // enable autobaud
+ return 400000;
+ }
+ if (REG_GET_BIT(UART_CONF0_REG(0), UART_AUTOBAUD_EN) && REG_READ(UART_RXD_CNT_REG(0)) < 300)
+ {
+ return 400000;
+ }
+
+ state = MEASURED;
+
+ const uint32_t low_period = REG_READ(UART_LOWPULSE_REG(0));
+ const uint32_t high_period = REG_READ(UART_HIGHPULSE_REG(0));
+ REG_CLR_BIT(UART_CONF0_REG(0), UART_AUTOBAUD_EN); // disable autobaud
+ REG_CLR_BIT(UART_RX_FILT_REG(0), UART_GLITCH_FILT_EN); // disable glitch filtering
+
+ DBGLN("autobaud: low %d, high %d", low_period, high_period);
+ // According to the tecnnical reference
+ const int32_t calulatedBaud = UART_CLK_FREQ / (low_period + high_period + 2);
+ int32_t bestBaud = TxToHandsetBauds[0];
+ for(int i=0 ; i abs(calulatedBaud - TxToHandsetBauds[i]))
+ {
+ bestBaud = TxToHandsetBauds[i];
+ }
+ }
+ return bestBaud;
+}
+#elif defined(PLATFORM_ESP32)
+uint32_t CRSFHandset::autobaud()
+{
+ static enum { INIT, MEASURED, INVERTED } state;
if (state == MEASURED) {
UARTinverted = !UARTinverted;
state = INVERTED;
return UARTrequestedBaud;
- } else if (state == INVERTED) {
+ }
+ if (state == INVERTED) {
UARTinverted = !UARTinverted;
state = INIT;
}
- if ((*autobaud_reg & 1) == 0) {
- *autobaud_reg = (4 << 8) | 1; // enable, glitch filter 4
+ if (REG_GET_BIT(UART_AUTOBAUD_REG(0), UART_AUTOBAUD_EN) == 0) {
+ REG_WRITE(UART_AUTOBAUD_REG(0), 4 << UART_GLITCH_FILT_S | UART_AUTOBAUD_EN); // enable, glitch filter 4
return 400000;
- } else if ((*autobaud_reg & 1) && (*rxd_cnt_reg < 300))
+ }
+ if (REG_GET_BIT(UART_AUTOBAUD_REG(0), UART_AUTOBAUD_EN) && REG_READ(UART_RXD_CNT_REG(0)) < 300)
+ {
return 400000;
+ }
state = MEASURED;
- uint32_t low_period = *(uint32_t *)UART_LOWPULSE_REG(0);
- uint32_t high_period = *(uint32_t *)UART_HIGHPULSE_REG(0);
- *autobaud_reg = (4 << 8) | 0;
+ auto low_period = (int32_t)REG_READ(UART_LOWPULSE_REG(0));
+ auto high_period = (int32_t)REG_READ(UART_HIGHPULSE_REG(0));
+ REG_CLR_BIT(UART_AUTOBAUD_REG(0), UART_AUTOBAUD_EN); // disable autobaud
DBGLN("autobaud: low %d, high %d", low_period, high_period);
// sample code at https://github.com/espressif/esp-idf/issues/3336
// says baud rate = 80000000/min(UART_LOWPULSE_REG, UART_HIGHPULSE_REG);
// Based on testing use max and add 2 for lowest deviation
- int32_t calulatedBaud = 80000000 / (max(low_period, high_period) + 3);
- int32_t bestBaud = (int32_t)TxToHandsetBauds[0];
- for(int i=0 ; i abs(calulatedBaud - (int32_t)TxToHandsetBauds[i]))
+ if (abs(calculatedBaud - bestBaud) > abs(calculatedBaud - (int32_t)TxToHandsetBaud))
{
- bestBaud = (int32_t)TxToHandsetBauds[i];
+ bestBaud = (int32_t)TxToHandsetBaud;
}
}
return bestBaud;
}
#else
-uint32_t CRSF::autobaud() {
+uint32_t CRSFHandset::autobaud() {
UARTcurrentBaudIdx = (UARTcurrentBaudIdx + 1) % ARRAY_SIZE(TxToHandsetBauds);
return TxToHandsetBauds[UARTcurrentBaudIdx];
}
#endif
-bool CRSF::UARTwdt()
+bool CRSFHandset::UARTwdt()
{
bool retval = false;
#if !defined(DEBUG_TX_FREERUN)
uint32_t now = millis();
if (now >= (UARTwdtLastChecked + UARTwdtInterval))
{
- if (BadPktsCount >= GoodPktsCount)
+ if (BadPktsCount >= GoodPktsCount || !controllerConnected)
{
DBGLN("Too many bad UART RX packets!");
- if (CRSFstate == true)
+ if (controllerConnected)
{
DBGLN("CRSF UART Disconnected");
if (disconnected) disconnected();
- CRSFstate = false;
+ controllerConnected = false;
}
UARTrequestedBaud = autobaud();
+ if (UARTrequestedBaud != 0)
+ {
+ DBGLN("UART WDT: Switch to: %d baud", UARTrequestedBaud);
- DBGLN("UART WDT: Switch to: %d baud", UARTrequestedBaud);
-
- adjustMaxPacketSize();
+ adjustMaxPacketSize();
- SerialOutFIFO.flush();
+ SerialOutFIFO.flush();
#if defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32)
- CRSF::Port.flush();
- CRSF::Port.updateBaudRate(UARTrequestedBaud);
+ CRSFHandset::Port.flush();
+ CRSFHandset::Port.updateBaudRate(UARTrequestedBaud);
#elif defined(TARGET_TX_GHOST)
- CRSF::Port.begin(UARTrequestedBaud);
- USART1->CR1 &= ~USART_CR1_UE;
- USART1->CR3 |= USART_CR3_HDSEL;
- USART1->CR2 |= USART_CR2_RXINV | USART_CR2_TXINV | USART_CR2_SWAP; //inverted/swapped
- USART1->CR1 |= USART_CR1_UE;
+ CRSFHandset::Port.begin(UARTrequestedBaud);
+ USART1->CR1 &= ~USART_CR1_UE;
+ USART1->CR3 |= USART_CR3_HDSEL;
+ USART1->CR2 |= USART_CR2_RXINV | USART_CR2_TXINV | USART_CR2_SWAP; //inverted/swapped
+ USART1->CR1 |= USART_CR1_UE;
#elif defined(TARGET_TX_FM30_MINI)
- CRSF::Port.begin(UARTrequestedBaud);
- LL_GPIO_SetPinPull(GPIOA, GPIO_PIN_2, LL_GPIO_PULL_DOWN); // default is PULLUP
- USART2->CR1 &= ~USART_CR1_UE;
- USART2->CR2 |= USART_CR2_RXINV | USART_CR2_TXINV; //inverted
- USART2->CR1 |= USART_CR1_UE;
+ CRSFHandset::Port.begin(UARTrequestedBaud);
+ LL_GPIO_SetPinPull(GPIOA, GPIO_PIN_2, LL_GPIO_PULL_DOWN); // default is PULLUP
+ USART2->CR1 &= ~USART_CR1_UE;
+ USART2->CR2 |= USART_CR2_RXINV | USART_CR2_TXINV; //inverted
+ USART2->CR1 |= USART_CR1_UE;
#else
- CRSF::Port.begin(UARTrequestedBaud);
+ CRSFHandset::Port.begin(UARTrequestedBaud);
#endif
- duplex_set_RX();
- // cleanup input buffer
- flush_port_input();
-
+ duplex_set_RX();
+ // cleanup input buffer
+ flush_port_input();
+ }
retval = true;
}
#ifdef DEBUG_OPENTX_SYNC
@@ -913,99 +808,5 @@ bool CRSF::UARTwdt()
#endif
return retval;
}
-#endif // CRSF_RX_MODULE
-
-/***
- * @brief: Convert `version` (string) to a integer version representation
- * e.g. "2.2.15 ISM24G" => 0x0002020f
- * Assumes all version fields are < 256, the number portion
- * MUST be followed by a space to correctly be parsed
- ***/
-uint32_t CRSF::VersionStrToU32(const char *verStr)
-{
- uint32_t retVal = 0;
-#if !defined(FORCE_NO_DEVICE_VERSION)
- uint8_t accumulator = 0;
- char c;
- bool trailing_data = false;
- while ((c = *verStr))
- {
- ++verStr;
- // A decimal indicates moving to a new version field
- if (c == '.')
- {
- retVal = (retVal << 8) | accumulator;
- accumulator = 0;
- trailing_data = false;
- }
- // Else if this is a number add it up
- else if (c >= '0' && c <= '9')
- {
- accumulator = (accumulator * 10) + (c - '0');
- trailing_data = true;
- }
- // Anything except [0-9.] ends the parsing
- else
- {
- break;
- }
- }
- if (trailing_data)
- {
- retVal = (retVal << 8) | accumulator;
- }
- // If the version ID is < 1.0.0, we could not parse it,
- // just use the OTA version as the major version number
- if (retVal < 0x010000)
- {
- retVal = OTA_VERSION_ID << 16;
- }
-#endif
- return retVal;
-}
-
-void CRSF::GetDeviceInformation(uint8_t *frame, uint8_t fieldCount)
-{
- const uint8_t size = strlen(device_name)+1;
- deviceInformationPacket_t *device = (deviceInformationPacket_t *)(frame + sizeof(crsf_ext_header_t) + size);
- // Packet starts with device name
- memcpy(frame + sizeof(crsf_ext_header_t), device_name, size);
- // Followed by the device
- device->serialNo = htobe32(0x454C5253); // ['E', 'L', 'R', 'S'], seen [0x00, 0x0a, 0xe7, 0xc6] // "Serial 177-714694" (value is 714694)
- device->hardwareVer = 0; // unused currently by us, seen [ 0x00, 0x0b, 0x10, 0x01 ] // "Hardware: V 1.01" / "Bootloader: V 3.06"
- device->softwareVer = htobe32(VersionStrToU32(version)); // seen [ 0x00, 0x00, 0x05, 0x0f ] // "Firmware: V 5.15"
- device->fieldCnt = fieldCount;
- device->parameterVersion = 0;
-}
-
-void CRSF::SetMspV2Request(uint8_t *frame, uint16_t function, uint8_t *payload, uint8_t payloadLength)
-{
- uint8_t *packet = (uint8_t *)(frame + sizeof(crsf_ext_header_t));
- packet[0] = 0x50; // no error, version 2, beginning of the frame, first frame (0)
- packet[1] = 0; // flags
- packet[2] = function & 0xFF;
- packet[3] = (function >> 8) & 0xFF;
- packet[4] = payloadLength & 0xFF;
- packet[5] = (payloadLength >> 8) & 0xFF;
- memcpy(packet + 6, payload, payloadLength);
- packet[6 + payloadLength] = CalcCRCMsp(packet + 1, payloadLength + 5); // crc = flags + function + length + payload
-}
-
-void CRSF::SetHeaderAndCrc(uint8_t *frame, uint8_t frameType, uint8_t frameSize, uint8_t destAddr)
-{
- crsf_header_t *header = (crsf_header_t *)frame;
- header->device_addr = destAddr;
- header->frame_size = frameSize;
- header->type = frameType;
-
- uint8_t crc = crsf_crc.calc(&frame[CRSF_FRAME_NOT_COUNTED_BYTES], frameSize - 1, 0);
- frame[frameSize + CRSF_FRAME_NOT_COUNTED_BYTES - 1] = crc;
-}
-void CRSF::SetExtendedHeaderAndCrc(uint8_t *frame, uint8_t frameType, uint8_t frameSize, uint8_t senderAddr, uint8_t destAddr)
-{
- crsf_ext_header_t *header = (crsf_ext_header_t *)frame;
- header->dest_addr = destAddr;
- header->orig_addr = senderAddr;
- SetHeaderAndCrc(frame, frameType, frameSize, destAddr);
-}
+#endif // CRSF_TX_MODULE
diff --git a/src/lib/Handset/CRSFHandset.h b/src/lib/Handset/CRSFHandset.h
new file mode 100644
index 0000000000..8f1d75629e
--- /dev/null
+++ b/src/lib/Handset/CRSFHandset.h
@@ -0,0 +1,91 @@
+#ifndef H_CRSF_CONTROLLER
+#define H_CRSF_CONTROLLER
+
+#include "handset.h"
+#include "crsf_protocol.h"
+#ifndef TARGET_NATIVE
+#include "HardwareSerial.h"
+#endif
+#include "common.h"
+
+#ifdef PLATFORM_ESP32
+#include "driver/uart.h"
+#endif
+
+class CRSFHandset final : public Handset
+{
+
+public:
+ /////Variables/////
+ void Begin() override;
+ void End() override;
+
+#ifdef CRSF_TX_MODULE
+ bool IsArmed() override { return CRSF_to_BIT(ChannelData[4]); } // AUX1
+ void handleInput() override;
+ void handleOutput();
+
+ static HardwareSerial Port;
+ static Stream *PortSecondary; // A second UART used to mirror telemetry out on the TX, not read from
+
+ static uint8_t modelId; // The model ID as received from the Transmitter
+ static bool ForwardDevicePings; // true if device pings should be forwarded OTA
+ static bool elrsLUAmode;
+
+ static uint32_t GoodPktsCountResult; // need to latch the results
+ static uint32_t BadPktsCountResult; // need to latch the results
+
+ static void makeLinkStatisticsPacket(uint8_t buffer[LinkStatisticsFrameLength + 4]);
+
+ static void packetQueueExtended(uint8_t type, void *data, uint8_t len);
+
+ void setPacketInterval(int32_t PacketInterval) override;
+ void JustSentRFpacket() override;
+ void sendTelemetryToTX(uint8_t *data) override;
+
+ static uint8_t getModelID() { return modelId; }
+
+ uint8_t GetMaxPacketBytes() const override { return maxPacketBytes; }
+ static uint32_t GetCurrentBaudRate() { return UARTrequestedBaud; }
+
+private:
+ inBuffer_U inBuffer = {};
+
+ /// OpenTX mixer sync ///
+ volatile uint32_t dataLastRecv = 0;
+ volatile int32_t OpenTXsyncOffset = 0;
+ volatile int32_t OpenTXsyncWindow = 0;
+ volatile int32_t OpenTXsyncWindowSize = 0;
+ uint32_t OpenTXsyncLastSent = 0;
+
+ /// UART Handling ///
+ uint8_t SerialInPacketLen = 0; // length of the CRSF packet as measured
+ uint8_t SerialInPacketPtr = 0; // index where we are reading/writing
+ bool CRSFframeActive = false; // since we get a copy of the serial data use this flag to know when to ignore it
+ uint32_t GoodPktsCount = 0;
+ uint32_t BadPktsCount = 0;
+ uint32_t UARTwdtLastChecked = 0;
+ uint8_t maxPacketBytes = CRSF_MAX_PACKET_LEN;
+ uint8_t maxPeriodBytes = CRSF_MAX_PACKET_LEN;
+
+ static uint8_t UARTcurrentBaudIdx;
+ static uint32_t UARTrequestedBaud;
+
+#if defined(PLATFORM_ESP32)
+ bool UARTinverted = false;
+#endif
+
+ void sendSyncPacketToTX();
+ void adjustMaxPacketSize();
+ void duplex_set_RX() const;
+ void duplex_set_TX() const;
+ void RcPacketToChannelsData();
+ bool processInternalCrsfPackage(uint8_t *package);
+ bool ProcessPacket();
+ bool UARTwdt();
+ uint32_t autobaud();
+ void flush_port_input();
+#endif
+};
+
+#endif
diff --git a/src/lib/Handset/PPMHandset.cpp b/src/lib/Handset/PPMHandset.cpp
new file mode 100644
index 0000000000..c9e2e8e892
--- /dev/null
+++ b/src/lib/Handset/PPMHandset.cpp
@@ -0,0 +1,82 @@
+#include "targets.h"
+
+#if defined(PLATFORM_ESP32) && defined(TARGET_TX)
+
+#include "PPMHandset.h"
+#include "common.h"
+#include "crsf_protocol.h"
+#include "logging.h"
+
+#include
+
+constexpr auto RMT_TICKS_PER_US = 10;
+
+void PPMHandset::Begin()
+{
+ constexpr auto divisor = 80 / RMT_TICKS_PER_US;
+
+ rmt_config_t rmt_rx_config = RMT_DEFAULT_CONFIG_RX(static_cast(GPIO_PIN_RCSIGNAL_RX), PPM_RMT_CHANNEL);
+ rmt_rx_config.clk_div = divisor;
+ rmt_rx_config.rx_config.idle_threshold = min(65000, RMT_TICKS_PER_US * 4000); // greater than 4ms triggers end of frame
+ rmt_config(&rmt_rx_config);
+ rmt_driver_install(PPM_RMT_CHANNEL, 1000, 0);
+
+ rmt_get_ringbuf_handle(PPM_RMT_CHANNEL, &rb);
+ rmt_rx_start(PPM_RMT_CHANNEL, true);
+ lastPPM = 0;
+
+ if (connected)
+ {
+ connected();
+ }
+}
+
+void PPMHandset::End()
+{
+ rmt_driver_uninstall(PPM_RMT_CHANNEL);
+}
+
+bool PPMHandset::IsArmed()
+{
+ bool maybeArmed = numChannels < 5 || CRSF_to_BIT(ChannelData[4]);
+ return maybeArmed && lastPPM;
+}
+
+void PPMHandset::handleInput()
+{
+ const auto now = millis();
+ size_t length = 0;
+
+ auto *items = static_cast(xRingbufferReceive(rb, &length, 0));
+ if (items)
+ {
+ length /= 4; // one RMT = 4 Bytes
+ int channelCount = 0;
+ for (int i = 0; i < length; i++)
+ {
+ const auto item = items[i];
+ // Stop if there is a 0 duration
+ if (item.duration0 == 0 || item.duration1 == 0)
+ {
+ break;
+ }
+ channelCount ++;
+ const auto ppm = (item.duration0 + item.duration1) / RMT_TICKS_PER_US;
+ ChannelData[i] = fmap(ppm, 988, 2012, CRSF_CHANNEL_VALUE_MIN, CRSF_CHANNEL_VALUE_MAX);
+ }
+ numChannels = channelCount;
+ vRingbufferReturnItem(rb, static_cast(items));
+ lastPPM = now;
+ }
+ else if (lastPPM && now - 1000 > lastPPM)
+ {
+ DBGLN("PPM signal lost, disarming");
+ if (disconnected)
+ {
+ disconnected();
+ }
+ lastPPM = 0;
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/lib/Handset/PPMHandset.h b/src/lib/Handset/PPMHandset.h
new file mode 100644
index 0000000000..84f5767d6a
--- /dev/null
+++ b/src/lib/Handset/PPMHandset.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "handset.h"
+
+#include
+
+class PPMHandset final : public Handset
+{
+public:
+ void Begin() override;
+ void End() override;
+ bool IsArmed() override;
+ void handleInput() override;
+
+private:
+ uint32_t lastPPM = 0;
+ size_t numChannels = 0;
+ RingbufHandle_t rb = nullptr;
+};
+
+#if defined(PLATFORM_ESP32_S3)
+constexpr rmt_channel_t PPM_RMT_CHANNEL = RMT_CHANNEL_4;
+#else
+constexpr rmt_channel_t PPM_RMT_CHANNEL = RMT_CHANNEL_0;
+#endif
diff --git a/src/lib/Handset/devHandset.cpp b/src/lib/Handset/devHandset.cpp
new file mode 100644
index 0000000000..e1892ca9ef
--- /dev/null
+++ b/src/lib/Handset/devHandset.cpp
@@ -0,0 +1,58 @@
+#include "targets.h"
+
+#ifdef TARGET_TX
+
+#include "CRSF.h"
+#include "CRSFHandset.h"
+#include "POWERMGNT.h"
+#include "devHandset.h"
+
+#if defined(PLATFORM_ESP32)
+#include "AutoDetect.h"
+#endif
+
+Handset *handset;
+
+static void initialize()
+{
+#if defined(PLATFORM_ESP32)
+ if (GPIO_PIN_RCSIGNAL_RX == GPIO_PIN_RCSIGNAL_TX)
+ {
+ handset = new AutoDetect();
+ return;
+ }
+#endif
+ handset = new CRSFHandset();
+}
+
+static int start()
+{
+ handset->Begin();
+#if defined(DEBUG_TX_FREERUN)
+ if (!handset->connect())
+ {
+ ERRLN("CRSF::connected has not been initialised");
+ }
+#endif
+ return DURATION_IMMEDIATELY;
+}
+
+static int timeout()
+{
+ handset->handleInput();
+ return DURATION_IMMEDIATELY;
+}
+
+static int event()
+{
+ // An event should be generated every time the TX power changes
+ CRSF::LinkStatistics.uplink_TX_Power = powerToCrsfPower(PowerLevelContainer::currPower());
+ return DURATION_IGNORE;
+}
+
+device_t Handset_device = {
+ .initialize = initialize,
+ .start = start,
+ .event = event,
+ .timeout = timeout};
+#endif
diff --git a/src/lib/Handset/devHandset.h b/src/lib/Handset/devHandset.h
new file mode 100644
index 0000000000..e08e4e6802
--- /dev/null
+++ b/src/lib/Handset/devHandset.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "device.h"
+
+extern device_t Handset_device;
\ No newline at end of file
diff --git a/src/lib/Handset/handset.h b/src/lib/Handset/handset.h
new file mode 100644
index 0000000000..42818e6a47
--- /dev/null
+++ b/src/lib/Handset/handset.h
@@ -0,0 +1,107 @@
+#pragma once
+
+#include "targets.h"
+
+/**
+ * @brief Abstract class that is extended to provide an interface to a handset.
+ *
+ * There are three implementations of the Handset class
+ *
+ * - CRSFHandset - implements the CRSF protocol for communicating with the handset
+ * - PPMHandset - PPM protocol, can be connected to the DSC/trainer port for simple non-CRSF handsets
+ * - AutoDetect - this implementation uses an RMT channel to auto-detect a PPM or CRSF handset and swap the
+ * global `handset` variable to point to instance of the actual implementation. This allows a TX module
+ * to be moved between a CRSF capable handset and PPM only handset e.g. an EdgeTX radio and a surface radio.
+ */
+class Handset
+{
+public:
+ /**
+ * @brief Start the handset protocol
+ */
+ virtual void Begin() = 0;
+
+ /**
+ * @brief End the handset protocol
+ */
+ virtual void End() = 0;
+
+ /**
+ * @brief register a function to be called when the protocol has read an RC data packet from the handset
+ * @param callback
+ */
+ void setRCDataCallback(void (*callback)()) { RCdataCallback = callback; }
+ /**
+ * @brief register a function to be called when a request to update a parameter is send from the handset
+ * @param callback
+ */
+ void registerParameterUpdateCallback(void (*callback)(uint8_t type, uint8_t index, uint8_t arg)) { RecvParameterUpdate = callback; }
+ /**
+ * Register callback functions for state information about the connection or handset
+ * @param connectedCallback called when the protocol detects a stable connection to the handset
+ * @param disconnectedCallback called when the protocol loses its connection to the handset
+ * @param RecvModelUpdateCallback called when the handset sends a message to set the current model number
+ */
+ void registerCallbacks(void (*connectedCallback)(), void (*disconnectedCallback)(), void (*RecvModelUpdateCallback)(), void (*bindingCommandCallback)())
+ {
+ connected = connectedCallback;
+ disconnected = disconnectedCallback;
+ RecvModelUpdate = RecvModelUpdateCallback;
+ OnBindingCommand = bindingCommandCallback;
+ }
+
+ /**
+ * @brief Process any pending input data from the handset
+ */
+ virtual void handleInput() = 0;
+
+ /**
+ * @return true if the protocol detects that the arming state is active
+ */
+ virtual bool IsArmed() = 0;
+
+ /**
+ * Called to set the expected packet interval from the handset.
+ * This can be used to synchronise the packets from the handset.
+ * @param PacketInterval in microseconds
+ */
+ virtual void setPacketInterval(int32_t PacketInterval) { RequestedRCpacketInterval = PacketInterval; }
+ /**
+ * @return the maximum number of bytes that the protocol can send to the handset in a single message
+ */
+ virtual uint8_t GetMaxPacketBytes() const { return 255; }
+
+ /**
+ * @brief Called to indicate to the protocol that a packet has just been sent over-the-air
+ * This is used to synchronise the packets from the handset to the OTA protocol to minimise latency
+ */
+ virtual void JustSentRFpacket() {}
+ /**
+ * Send a telemetry packet back to the handset
+ * @param data
+ */
+ virtual void sendTelemetryToTX(uint8_t *data) {}
+
+ /**
+ * @return the time in microseconds when the last RC packet was received from the handset
+ */
+ uint32_t GetRCdataLastRecv() const { return RCdataLastRecv; }
+
+protected:
+ virtual ~Handset() = default;
+
+ bool controllerConnected = false;
+ void (*RCdataCallback)() = nullptr; // called when there is new RC data
+ void (*disconnected)() = nullptr; // called when RC packet stream is lost
+ void (*connected)() = nullptr; // called when RC packet stream is regained
+ void (*RecvModelUpdate)() = nullptr; // called when model id changes, ie command from Radio
+ void (*RecvParameterUpdate)(uint8_t type, uint8_t index, uint8_t arg) = nullptr; // called when recv parameter update req, ie from LUA
+ void (*OnBindingCommand)() = nullptr; // Called when a binding command is received
+
+ volatile uint32_t RCdataLastRecv = 0;
+ int32_t RequestedRCpacketInterval = 5000; // default to 200hz as per 'normal'
+};
+
+#ifdef TARGET_TX
+extern Handset *handset;
+#endif
diff --git a/src/lib/LBT/LBT.cpp b/src/lib/LBT/LBT.cpp
index 147f7f7399..760d5d536b 100644
--- a/src/lib/LBT/LBT.cpp
+++ b/src/lib/LBT/LBT.cpp
@@ -3,8 +3,6 @@
#include "logging.h"
#include "LBT.h"
-extern SX1280Driver Radio;
-
LQCALC<100> LBTSuccessCalc;
static uint32_t rxStartTime;
@@ -13,7 +11,7 @@ static uint32_t rxStartTime;
#endif
bool LBTEnabled = false;
-static bool LBTStarted = false;
+static uint32_t validRSSIdelayUs = 0;
static uint32_t ICACHE_RAM_ATTR SpreadingFactorToRSSIvalidDelayUs(
SX1280_RadioLoRaSpreadingFactors_t SF,
@@ -29,13 +27,13 @@ static uint32_t ICACHE_RAM_ATTR SpreadingFactorToRSSIvalidDelayUs(
// SF5: 39.4us
// SF6: 78.8us
// SF7: 157.6us
- // SF9: 630.5us
+ // SF8: 315.2us
// However, by measuring when the RSSI reading is stable and valid, it was found that
// actual necessary wait times are:
// SF5 ~100us (60us + SF5 symbol time)
// SF6 ~141us (60us + SF6 symbol time)
// SF7 ~218us (60us + SF7 symbol time)
- // SF9 ~218us (Odd one out, measured to same as SF7 wait time)
+ // SF8 ~376us (60us + SF8 symbol time) Empirical testing shows 480us to be the sweet-spot
if (radio_type == RADIO_TYPE_SX128x_LORA)
{
@@ -44,8 +42,8 @@ static uint32_t ICACHE_RAM_ATTR SpreadingFactorToRSSIvalidDelayUs(
case SX1280_LORA_SF5: return 100;
case SX1280_LORA_SF6: return 141;
case SX1280_LORA_SF7: return 218;
- case SX1280_LORA_SF8: return 218;
- default: return 218;
+ case SX1280_LORA_SF8: return 480;
+ default: return 480;
}
}
else if (radio_type == RADIO_TYPE_SX128x_FLRC)
@@ -101,26 +99,20 @@ static int8_t ICACHE_RAM_ATTR PowerEnumToLBTLimit(PowerLevels_e txPower, uint8_t
void ICACHE_RAM_ATTR SetClearChannelAssessmentTime(void)
{
+ if (!LBTEnabled)
+ return;
+
rxStartTime = micros();
-}
+ validRSSIdelayUs = SpreadingFactorToRSSIvalidDelayUs((SX1280_RadioLoRaSpreadingFactors_t)ExpressLRS_currAirRate_Modparams->sf, ExpressLRS_currAirRate_Modparams->radio_type);
-void ICACHE_RAM_ATTR BeginClearChannelAssessment()
-{
- if(!LBTStarted)
- {
- Radio.RXnb(SX1280_MODE_RX_CONT);
- if (LBTEnabled)
- {
- rxStartTime = micros();
- LBTStarted = true;
- }
- }
+#if defined(TARGET_TX)
+ Radio.RXnb(SX1280_MODE_RX, validRSSIdelayUs);
+#endif
}
SX12XX_Radio_Number_t ICACHE_RAM_ATTR ChannelIsClear(SX12XX_Radio_Number_t radioNumber)
{
LBTSuccessCalc.inc(); // Increment count for every channel check
- LBTStarted = false;
if (!LBTEnabled)
{
@@ -141,21 +133,21 @@ SX12XX_Radio_Number_t ICACHE_RAM_ATTR ChannelIsClear(SX12XX_Radio_Number_t radio
// But for now, FHSShops and telemetry rates does not divide evenly, so telemetry will some times happen
// right after FHSS and we need wait here.
- uint32_t validRSSIdelayUs = SpreadingFactorToRSSIvalidDelayUs((SX1280_RadioLoRaSpreadingFactors_t)ExpressLRS_currAirRate_Modparams->sf,
- ExpressLRS_currAirRate_Modparams->radio_type);
uint32_t elapsed = micros() - rxStartTime;
if(elapsed < validRSSIdelayUs)
{
delayMicroseconds(validRSSIdelayUs - elapsed);
}
- int8_t rssiInst = 0;
+ int8_t rssiInst1 = 0;
+ int8_t rssiInst2 = 0;
SX12XX_Radio_Number_t clearChannelsMask = SX12XX_Radio_NONE;
+ int8_t rssiCutOff = PowerEnumToLBTLimit((PowerLevels_e)POWERMGNT::currPower(), ExpressLRS_currAirRate_Modparams->radio_type);
if (radioNumber & SX12XX_Radio_1)
{
- rssiInst = Radio.GetRssiInst(SX12XX_Radio_1);
- if(rssiInst < PowerEnumToLBTLimit((PowerLevels_e)POWERMGNT::currPower(), ExpressLRS_currAirRate_Modparams->radio_type))
+ rssiInst1 = Radio.GetRssiInst(SX12XX_Radio_1);
+ if(rssiInst1 < rssiCutOff)
{
clearChannelsMask |= SX12XX_Radio_1;
}
@@ -163,15 +155,15 @@ SX12XX_Radio_Number_t ICACHE_RAM_ATTR ChannelIsClear(SX12XX_Radio_Number_t radio
if (radioNumber & SX12XX_Radio_2)
{
- rssiInst = Radio.GetRssiInst(SX12XX_Radio_2);
- if(rssiInst < PowerEnumToLBTLimit((PowerLevels_e)POWERMGNT::currPower(), ExpressLRS_currAirRate_Modparams->radio_type))
+ rssiInst2 = Radio.GetRssiInst(SX12XX_Radio_2);
+ if(rssiInst2 < rssiCutOff)
{
clearChannelsMask |= SX12XX_Radio_2;
}
}
- // Useful to debug if and how long the rssi wait is, and rssi threshold level
- // DBGLN("wait: %d, rssi: %d, %s", validRSSIdelayUs - elapsed, rssiInst, clearChannelsMask ? "clear" : "in use");
+ // Useful to debug if and how long the rssi wait is, and rssi threshold rssiCutOff
+ // DBGLN("wait: %d, cutoff: %d, rssi: %d %d, %s", validRSSIdelayUs - elapsed, rssiCutOff, rssiInst1, rssiInst2, clearChannelsMask ? "clear" : "in use");
if(clearChannelsMask)
{
diff --git a/src/lib/LBT/LBT.h b/src/lib/LBT/LBT.h
index 73204a5172..a6e01e49a8 100644
--- a/src/lib/LBT/LBT.h
+++ b/src/lib/LBT/LBT.h
@@ -9,6 +9,5 @@ extern LQCALC<100> LBTSuccessCalc;
extern bool LBTEnabled;
void ICACHE_RAM_ATTR SetClearChannelAssessmentTime(void);
-void ICACHE_RAM_ATTR BeginClearChannelAssessment(void);
SX12XX_Radio_Number_t ICACHE_RAM_ATTR ChannelIsClear(SX12XX_Radio_Number_t radioNumber);
#endif
diff --git a/src/lib/LED/devLED.cpp b/src/lib/LED/devLED.cpp
index e8c1e93aba..7732ab1866 100644
--- a/src/lib/LED/devLED.cpp
+++ b/src/lib/LED/devLED.cpp
@@ -34,7 +34,6 @@
#define GPIO_LED_BLUE_INVERTED 0
#endif
-extern bool InBindingMode;
#if defined(TARGET_RX)
extern bool connectionHasModelMatch;
#endif
@@ -206,13 +205,13 @@ static int event()
if (GPIO_PIN_LED != UNDEF_PIN)
{
- if (connectionHasModelMatch)
+ if (!connectionHasModelMatch || !teamraceHasModelMatch)
{
- digitalWrite(GPIO_PIN_LED, HIGH ^ GPIO_LED_RED_INVERTED); // turn on led
+ return flashLED(GPIO_PIN_LED, GPIO_LED_RED_INVERTED, LEDSEQ_MODEL_MISMATCH, sizeof(LEDSEQ_MODEL_MISMATCH));
}
else
{
- return flashLED(GPIO_PIN_LED, GPIO_LED_RED_INVERTED, LEDSEQ_MODEL_MISMATCH, sizeof(LEDSEQ_MODEL_MISMATCH));
+ digitalWrite(GPIO_PIN_LED, HIGH ^ GPIO_LED_RED_INVERTED); // turn on led
}
}
#endif
diff --git a/src/lib/LED/devRGB.cpp b/src/lib/LED/devRGB.cpp
index b637a10b03..b4d1658499 100644
--- a/src/lib/LED/devRGB.cpp
+++ b/src/lib/LED/devRGB.cpp
@@ -36,8 +36,6 @@ static uint8_t *bootLEDs = statusLEDs;
#if (defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)) && defined(GPIO_PIN_LED_WS2812)
-#include
-
static uint8_t pixelCount;
static uint8_t *statusLEDs;
static uint8_t statusLEDcount;
@@ -47,26 +45,36 @@ static uint8_t *bootLEDs;
static uint8_t bootLEDcount;
#if defined(PLATFORM_ESP32)
- #define METHOD Neo800KbpsMethod
-#elif defined(PLATFORM_ESP8266)
- #define METHOD NeoEsp8266Uart1800KbpsMethod
-#endif
-
+#include "esp32rgb.h"
+static ESP32S3LedDriverGRB *stripgrb;
+static ESP32S3LedDriverRGB *striprgb;
+#else
+#include
+#define METHOD NeoEsp8266Uart1800KbpsMethod
static NeoPixelBus *stripgrb;
static NeoPixelBus *striprgb;
+#endif
void WS281Binit()
{
if (OPT_WS2812_IS_GRB)
{
+#if defined(PLATFORM_ESP32)
+ stripgrb = new ESP32S3LedDriverGRB(pixelCount, GPIO_PIN_LED_WS2812);
+#else
stripgrb = new NeoPixelBus(pixelCount, GPIO_PIN_LED_WS2812);
+#endif
stripgrb->Begin();
stripgrb->ClearTo(RgbColor(0), 0, pixelCount-1);
stripgrb->Show();
}
else
{
+#if defined(PLATFORM_ESP32)
+ striprgb = new ESP32S3LedDriverRGB(pixelCount, GPIO_PIN_LED_WS2812);
+#else
striprgb = new NeoPixelBus (pixelCount, GPIO_PIN_LED_WS2812);
+#endif
striprgb->Begin();
striprgb->ClearTo(RgbColor(0), 0, pixelCount-1);
striprgb->Show();
@@ -77,13 +85,11 @@ void WS281BsetLED(int index, uint32_t color)
{
if (OPT_WS2812_IS_GRB)
{
- stripgrb->SetPixelColor(index, RgbColor(HtmlColor(color)));
- stripgrb->Show();
+ stripgrb->SetPixelColor(index, RgbColor(color >> 16, color >> 8, color));
}
else
{
- striprgb->SetPixelColor(index, RgbColor(HtmlColor(color)));
- striprgb->Show();
+ striprgb->SetPixelColor(index, RgbColor(color >> 16, color >> 8, color));
}
}
@@ -93,11 +99,11 @@ void WS281BsetLED(uint32_t color)
{
if (OPT_WS2812_IS_GRB)
{
- stripgrb->SetPixelColor(statusLEDs[i], RgbColor(HtmlColor(color)));
+ stripgrb->SetPixelColor(statusLEDs[i], RgbColor(color >> 16, color >> 8, color));
}
else
{
- striprgb->SetPixelColor(statusLEDs[i], RgbColor(HtmlColor(color)));
+ striprgb->SetPixelColor(statusLEDs[i], RgbColor(color >> 16, color >> 8, color));
}
}
if (OPT_WS2812_IS_GRB)
@@ -111,11 +117,6 @@ void WS281BsetLED(uint32_t color)
}
#endif
-#if defined(TARGET_RX)
-extern bool InBindingMode;
-extern bool connectionHasModelMatch;
-#endif
-
typedef struct {
uint8_t h, s, v;
} blinkyColor_t;
@@ -305,13 +306,14 @@ static int blinkyUpdate() {
for (int i=0 ; iSetPixelColor(bootLEDs[i], RgbColor(HtmlColor(HsvToRgb(c))));
+ stripgrb->SetPixelColor(bootLEDs[i], RgbColor(color >> 16, color >> 8, color));
}
else
{
- striprgb->SetPixelColor(bootLEDs[i], RgbColor(HtmlColor(HsvToRgb(c))));
+ striprgb->SetPixelColor(bootLEDs[i], RgbColor(color >> 16, color >> 8, color));
}
}
if (OPT_WS2812_IS_GRB)
@@ -333,18 +335,24 @@ static int blinkyUpdate() {
if (OPT_WS2812_IS_GRB)
{
stripgrb->ClearTo(RgbColor(0), 0, pixelCount-1);
- stripgrb->Show();
}
else
{
striprgb->ClearTo(RgbColor(0), 0, pixelCount-1);
- striprgb->Show();
}
}
- #endif
#if defined(TARGET_TX)
setButtonColors(config.GetButtonActions(0)->val.color, config.GetButtonActions(1)->val.color);
#endif
+ if (OPT_WS2812_IS_GRB)
+ {
+ stripgrb->Show();
+ }
+ else
+ {
+ striprgb->Show();
+ }
+ #endif
return NORMAL_UPDATE_INTERVAL;
}
blinkyColor.v -= lightnessStep;
@@ -449,7 +457,7 @@ static int timeout()
{
case connected:
#if defined(TARGET_RX)
- if (!connectionHasModelMatch)
+ if (!connectionHasModelMatch || !teamraceHasModelMatch)
{
blinkyColor.h = 10;
return flashLED(blinkyColor, 192, 0, LEDSEQ_MODEL_MISMATCH, sizeof(LEDSEQ_MODEL_MISMATCH));
diff --git a/src/lib/LED/esp32rgb.cpp b/src/lib/LED/esp32rgb.cpp
new file mode 100644
index 0000000000..f90fcb5073
--- /dev/null
+++ b/src/lib/LED/esp32rgb.cpp
@@ -0,0 +1,119 @@
+#include "targets.h"
+
+#if defined(PLATFORM_ESP32)
+
+#include "logging.h"
+
+#include
+#include
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_system.h"
+#include "driver/i2s.h"
+#include "esp32rgb.h"
+
+#define I2S_NUM i2s_port_t(0)
+
+#if defined(CONFIG_IDF_TARGET_ESP32S2)
+#define SAMPLE_RATE (360000)
+#define MCLK 48000000
+#elif defined(CONFIG_IDF_TARGET_ESP32S3)
+#define SAMPLE_RATE (800000)
+#define MCLK 160000000
+#elif defined(CONFIG_IDF_TARGET_ESP32)
+#define SAMPLE_RATE (360000)
+#define MCLK 48000000
+#endif
+
+ESP32S3LedDriver::ESP32S3LedDriver(int count, int pin) : num_leds(count), gpio_pin(pin)
+{
+ out_buffer_size = num_leds * 24 * sizeof(uint16_t);
+ out_buffer = (uint16_t *)heap_caps_malloc(out_buffer_size, MALLOC_CAP_8BIT);
+}
+
+ESP32S3LedDriver::~ESP32S3LedDriver()
+{
+ heap_caps_free(out_buffer);
+}
+
+void ESP32S3LedDriver::Begin()
+{
+ i2s_config_t i2s_config = {
+ .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_TX),
+ .sample_rate = SAMPLE_RATE,
+ .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
+ .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
+ .communication_format = I2S_COMM_FORMAT_STAND_I2S,
+ .intr_alloc_flags = 0,
+ .dma_buf_count = 4,
+ .use_apll = true,
+ .tx_desc_auto_clear = true,
+ .fixed_mclk = MCLK,
+ };
+
+ i2s_pin_config_t pin_config = {
+ .bck_io_num = -1,
+ .ws_io_num = -1,
+ .data_out_num = gpio_pin,
+ .data_in_num = -1,
+ };
+
+ i2s_config.dma_buf_len = out_buffer_size;
+ i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
+ delay(1); // without this it fails to boot and gets stuck!
+ i2s_set_pin(I2S_NUM, &pin_config);
+ i2s_zero_dma_buffer(I2S_NUM);
+ i2s_stop(I2S_NUM);
+}
+
+void ESP32S3LedDriver::Show()
+{
+ size_t bytes_written = 0;
+ i2s_stop(I2S_NUM);
+ i2s_write(I2S_NUM, out_buffer, out_buffer_size, &bytes_written, portMAX_DELAY);
+ i2s_start(I2S_NUM);
+}
+
+void ESP32S3LedDriver::ClearTo(RgbColor color, uint16_t first, uint16_t last)
+{
+ for (uint16_t i=first ; i<=last; i++)
+ {
+ SetPixelColor(i, color);
+ }
+}
+
+
+#if defined(CONFIG_IDF_TARGET_ESP32S2)
+static const int bitorder[] = {0x40, 0x80, 0x10, 0x20, 0x04, 0x08, 0x01, 0x02};
+#elif defined(CONFIG_IDF_TARGET_ESP32S3)
+static const int bitorder[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
+#elif defined(CONFIG_IDF_TARGET_ESP32)
+static const int bitorder[] = {0x40, 0x80, 0x10, 0x20, 0x04, 0x08, 0x01, 0x02};
+#endif
+
+void ESP32S3LedDriverGRB::SetPixelColor(uint16_t indexPixel, RgbColor color)
+{
+ int loc = indexPixel * 24;
+ for(int bitpos = 0 ; bitpos < 8 ; bitpos++)
+ {
+ int bit = bitorder[bitpos];
+ out_buffer[loc + bitpos + 0] = (color.G & bit) ? 0xFFE0 : 0xF000;
+ out_buffer[loc + bitpos + 8] = (color.R & bit) ? 0xFFE0 : 0xF000;
+ out_buffer[loc + bitpos + 16] = (color.B & bit) ? 0xFFE0 : 0xF000;
+ }
+}
+
+void ESP32S3LedDriverRGB::SetPixelColor(uint16_t indexPixel, RgbColor color)
+{
+ int loc = indexPixel * 24;
+ for(int bitpos = 0 ; bitpos < 8 ; bitpos++)
+ {
+ int bit = bitorder[bitpos];
+ out_buffer[loc + bitpos + 0] = (color.R & bit) ? 0xFFE0 : 0xF000;
+ out_buffer[loc + bitpos + 8] = (color.G & bit) ? 0xFFE0 : 0xF000;
+ out_buffer[loc + bitpos + 16] = (color.B & bit) ? 0xFFE0 : 0xF000;
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/lib/LED/esp32rgb.h b/src/lib/LED/esp32rgb.h
new file mode 100644
index 0000000000..af7a3c9e49
--- /dev/null
+++ b/src/lib/LED/esp32rgb.h
@@ -0,0 +1,48 @@
+#include
+
+class RgbColor
+{
+public:
+ RgbColor(uint8_t r, uint8_t g, uint8_t b) : R(r), G(g), B(b) {}
+ explicit RgbColor(uint8_t brightness) : R(brightness), G(brightness), B(brightness) {}
+
+ uint8_t R;
+ uint8_t G;
+ uint8_t B;
+};
+
+class ESP32S3LedDriver
+{
+public:
+ ESP32S3LedDriver(int count, int pin);
+ ~ESP32S3LedDriver();
+
+ void Begin();
+ void Show();
+ void ClearTo(RgbColor color, uint16_t first, uint16_t last);
+ virtual void SetPixelColor(uint16_t indexPixel, RgbColor color) = 0;
+
+private:
+ RgbColor *ledsbuff = nullptr;
+ uint16_t *out_buffer = nullptr;
+ size_t out_buffer_size;
+ int num_leds;
+ int gpio_pin;
+
+ friend class ESP32S3LedDriverGRB;
+ friend class ESP32S3LedDriverRGB;
+};
+
+class ESP32S3LedDriverGRB : public ESP32S3LedDriver
+{
+public:
+ ESP32S3LedDriverGRB(int count, int pin) : ESP32S3LedDriver(count, pin) {}
+ void SetPixelColor(uint16_t indexPixel, RgbColor color) override;
+};
+
+class ESP32S3LedDriverRGB : public ESP32S3LedDriver
+{
+public:
+ ESP32S3LedDriverRGB(int count, int pin) : ESP32S3LedDriver(count, pin) {}
+ void SetPixelColor(uint16_t indexPixel, RgbColor color) override;
+};
diff --git a/src/lib/LQCALC/LQCALC.h b/src/lib/LQCALC/LQCALC.h
index e44169e792..c859ff31f9 100644
--- a/src/lib/LQCALC/LQCALC.h
+++ b/src/lib/LQCALC/LQCALC.h
@@ -8,10 +8,7 @@ class LQCALC
public:
LQCALC(void)
{
- reset();
- // count is reset here only once on construction to start LQ counting
- // at 100% on first connect, but 0 out of N after a failsafe
- count = 1;
+ reset100();
}
/* Set the bit for the current period to true and update the running LQ */
@@ -80,7 +77,7 @@ class LQCALC
void reset()
{
// count is intentonally not zeroed here to start LQ counting up from 0
- // after a failsafe, instead of down from 100
+ // after a failsafe, instead of down from 100. Use reset100() to start from 100
LQ = 0;
index = 0;
LQmask = (1 << 0);
@@ -88,6 +85,13 @@ class LQCALC
LQArray[i] = 0;
}
+ /* Reset and start at 100% */
+ void reset100()
+ {
+ reset();
+ count = 1;
+ }
+
/* Return true if the current period was add()ed */
bool ICACHE_RAM_ATTR currentIsSet() const
{
diff --git a/src/lib/LR1121Driver/LR1121.cpp b/src/lib/LR1121Driver/LR1121.cpp
new file mode 100644
index 0000000000..ef128ad46b
--- /dev/null
+++ b/src/lib/LR1121Driver/LR1121.cpp
@@ -0,0 +1,746 @@
+#include "LR1121_Regs.h"
+#include "LR1121_hal.h"
+#include "LR1121.h"
+#include "logging.h"
+#include
+
+LR1121Hal hal;
+LR1121Driver *LR1121Driver::instance = NULL;
+
+//DEBUG_LR1121_OTA_TIMING
+
+#if defined(DEBUG_LR1121_OTA_TIMING)
+static uint32_t beginTX;
+static uint32_t endTX;
+#endif
+
+// RxTimeout is expressed in periods of the 32.768kHz RTC
+#define RX_TIMEOUT_PERIOD_BASE_NANOS 1000000000 / 32768 // TODO check for LR1121
+
+#ifdef USE_HARDWARE_DCDC
+ #ifndef OPT_USE_HARDWARE_DCDC
+ #define OPT_USE_HARDWARE_DCDC true
+ #endif
+#else
+ #define OPT_USE_HARDWARE_DCDC false
+#endif
+
+// This define refers to the High Frequency output on the SX1276. But has been reused/repruposed for the LR1121.
+// In ELRS V4 it should be changed to USE_RADIO_RFO_LP and refer to using the Low Power radio frequency output
+// of both the SX1276 and LR1121.
+#ifdef USE_SX1276_RFO_HF
+ #ifndef OPT_USE_SX1276_RFO_HF
+ #define OPT_USE_SX1276_RFO_HF true
+ #endif
+#else
+ #define OPT_USE_SX1276_RFO_HF false
+#endif
+
+LR1121Driver::LR1121Driver(): SX12xxDriverCommon()
+{
+ instance = this;
+ timeout = 0xFFFFFF;
+ lastSuccessfulPacketRadio = SX12XX_Radio_1;
+ fallBackMode = LR1121_MODE_FS;
+ ignoreSecondIRQ = false;
+}
+
+void LR1121Driver::End()
+{
+ SetMode(LR1121_MODE_SLEEP, SX12XX_Radio_All);
+ hal.end();
+ RemoveCallbacks();
+}
+
+bool LR1121Driver::Begin(uint32_t minimumFrequency, uint32_t maximumFrequency)
+{
+ hal.init();
+ hal.IsrCallback_1 = &LR1121Driver::IsrCallback_1;
+ hal.IsrCallback_2 = &LR1121Driver::IsrCallback_2;
+
+ hal.reset();
+
+ // Validate that the LR1121 is working.
+ uint8_t version[5] = {0};
+ hal.WriteCommand(LR11XX_SYSTEM_GET_VERSION_OC, SX12XX_Radio_1);
+ hal.ReadCommand(version, sizeof(version), SX12XX_Radio_1);
+
+ DBGLN("Read LR1121 #1 Use Case (0x03 = LR1121): %d", version[2]);
+ if (version[2] != 0x03)
+ {
+ DBGLN("LR1121 #1 failed to be detected.");
+ return false;
+ }
+ DBGLN("LR1121 #1 Ready");
+
+ if (GPIO_PIN_NSS_2 != UNDEF_PIN)
+ {
+ // Validate that the LR1121 #2 is working.
+ memset(version, 0, sizeof(version));
+ hal.WriteCommand(LR11XX_SYSTEM_GET_VERSION_OC, SX12XX_Radio_2);
+ hal.ReadCommand(version, sizeof(version), SX12XX_Radio_2);
+
+ DBGLN("Read LR1121 #2 Use Case (0x03 = LR1121): %d", version[2]);
+ if (version[2] != 0x03)
+ {
+ DBGLN("LR1121 #2 failed to be detected.");
+ return false;
+ }
+ DBGLN("LR1121 #2 Ready");
+ }
+
+ //Clear Errors
+ hal.WriteCommand(LR11XX_SYSTEM_CLEAR_ERRORS_OC, SX12XX_Radio_All); // Remove later? Might not be required???
+
+/*
+Do not enable for dual radio TX.
+When AUTOFS is set and tlm received by only 1 of the 2 radios, that radio will go into FS mode and the other
+into Standby mode. After the following SPI command for tx mode, busy will go high for differing periods of time because 1 is
+transitioning from FS mode and the other from Standby mode. This causes the tx done dio of the 2 radios to occur at very different times.
+*/
+ // 7.2.5 SetRxTxFallbackMode
+ uint8_t FBbuf[1] = {LR11XX_RADIO_FALLBACK_FS};
+ fallBackMode = LR1121_MODE_FS;
+ hal.WriteCommand(LR11XX_RADIO_SET_RX_TX_FALLBACK_MODE_OC, FBbuf, sizeof(FBbuf), SX12XX_Radio_All);
+
+ // 7.2.12 SetRxBoosted
+ uint8_t abuf[1] = {1};
+ hal.WriteCommand(LR11XX_RADIO_SET_RX_BOOSTED_OC, abuf, sizeof(abuf), SX12XX_Radio_All);
+
+ SetDioAsRfSwitch();
+ SetDioIrqParams();
+
+#if defined(USE_HARDWARE_DCDC)
+ if (OPT_USE_HARDWARE_DCDC)
+ {
+ // 5.1.1 SetRegMode
+ uint8_t RegMode[1] = {1};
+ hal.WriteCommand(LR11XX_SYSTEM_SET_REGMODE_OC, RegMode, sizeof(RegMode), SX12XX_Radio_All); // Enable DCDC converter instead of LDO
+ }
+#endif
+
+ // 2.1.3.1 CalibImage
+ uint8_t CalImagebuf[2];
+ CalImagebuf[0] = ((minimumFrequency / 1000000 ) - 1) / 4; // Freq1 = floor( (fmin_mhz - 1)/4)
+ CalImagebuf[1] = 1 + ((maximumFrequency / 1000000 ) + 1) / 4; // Freq2 = ceil( (fmax_mhz + 1)/4)
+ hal.WriteCommand(LR11XX_SYSTEM_CALIBRATE_IMAGE_OC, CalImagebuf, sizeof(CalImagebuf), SX12XX_Radio_All);
+
+ return true;
+}
+
+void LR1121Driver::startCWTest(uint32_t freq, SX12XX_Radio_Number_t radioNumber)
+{
+ // SetFrequencyHz(freq, radioNumber);
+ // CommitOutputPower();
+ // hal.TXenable(radioNumber);
+ // hal.WriteCommand(0x0219, radioNumber);
+}
+
+void LR1121Driver::Config(uint8_t bw, uint8_t sf, uint8_t cr, uint32_t regfreq,
+ uint8_t PreambleLength, bool InvertIQ, uint8_t _PayloadLength, uint32_t interval, SX12XX_Radio_Number_t radioNumber)
+{
+ DBGLN("Config LoRa ");
+ PayloadLength = _PayloadLength;
+
+ bool isSubGHz = regfreq < 1000000000;
+
+ if (radioNumber & SX12XX_Radio_1)
+ radio1isSubGHz = isSubGHz;
+
+ if (radioNumber & SX12XX_Radio_2)
+ radio2isSubGHz = isSubGHz;
+
+ IQinverted = InvertIQ ? LR11XX_RADIO_LORA_IQ_INVERTED : LR11XX_RADIO_LORA_IQ_STANDARD;
+ // IQinverted is always STANDARD for 900 and SX1276
+ if (isSubGHz)
+ {
+ IQinverted = LR11XX_RADIO_LORA_IQ_STANDARD;
+ }
+
+ SetRxTimeoutUs(interval);
+
+ SetMode(LR1121_MODE_STDBY_RC, radioNumber);
+
+ // 8.1.1 SetPacketType
+ uint8_t buf[1] = {LR11XX_RADIO_PKT_TYPE_LORA};
+ hal.WriteCommand(LR11XX_RADIO_SET_PKT_TYPE_OC, buf, sizeof(buf), radioNumber);
+
+ ConfigModParamsLoRa(bw, sf, cr, radioNumber);
+
+#if defined(DEBUG_FREQ_CORRECTION) // TODO Check if this available with the LR1121?
+ lr11xx_RadioLoRaPacketLengthsModes_t packetLengthType = LR1121_LORA_PACKET_VARIABLE_LENGTH;
+#else
+ lr11xx_RadioLoRaPacketLengthsModes_t packetLengthType = LR1121_LORA_PACKET_FIXED_LENGTH;
+#endif
+
+ SetPacketParamsLoRa(PreambleLength, packetLengthType, _PayloadLength, IQinverted, radioNumber);
+
+ SetFrequencyHz(regfreq, radioNumber);
+
+ pwrForceUpdate = true; // Must be called after changing rf modes between subG and 2.4G. This sets the correct rf amps, and txen pins to be used.
+}
+
+void LR1121Driver::SetDioAsRfSwitch()
+{
+ // 4.2.1 SetDioAsRfSwitch
+ uint8_t switchbuf[8];
+ switchbuf[0] = 0b00001111; // RfswEnable
+ switchbuf[1] = 0b00000000; // RfSwStbyCfg
+ switchbuf[2] = 0b00000100; // RfSwRxCfg
+ switchbuf[3] = 0b00001000; // RfSwTxCfg
+ switchbuf[4] = 0b00001000; // RfSwTxHPCfg
+ switchbuf[5] = 0b00000010; // RfSwTxHfCfg
+ switchbuf[6] = 0; //
+ switchbuf[7] = 0b00000001; // RfSwWifiCfg - Each bit indicates the state of the relevant RFSW DIO when in Wi-Fi scanning mode or high frequency RX mode (LR1110_H1_UM_V1-7-1.pdf)
+ hal.WriteCommand(LR11XX_SYSTEM_SET_DIO_AS_RF_SWITCH_OC, switchbuf, sizeof(switchbuf), SX12XX_Radio_All);
+}
+
+void LR1121Driver::SetRxTimeoutUs(uint32_t interval)
+{
+ timeout = 0xFFFFFF; // no timeout, continuous mode
+ if (interval)
+ {
+ timeout = interval * 1000 / RX_TIMEOUT_PERIOD_BASE_NANOS;
+ }
+}
+
+void LR1121Driver::CorrectRegisterForSF6(uint8_t sf, SX12XX_Radio_Number_t radioNumber)
+{
+ // 8.3.1 SetModulationParams
+ // - SF6 can be made compatible with the SX127x family in implicit mode via a register setting1.
+ // - Set bit 18 of register at address 0xf20414 to 1
+ // - Set bit 23 of register at address 0xf20414 to 0. This information is from Semecth in an email.
+ // 3.7.3 WriteRegMemMask32
+
+ if ((lr11xx_radio_lora_sf_t)sf == LR11XX_RADIO_LORA_SF6)
+ {
+ uint8_t wrbuf[12];
+ // Address
+ wrbuf[0] = 0x00; // MSB
+ wrbuf[1] = 0xf2;
+ wrbuf[2] = 0x04;
+ wrbuf[3] = 0x14;
+ // Mask
+ wrbuf[4] = 0x00; // MSB
+ wrbuf[5] = 0b10000100; // bit18=1 and bit23=0
+ wrbuf[6] = 0x00;
+ wrbuf[7] = 0x00;
+ // Data
+ wrbuf[8] = 0x00; // MSB
+ wrbuf[9] = 0b00000100; // bit18=1 and bit23=0
+ wrbuf[10] = 0x00;
+ wrbuf[11] = 0x00;
+ hal.WriteCommand(LR11XX_REGMEM_WRITE_REGMEM32_MASK_OC, wrbuf, sizeof(wrbuf), radioNumber);
+ }
+}
+
+/***
+ * @brief: Schedule an output power change after the next transmit
+ ***/
+void LR1121Driver::SetOutputPower(int8_t power, bool isSubGHz)
+{
+ uint8_t pwrNew;
+
+ if (isSubGHz)
+ {
+ if (OPT_USE_SX1276_RFO_HF)
+ {
+ pwrNew = constrain(power, LR1121_POWER_MIN_LP_PA, LR1121_POWER_MAX_LP_PA);
+ }
+ else
+ {
+ pwrNew = constrain(power, LR1121_POWER_MIN_HP_PA, LR1121_POWER_MAX_HP_PA);
+ }
+
+ if ((pwrPendingLF == PWRPENDING_NONE && pwrCurrentLF != pwrNew) || pwrPendingLF != pwrNew)
+ {
+ pwrPendingLF = pwrNew;
+ }
+ }
+ else
+ {
+ pwrNew = constrain(power, LR1121_POWER_MIN_HF_PA, LR1121_POWER_MAX_HF_PA);
+
+ if ((pwrPendingHF == PWRPENDING_NONE && pwrCurrentHF != pwrNew) || pwrPendingHF != pwrNew)
+ {
+ pwrPendingHF = pwrNew;
+ }
+ }
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::CommitOutputPower()
+{
+ if (pwrPendingLF != PWRPENDING_NONE)
+ {
+ pwrCurrentLF = pwrPendingLF;
+ pwrPendingLF = PWRPENDING_NONE;
+ pwrForceUpdate = true;
+ }
+
+ if (pwrPendingHF != PWRPENDING_NONE)
+ {
+ pwrCurrentHF = pwrPendingHF;
+ pwrPendingHF = PWRPENDING_NONE;
+ pwrForceUpdate = true;
+ }
+
+ if (pwrForceUpdate)
+ {
+ WriteOutputPower(radio1isSubGHz ? pwrCurrentLF : pwrCurrentHF, radio1isSubGHz, SX12XX_Radio_1);
+ WriteOutputPower(radio2isSubGHz ? pwrCurrentLF : pwrCurrentHF, radio2isSubGHz, SX12XX_Radio_2);
+ pwrForceUpdate = false;
+ }
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::WriteOutputPower(uint8_t power, bool isSubGHz, SX12XX_Radio_Number_t radioNumber)
+{
+ uint8_t Pabuf[4] = {0};
+ uint8_t Txbuf[2] = {power, LR11XX_RADIO_RAMP_48_US};
+
+ // 9.5.1 SetPaConfig
+ // 9.5.2 SetTxParams
+ if (isSubGHz)
+ {
+ // 900M low power RF Amp
+ // Table 9-1: Optimized Settings for LP PA with Same Matching Network
+ // -17dBm (0xEF) to +14dBm (0x0E) by steps of 1dB if the low power PA is selected
+ if (OPT_USE_SX1276_RFO_HF)
+ {
+ Pabuf[0] = LR11XX_RADIO_PA_SEL_LP; // PaSel - 0x01: Selects the high power PA
+ Pabuf[1] = LR11XX_RADIO_PA_REG_SUPPLY_VREG; // RegPaSupply - 0x01: Powers the PA from VBAT. The user must use RegPaSupply = 0x01 whenever TxPower > 14
+ Pabuf[2] = 0x07; // PaDutyCycle
+ }
+ // 900M high power RF Amp
+ // Table 9-2: Optimized Settings for HP PA with Same Matching Network
+ // -9dBm (0xF7) to +22dBm (0x16) by steps of 1dB if the high power PA is selected
+ else
+ {
+ Pabuf[0] = LR11XX_RADIO_PA_SEL_HP; // PaSel - 0x01: Selects the high power PA
+ Pabuf[1] = LR11XX_RADIO_PA_REG_SUPPLY_VBAT; // RegPaSupply - 0x01: Powers the PA from VBAT. The user must use RegPaSupply = 0x01 whenever TxPower > 14
+ Pabuf[2] = 0x04; // PaDutyCycle
+ Pabuf[3] = 0x07; // PaHPSel - In order to reach +22dBm output power, PaHPSel must be set to 7. PaHPSel has no impact on either the low power PA or the high frequency PA.
+ }
+ }
+ // 2.4G RF Amp
+ // Table 9-3: Optimized Settings for HF PA with the Same Matching Network
+ // -18dBm (0xEE) to +13dBm (0x0D) by steps of 1dB if the high frequency PA is selected
+ else
+ {
+ Pabuf[0] = LR11XX_RADIO_PA_SEL_HF; // PaSel - 0x02: Selects the high frequency PA
+ Pabuf[1] = LR11XX_RADIO_PA_REG_SUPPLY_VREG; // RegPaSupply - 0x01: Powers the PA from VBAT. The user must use RegPaSupply = 0x01 whenever TxPower > 14
+ Pabuf[2] = 0x00; // PaDutyCycle
+ Pabuf[3] = 0x00; // PaHPSel - In order to reach +22dBm output power, PaHPSel must be set to 7. PaHPSel has no impact on either the low power PA or the high frequency PA.
+ }
+
+ hal.WriteCommand(LR11XX_RADIO_SET_PA_CFG_OC, Pabuf, sizeof(Pabuf), radioNumber);
+ hal.WriteCommand(LR11XX_RADIO_SET_TX_PARAMS_OC, Txbuf, sizeof(Txbuf), radioNumber);
+}
+
+void LR1121Driver::SetMode(lr11xx_RadioOperatingModes_t OPmode, SX12XX_Radio_Number_t radioNumber)
+{
+ WORD_ALIGNED_ATTR uint8_t buf[5] = {0};
+ switch (OPmode)
+ {
+ case LR1121_MODE_SLEEP:
+ // 2.1.5.1 SetSleep
+ hal.WriteCommand(LR11XX_SYSTEM_SET_SLEEP_OC, buf, 5, radioNumber);
+ break;
+
+ case LR1121_MODE_STDBY_RC:
+ //2.1.2.1 SetStandby
+ buf[0] = 0x00;
+ hal.WriteCommand(LR11XX_SYSTEM_SET_STANDBY_OC, buf, 1, radioNumber);
+ break;
+
+ case LR1121_MODE_STDBY_XOSC:
+ //2.1.2.1 SetStandby
+ buf[0] = 0x01;
+ hal.WriteCommand(LR11XX_SYSTEM_SET_STANDBY_OC, buf, 1, radioNumber);
+ break;
+
+ case LR1121_MODE_FS:
+ // 2.1.9.1 SetFs
+ hal.WriteCommand(LR11XX_SYSTEM_SET_FS_OC, radioNumber);
+ break;
+
+ case LR1121_MODE_RX:
+ // 7.2.2 SetRx
+ buf[0] = timeout >> 16;
+ buf[1] = timeout >> 8;
+ buf[2] = timeout & 0xFF;
+ hal.WriteCommand(LR11XX_RADIO_SET_RX_OC, buf, 3, radioNumber);
+ break;
+
+ case LR1121_MODE_RX_CONT:
+ // 7.2.2 SetRx
+ buf[0] = 0xFF;
+ buf[1] = 0xFF;
+ buf[2] = 0xFF;
+ hal.WriteCommand(LR11XX_RADIO_SET_RX_OC, buf, 3, radioNumber);
+ break;
+
+ case LR1121_MODE_TX:
+ // Table 7-3: SetTx Command
+ hal.WriteCommand(LR11XX_RADIO_SET_TX_OC, buf, 3, radioNumber);
+ break;
+
+ case LR1121_MODE_CAD:
+ break;
+
+ default:
+ break;
+ }
+}
+
+void LR1121Driver::ConfigModParamsLoRa(uint8_t bw, uint8_t sf, uint8_t cr, SX12XX_Radio_Number_t radioNumber)
+{
+ // 8.3.1 SetModulationParams
+ uint8_t buf[4];
+ buf[0] = sf;
+ buf[1] = bw;
+ buf[2] = cr;
+ buf[3] = 0x00; // 0x00: LowDataRateOptimize off
+ hal.WriteCommand(LR11XX_RADIO_SET_MODULATION_PARAM_OC, buf, sizeof(buf), radioNumber);
+
+ if (radioNumber & SX12XX_Radio_1 && radio1isSubGHz)
+ CorrectRegisterForSF6(sf, SX12XX_Radio_1);
+
+ if (radioNumber & SX12XX_Radio_2 && radio2isSubGHz)
+ CorrectRegisterForSF6(sf, SX12XX_Radio_2);
+}
+
+void LR1121Driver::SetPacketParamsLoRa(uint8_t PreambleLength, lr11xx_RadioLoRaPacketLengthsModes_t HeaderType,
+ uint8_t PayloadLength, uint8_t InvertIQ, SX12XX_Radio_Number_t radioNumber)
+{
+ // 8.3.2 SetPacketParams
+ uint8_t buf[6];
+ buf[0] = 0; // MSB PbLengthTX defines the length of the LoRa packet preamble. Minimum of 12 with SF5 and SF6, and of 8 for other SF advised;
+ buf[1] = PreambleLength; // LSB PbLengthTX defines the length of the LoRa packet preamble. Minimum of 12 with SF5 and SF6, and of 8 for other SF advised;
+ buf[2] = HeaderType; // HeaderType defines if the header is explicit or implicit
+ buf[3] = PayloadLength; // PayloadLen defines the size of the payload
+ buf[4] = LR11XX_RADIO_LORA_CRC_OFF;
+ buf[5] = InvertIQ;
+ hal.WriteCommand(LR11XX_RADIO_SET_PKT_PARAM_OC, buf, sizeof(buf), radioNumber);
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::SetFrequencyHz(uint32_t freq, SX12XX_Radio_Number_t radioNumber)
+{
+ // 7.2.1 SetRfFrequency
+ uint8_t buf[4];
+ buf[0] = freq >> 24;
+ buf[1] = freq >> 16;
+ buf[2] = freq >> 8;
+ buf[3] = freq & 0xFF;
+ hal.WriteCommand(LR11XX_RADIO_SET_RF_FREQUENCY_OC, buf, sizeof(buf), radioNumber);
+
+ currFreq = freq;
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::SetFrequencyReg(uint32_t freq, SX12XX_Radio_Number_t radioNumber)
+{
+ SetFrequencyHz(freq, radioNumber);
+}
+
+// 4.1.1 SetDioIrqParams
+void LR1121Driver::SetDioIrqParams()
+{
+ uint8_t buf[8] = {0};
+ buf[3] = LR1121_IRQ_TX_DONE | LR1121_IRQ_RX_DONE;
+ hal.WriteCommand(LR11XX_SYSTEM_SET_DIOIRQPARAMS_OC, buf, sizeof(buf), SX12XX_Radio_All);
+}
+
+// 3.4.1 GetStatus
+uint32_t ICACHE_RAM_ATTR LR1121Driver::GetIrqStatus(SX12XX_Radio_Number_t radioNumber)
+{
+ uint8_t status[6] = {0};
+ status[0] = LR11XX_SYSTEM_CLEAR_IRQ_OC >> 8;
+ status[1] = LR11XX_SYSTEM_CLEAR_IRQ_OC & 0xFF;
+ status[2] = 0xFF;
+ status[3] = 0xFF;
+ status[4] = 0xFF;
+ status[5] = 0xFF;
+ hal.ReadCommand(status, sizeof(status), radioNumber);
+ return status[2] << 24 | status[3] << 16 | status[4] << 8 | status[5];
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::ClearIrqStatus(SX12XX_Radio_Number_t radioNumber)
+{
+ uint8_t buf[4];
+ buf[0] = 0xFF;
+ buf[1] = 0xFF;
+ buf[2] = 0xFF;
+ buf[3] = 0xFF;
+ hal.WriteCommand(LR11XX_SYSTEM_CLEAR_IRQ_OC, buf, sizeof(buf), radioNumber);
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::TXnbISR()
+{
+#ifdef DEBUG_LR1121_OTA_TIMING
+ endTX = micros();
+ DBGLN("TOA: %d", endTX - beginTX);
+#endif
+ CommitOutputPower();
+ TXdoneCallback();
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::TXnb(uint8_t * data, uint8_t size, SX12XX_Radio_Number_t radioNumber)
+{
+ transmittingRadio = radioNumber;
+
+ // //catch TX timeout
+ // if (currOpmode == SX1280_MODE_TX)
+ // {
+ // DBGLN("Timeout!");
+ // SetMode(SX1280_MODE_FS, SX12XX_Radio_All);
+ // ClearIrqStatus(SX1280_IRQ_RADIO_ALL, SX12XX_Radio_All);
+ // TXnbISR();
+ // return;
+ // }
+
+ if (radioNumber == SX12XX_Radio_NONE)
+ {
+ SetMode(fallBackMode, SX12XX_Radio_All);
+ return;
+ }
+
+#if defined(DEBUG_RCVR_SIGNAL_STATS)
+ if (radioNumber == SX12XX_Radio_All || radioNumber == SX12XX_Radio_1)
+ {
+ instance->rxSignalStats[0].telem_count++;
+ }
+ if (radioNumber == SX12XX_Radio_All || radioNumber == SX12XX_Radio_2)
+ {
+ instance->rxSignalStats[1].telem_count++;
+ }
+#endif
+
+ // Normal diversity mode
+ if (GPIO_PIN_NSS_2 != UNDEF_PIN && radioNumber != SX12XX_Radio_All)
+ {
+ // Make sure the unused radio is in FS mode and will not receive the tx packet.
+ if (radioNumber == SX12XX_Radio_1)
+ {
+ SetMode(fallBackMode, SX12XX_Radio_2);
+ }
+ else
+ {
+ SetMode(fallBackMode, SX12XX_Radio_1);
+ }
+ }
+
+ // 3.7.4 WriteBuffer8
+ hal.WriteCommand(LR11XX_REGMEM_WRITE_BUFFER8_OC, data, size, radioNumber);
+
+ SetMode(LR1121_MODE_TX, radioNumber);
+
+#ifdef DEBUG_LLCC68_OTA_TIMING
+ beginTX = micros();
+#endif
+}
+
+bool ICACHE_RAM_ATTR LR1121Driver::RXnbISR(SX12XX_Radio_Number_t radioNumber)
+{
+ // 7.2.11 GetRxBufferStatus
+ uint8_t buf[3] = {0};
+ hal.WriteCommand(LR11XX_RADIO_GET_RXBUFFER_STATUS_OC, radioNumber);
+ hal.ReadCommand(buf, sizeof(buf), radioNumber);
+
+ uint8_t const PayloadLengthRX = buf[1];
+ uint8_t const RxStartBufferPointer = buf[2];
+
+ // 3.7.5 ReadBuffer8
+ uint8_t inbuf[2];
+ inbuf[0] = RxStartBufferPointer;
+ inbuf[1] = PayloadLengthRX;
+
+ uint8_t payloadbuf[PayloadLengthRX + 1] = {0};
+ hal.WriteCommand(LR11XX_REGMEM_READ_BUFFER8_OC, inbuf, sizeof(inbuf), radioNumber);
+ hal.ReadCommand(payloadbuf, sizeof(payloadbuf), radioNumber);
+
+ memcpy(RXdataBuffer, payloadbuf + 1, PayloadLengthRX);
+
+ return RXdoneCallback(SX12XX_RX_OK);
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::RXnb(lr11xx_RadioOperatingModes_t rxMode)
+{
+ SetMode(LR1121_MODE_RX, SX12XX_Radio_All);
+}
+
+bool ICACHE_RAM_ATTR LR1121Driver::GetFrequencyErrorbool()
+{
+ return false;
+}
+
+// 7.2.8 GetRssiInst
+int8_t ICACHE_RAM_ATTR LR1121Driver::GetRssiInst(SX12XX_Radio_Number_t radioNumber)
+{
+ uint8_t status[2] = {0};
+ hal.WriteCommand(LR11XX_RADIO_GET_RSSI_INST_OC, radioNumber);
+ hal.ReadCommand(status, sizeof(status), radioNumber);
+ return -(int8_t)(status[1] / 2);
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::GetLastPacketStats()
+{
+ SX12XX_Radio_Number_t radio[2] = {SX12XX_Radio_1, SX12XX_Radio_2};
+ bool gotRadio[2] = {false, false}; // one-radio default.
+ uint8_t processingRadioIdx = (instance->processingPacketRadio == SX12XX_Radio_1) ? 0 : 1;
+ uint8_t secondRadioIdx = !processingRadioIdx;
+
+ // processingRadio always passed the sanity check here
+ gotRadio[processingRadioIdx] = true;
+
+ // if it's a dual radio, and if it's the first IRQ
+ // (don't need this if it's the second IRQ, because we know the first IRQ is already failed)
+ if (instance->isFirstRxIrq && GPIO_PIN_NSS_2 != UNDEF_PIN)
+ {
+ bool isSecondRadioGotData = false;
+
+ uint32_t secondIrqStatus = instance->GetIrqStatus(radio[secondRadioIdx]);
+ if(secondIrqStatus & LR1121_IRQ_RX_DONE)
+ {
+ // 7.2.11 GetRxBufferStatus
+ uint8_t buf[3] = {0};
+ hal.WriteCommand(LR11XX_RADIO_GET_RXBUFFER_STATUS_OC, radio[secondRadioIdx]);
+ hal.ReadCommand(buf, sizeof(buf), radio[secondRadioIdx]);
+
+ uint8_t const PayloadLengthRX = buf[1];
+ uint8_t const RxStartBufferPointer = buf[2];
+
+ // 3.7.5 ReadBuffer8
+ uint8_t inbuf[2];
+ inbuf[0] = RxStartBufferPointer;
+ inbuf[1] = PayloadLengthRX;
+
+ WORD_ALIGNED_ATTR uint8_t RXdataBuffer_second[PayloadLengthRX + 1] = {0};
+
+ hal.WriteCommand(LR11XX_REGMEM_READ_BUFFER8_OC, inbuf, sizeof(inbuf), radio[secondRadioIdx]);
+ hal.ReadCommand(RXdataBuffer_second, sizeof(RXdataBuffer_second), radio[secondRadioIdx]);
+
+ // if the second packet is same to the first, it's valid
+ if(memcmp(RXdataBuffer, RXdataBuffer_second + 1, PayloadLength) == 0)
+ {
+ isSecondRadioGotData = true;
+ }
+ }
+
+ // second radio received the same packet to the processing radio
+ gotRadio[secondRadioIdx] = isSecondRadioGotData;
+ #if defined(DEBUG_RCVR_SIGNAL_STATS)
+ if(!isSecondRadioGotData)
+ {
+ instance->rxSignalStats[secondRadioIdx].fail_count++;
+ }
+ #endif
+ }
+
+ uint8_t status[4];
+ int8_t rssi[2];
+ int8_t snr[2];
+
+ // Get both radios ready at the same time to return packet stats
+ hal.WriteCommand(LR11XX_RADIO_GET_PKT_STATUS_OC, instance->processingPacketRadio | (gotRadio[secondRadioIdx] ? radio[secondRadioIdx] : 0));
+
+ for(uint8_t i=0; i<2; i++)
+ {
+ if (gotRadio[i])
+ {
+ // 8.3.7 GetPacketStatus
+ memset(status, 0, sizeof(status));
+ // hal.WriteCommand(LR11XX_RADIO_GET_PKT_STATUS_OC, radio[i]);
+ hal.ReadCommand(status, sizeof(status), radio[i]);
+
+ // RssiPkt defines the average RSSI over the last packet received. RSSI value in dBm is –RssiPkt/2.
+ rssi[i] = -(int8_t)(status[1] / 2);
+
+ // SignalRssiPkt is an estimation of RSSI of the LoRa signal (after despreading) on last packet received, in two’s
+ // complement format [negated, dBm, fixdt(0,8,1)]. Actual RSSI in dB is -SignalRssiPkt/2.
+ // rssi[i = -(int8_t)(status[3] / 2); // SignalRssiPkt
+
+ snr[i] = (int8_t)status[2];
+
+ // If radio # is 0, update LastPacketRSSI, otherwise LastPacketRSSI2
+ (i == 0) ? LastPacketRSSI = rssi[i] : LastPacketRSSI2 = rssi[i];
+ // Update whatever SNRs we have
+ LastPacketSNRRaw = snr[i];
+ }
+ }
+
+ // by default, set the last successful packet radio to be the current processing radio (which got a successful packet)
+ instance->lastSuccessfulPacketRadio = instance->processingPacketRadio;
+
+ // when both radio got the packet, use the better RSSI one
+ if(gotRadio[0] && gotRadio[1])
+ {
+ LastPacketSNRRaw = instance->fuzzy_snr(snr[0], snr[1], instance->FuzzySNRThreshold);
+ // Update the last successful packet radio to be the one with better signal strength
+ instance->lastSuccessfulPacketRadio = (rssi[0]>rssi[1])? radio[0]: radio[1];
+ }
+
+#if defined(DEBUG_RCVR_SIGNAL_STATS)
+ // stat updates
+ for (uint8_t i = 0; i < 2; i++)
+ {
+ if (gotRadio[i])
+ {
+ instance->rxSignalStats[i].irq_count++;
+ instance->rxSignalStats[i].rssi_sum += rssi[i];
+ instance->rxSignalStats[i].snr_sum += snr[i];
+ if (snr[i] > instance->rxSignalStats[i].snr_max)
+ {
+ instance->rxSignalStats[i].snr_max = snr[i];
+ }
+ LastPacketSNRRaw = snr[i];
+ }
+ }
+ if(gotRadio[0] || gotRadio[1])
+ {
+ instance->irq_count_or++;
+ }
+ if(gotRadio[0] && gotRadio[1])
+ {
+ instance->irq_count_both++;
+ }
+#endif
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::IsrCallback_1()
+{
+ instance->IsrCallback(SX12XX_Radio_1);
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::IsrCallback_2()
+{
+ instance->IsrCallback(SX12XX_Radio_2);
+}
+
+void ICACHE_RAM_ATTR LR1121Driver::IsrCallback(SX12XX_Radio_Number_t radioNumber)
+{
+ if (instance->ignoreSecondIRQ)
+ return;
+
+ instance->processingPacketRadio = radioNumber;
+
+ uint32_t irqStatus = instance->GetIrqStatus(radioNumber);
+ if (irqStatus & LR1121_IRQ_TX_DONE)
+ {
+ instance->TXnbISR();
+ instance->ClearIrqStatus(SX12XX_Radio_All);
+ instance->ignoreSecondIRQ = true;
+ }
+ else if (irqStatus & LR1121_IRQ_RX_DONE)
+ {
+ if (instance->RXnbISR(radioNumber))
+ {
+ instance->ignoreSecondIRQ = true;
+ }
+#if defined(DEBUG_RCVR_SIGNAL_STATS)
+ else
+ {
+ instance->rxSignalStats[(radioNumber == SX12XX_Radio_1) ? 0 : 1].fail_count++;
+ }
+#endif
+ instance->isFirstRxIrq = false; // RX isr is already fired in this period. (reset to true in tock)
+ }
+}
diff --git a/src/lib/LR1121Driver/LR1121.h b/src/lib/LR1121Driver/LR1121.h
new file mode 100644
index 0000000000..3868276a46
--- /dev/null
+++ b/src/lib/LR1121Driver/LR1121.h
@@ -0,0 +1,85 @@
+#pragma once
+
+#include "targets.h"
+#include "LR1121_Regs.h"
+#include "LR1121_hal.h"
+#include "SX12xxDriverCommon.h"
+
+#ifdef PLATFORM_ESP8266
+#include
+#endif
+
+#define RADIO_SNR_SCALE 4
+
+class LR1121Driver: public SX12xxDriverCommon
+{
+public:
+ static LR1121Driver *instance;
+
+ ///////////Radio Variables////////
+ uint32_t timeout;
+
+ ///////////////////////////////////
+
+ ////////////////Configuration Functions/////////////
+ LR1121Driver();
+ bool Begin(uint32_t minimumFrequency, uint32_t maximumFrequency);
+ void End();
+ void SetTxIdleMode() { SetMode(LR1121_MODE_FS, SX12XX_Radio_All); }; // set Idle mode used when switching from RX to TX
+ void Config(uint8_t bw, uint8_t sf, uint8_t cr, uint32_t freq,
+ uint8_t PreambleLength, bool InvertIQ, uint8_t PayloadLength, uint32_t interval, SX12XX_Radio_Number_t radioNumber = SX12XX_Radio_All);
+ void SetFrequencyHz(uint32_t freq, SX12XX_Radio_Number_t radioNumber);
+ void SetFrequencyReg(uint32_t freq, SX12XX_Radio_Number_t radioNumber = SX12XX_Radio_All);
+ void SetRxTimeoutUs(uint32_t interval);
+ void SetOutputPower(int8_t power, bool isSubGHz = true);
+ void startCWTest(uint32_t freq, SX12XX_Radio_Number_t radioNumber);
+
+
+ bool GetFrequencyErrorbool();
+ // bool FrequencyErrorAvailable() const { return modeSupportsFei && (LastPacketSNRRaw > 0); }
+ bool FrequencyErrorAvailable() const { return false; }
+
+ void TXnb(uint8_t * data, uint8_t size, SX12XX_Radio_Number_t radioNumber);
+ void RXnb(lr11xx_RadioOperatingModes_t rxMode = LR1121_MODE_RX);
+
+ uint32_t GetIrqStatus(SX12XX_Radio_Number_t radioNumber);
+ void ClearIrqStatus(SX12XX_Radio_Number_t radioNumber);
+
+ int8_t GetRssiInst(SX12XX_Radio_Number_t radioNumber);
+ void GetLastPacketStats();
+
+private:
+ // constant used for no power change pending
+ // must not be a valid power register value
+ static const uint8_t PWRPENDING_NONE = 0x7f;
+
+ // LR1121_RadioOperatingModes_t currOpmode;
+ bool modeSupportsFei;
+ uint8_t pwrCurrentLF;
+ uint8_t pwrPendingLF;
+ uint8_t pwrCurrentHF; // HF = High Frequency
+ uint8_t pwrPendingHF;
+ bool pwrForceUpdate;
+ bool radio1isSubGHz;
+ bool radio2isSubGHz;
+ lr11xx_RadioOperatingModes_t fallBackMode;
+
+ void SetMode(lr11xx_RadioOperatingModes_t OPmode, SX12XX_Radio_Number_t radioNumber);
+
+ // LoRa functions
+ void ConfigModParamsLoRa(uint8_t bw, uint8_t sf, uint8_t cr, SX12XX_Radio_Number_t radioNumber);
+ void SetPacketParamsLoRa(uint8_t PreambleLength, lr11xx_RadioLoRaPacketLengthsModes_t HeaderType,
+ uint8_t PayloadLength, uint8_t InvertIQ, SX12XX_Radio_Number_t radioNumber);
+
+ void SetDioIrqParams();
+ void SetDioAsRfSwitch();
+ void CorrectRegisterForSF6(uint8_t sf, SX12XX_Radio_Number_t radioNumber);
+
+ static void IsrCallback_1();
+ static void IsrCallback_2();
+ static void IsrCallback(SX12XX_Radio_Number_t radioNumber);
+ bool RXnbISR(SX12XX_Radio_Number_t radioNumber); // ISR for non-blocking RX routine
+ void TXnbISR(); // ISR for non-blocking TX routine
+ void CommitOutputPower();
+ void WriteOutputPower(uint8_t pwr, bool isSubGHz, SX12XX_Radio_Number_t radioNumber);
+};
diff --git a/src/lib/LR1121Driver/LR1121Driver.h b/src/lib/LR1121Driver/LR1121Driver.h
new file mode 100644
index 0000000000..31ca912205
--- /dev/null
+++ b/src/lib/LR1121Driver/LR1121Driver.h
@@ -0,0 +1,4 @@
+#pragma once
+#include "LR1121_Regs.h"
+#include "LR1121_hal.h"
+#include "LR1121.h"
diff --git a/src/lib/LR1121Driver/LR1121_Regs.h b/src/lib/LR1121Driver/LR1121_Regs.h
new file mode 100644
index 0000000000..0b26d0d33e
--- /dev/null
+++ b/src/lib/LR1121Driver/LR1121_Regs.h
@@ -0,0 +1,281 @@
+/*!
+ * @file lr11xx_radio_types.h
+ *
+ * @brief Radio driver types for LR11XX
+ *
+ * The Clear BSD License
+ * Copyright Semtech Corporation 2021. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted (subject to the limitations in the disclaimer
+ * below) provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * 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.
+ * * Neither the name of the Semtech corporation nor the
+ * names of its contributors may be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
+ * THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
+ * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SEMTECH CORPORATION 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.
+ */
+
+#pragma once
+
+#define LR1121_XTAL_FREQ 32000000
+#define FREQ_STEP 61.03515625 // TODO check and fix... this is a cut and past from sx1276
+
+#define LR1121_POWER_MIN_LP_PA (-17) // Low Power PA
+#define LR1121_POWER_MAX_LP_PA (14)
+#define LR1121_POWER_MIN_HP_PA (-9) // High Power PA
+#define LR1121_POWER_MAX_HP_PA (22)
+#define LR1121_POWER_MIN_HF_PA (-18) // High Frequency PA
+#define LR1121_POWER_MAX_HF_PA (13)
+
+#define LR1121_IRQ_TX_DONE 0x00000004
+#define LR1121_IRQ_RX_DONE 0x00000008
+#define LR1121_IRQ_RADIO_NONE 0
+// #define LR1121_IRQ_RADIO_ALL 0xFFFFFFFF
+
+typedef enum
+{
+ LR1121_LORA_PACKET_VARIABLE_LENGTH = 0x00, //!< The packet is on variable size, header included
+ LR1121_LORA_PACKET_FIXED_LENGTH = 0x01, //!< The packet is known on both sides, no header included in the packet
+ LR1121_LORA_PACKET_EXPLICIT = LR1121_LORA_PACKET_VARIABLE_LENGTH,
+ LR1121_LORA_PACKET_IMPLICIT = LR1121_LORA_PACKET_FIXED_LENGTH,
+} lr11xx_RadioLoRaPacketLengthsModes_t;
+
+typedef enum
+{
+ LR1121_MODE_SLEEP = 0x00, //! The radio is in sleep mode
+ LR1121_MODE_STDBY_RC, //! The radio is in standby mode with RC oscillator
+ LR1121_MODE_STDBY_XOSC, //! The radio is in standby mode with XOSC oscillator
+ LR1121_MODE_FS, //! The radio is in frequency synthesis mode
+ LR1121_MODE_RX, //! The radio is in receive mode
+ LR1121_MODE_RX_CONT, //! The radio is in continuous receive mode
+ LR1121_MODE_TX, //! The radio is in transmit mode
+ LR1121_MODE_CAD //! The radio is in channel activity detection mode
+} lr11xx_RadioOperatingModes_t;
+
+enum
+{
+ LR11XX_RADIO_RESET_STATS_OC = 0x0200,
+ LR11XX_RADIO_GET_STATS_OC = 0x0201,
+ LR11XX_RADIO_GET_PKT_TYPE_OC = 0x0202,
+ LR11XX_RADIO_GET_RXBUFFER_STATUS_OC = 0x0203,
+ LR11XX_RADIO_GET_PKT_STATUS_OC = 0x0204,
+ LR11XX_RADIO_GET_RSSI_INST_OC = 0x0205,
+ LR11XX_RADIO_SET_GFSK_SYNC_WORD_OC = 0x0206,
+ LR11XX_RADIO_SET_LORA_PUBLIC_NETWORK_OC = 0x0208,
+ LR11XX_RADIO_SET_RX_OC = 0x0209,
+ LR11XX_RADIO_SET_TX_OC = 0x020A,
+ LR11XX_RADIO_SET_RF_FREQUENCY_OC = 0x020B,
+ LR11XX_RADIO_AUTOTXRX_OC = 0x020C,
+ LR11XX_RADIO_SET_CAD_PARAMS_OC = 0x020D,
+ LR11XX_RADIO_SET_PKT_TYPE_OC = 0x020E,
+ LR11XX_RADIO_SET_MODULATION_PARAM_OC = 0x020F,
+ LR11XX_RADIO_SET_PKT_PARAM_OC = 0x0210,
+ LR11XX_RADIO_SET_TX_PARAMS_OC = 0x0211,
+ LR11XX_RADIO_SET_PKT_ADRS_OC = 0x0212,
+ LR11XX_RADIO_SET_RX_TX_FALLBACK_MODE_OC = 0x0213,
+ LR11XX_RADIO_SET_RX_DUTY_CYCLE_OC = 0x0214,
+ LR11XX_RADIO_SET_PA_CFG_OC = 0x0215,
+ LR11XX_RADIO_STOP_TIMEOUT_ON_PREAMBLE_OC = 0x0217,
+ LR11XX_RADIO_SET_CAD_OC = 0x0218,
+ LR11XX_RADIO_SET_TX_CW_OC = 0x0219,
+ LR11XX_RADIO_SET_TX_INFINITE_PREAMBLE_OC = 0x021A,
+ LR11XX_RADIO_SET_LORA_SYNC_TIMEOUT_OC = 0x021B,
+ LR11XX_RADIO_SET_GFSK_CRC_PARAMS_OC = 0x0224,
+ LR11XX_RADIO_SET_GFSK_WHITENING_PARAMS_OC = 0x0225,
+ LR11XX_RADIO_SET_RX_BOOSTED_OC = 0x0227,
+ LR11XX_RADIO_SET_RSSI_CALIBRATION_OC = 0x0229,
+ LR11XX_RADIO_SET_LORA_SYNC_WORD_OC = 0x022B,
+ LR11XX_RADIO_SET_LR_FHSS_SYNC_WORD_OC = 0x022D,
+ LR11XX_RADIO_CFG_BLE_BEACON_OC = 0x022E,
+ LR11XX_RADIO_GET_LORA_RX_INFO_OC = 0x0230,
+ LR11XX_RADIO_BLE_BEACON_SEND_OC = 0x0231,
+};
+
+enum
+{
+ LR11XX_SYSTEM_GET_STATUS_OC = 0x0100,
+ LR11XX_SYSTEM_GET_VERSION_OC = 0x0101,
+ LR11XX_SYSTEM_GET_ERRORS_OC = 0x010D,
+ LR11XX_SYSTEM_CLEAR_ERRORS_OC = 0x010E,
+ LR11XX_SYSTEM_CALIBRATE_OC = 0x010F,
+ LR11XX_SYSTEM_SET_REGMODE_OC = 0x0110,
+ LR11XX_SYSTEM_CALIBRATE_IMAGE_OC = 0x0111,
+ LR11XX_SYSTEM_SET_DIO_AS_RF_SWITCH_OC = 0x0112,
+ LR11XX_SYSTEM_SET_DIOIRQPARAMS_OC = 0x0113,
+ LR11XX_SYSTEM_CLEAR_IRQ_OC = 0x0114,
+ LR11XX_SYSTEM_CFG_LFCLK_OC = 0x0116,
+ LR11XX_SYSTEM_SET_TCXO_MODE_OC = 0x0117,
+ LR11XX_SYSTEM_REBOOT_OC = 0x0118,
+ LR11XX_SYSTEM_GET_VBAT_OC = 0x0119,
+ LR11XX_SYSTEM_GET_TEMP_OC = 0x011A,
+ LR11XX_SYSTEM_SET_SLEEP_OC = 0x011B,
+ LR11XX_SYSTEM_SET_STANDBY_OC = 0x011C,
+ LR11XX_SYSTEM_SET_FS_OC = 0x011D,
+ LR11XX_SYSTEM_GET_RANDOM_OC = 0x0120,
+ LR11XX_SYSTEM_ERASE_INFOPAGE_OC = 0x0121,
+ LR11XX_SYSTEM_WRITE_INFOPAGE_OC = 0x0122,
+ LR11XX_SYSTEM_READ_INFOPAGE_OC = 0x0123,
+ LR11XX_SYSTEM_READ_UID_OC = 0x0125,
+ LR11XX_SYSTEM_READ_JOIN_EUI_OC = 0x0126,
+ LR11XX_SYSTEM_READ_PIN_OC = 0x0127,
+ LR11XX_SYSTEM_ENABLE_SPI_CRC_OC = 0x0128,
+ LR11XX_SYSTEM_DRIVE_DIO_IN_SLEEP_MODE_OC = 0x012A,
+};
+
+enum
+{
+ LR11XX_REGMEM_WRITE_REGMEM32_OC = 0x0105,
+ LR11XX_REGMEM_READ_REGMEM32_OC = 0x0106,
+ LR11XX_REGMEM_WRITE_MEM8_OC = 0x0107,
+ LR11XX_REGMEM_READ_MEM8_OC = 0x0108,
+ LR11XX_REGMEM_WRITE_BUFFER8_OC = 0x0109,
+ LR11XX_REGMEM_READ_BUFFER8_OC = 0x010A,
+ LR11XX_REGMEM_CLEAR_RXBUFFER_OC = 0x010B,
+ LR11XX_REGMEM_WRITE_REGMEM32_MASK_OC = 0x010C,
+};
+
+typedef enum
+{
+ LR11XX_RADIO_PA_SEL_LP = 0x00, //!< Low-power Power Amplifier
+ LR11XX_RADIO_PA_SEL_HP = 0x01, //!< High-power Power Amplifier
+ LR11XX_RADIO_PA_SEL_HF = 0x02, //!< High-frequency Power Amplifier
+} lr11xx_radio_pa_selection_t;
+
+typedef enum
+{
+ LR11XX_RADIO_FALLBACK_STDBY_RC = 0x01, //!< Standby RC (Default)
+ LR11XX_RADIO_FALLBACK_STDBY_XOSC = 0x02, //!< Standby XOSC
+ LR11XX_RADIO_FALLBACK_FS = 0x03 //!< FS
+} lr11xx_radio_fallback_modes_t;
+
+typedef enum
+{
+ LR11XX_RADIO_RAMP_16_US = 0x00, //!< 16 us Ramp Time
+ LR11XX_RADIO_RAMP_32_US = 0x01, //!< 32 us Ramp Time
+ LR11XX_RADIO_RAMP_48_US = 0x02, //!< 48 us Ramp Time (Default)
+ LR11XX_RADIO_RAMP_64_US = 0x03, //!< 64 us Ramp Time
+ LR11XX_RADIO_RAMP_80_US = 0x04, //!< 80 us Ramp Time
+ LR11XX_RADIO_RAMP_96_US = 0x05, //!< 96 us Ramp Time
+ LR11XX_RADIO_RAMP_112_US = 0x06, //!< 112 us Ramp Time
+ LR11XX_RADIO_RAMP_128_US = 0x07, //!< 128 us Ramp Time
+ LR11XX_RADIO_RAMP_144_US = 0x08, //!< 144 us Ramp Time
+ LR11XX_RADIO_RAMP_160_US = 0x09, //!< 160 us Ramp Time
+ LR11XX_RADIO_RAMP_176_US = 0x0A, //!< 176 us Ramp Time
+ LR11XX_RADIO_RAMP_192_US = 0x0B, //!< 192 us Ramp Time
+ LR11XX_RADIO_RAMP_208_US = 0x0C, //!< 208 us Ramp Time
+ LR11XX_RADIO_RAMP_240_US = 0x0D, //!< 240 us Ramp Time
+ LR11XX_RADIO_RAMP_272_US = 0x0E, //!< 272 us Ramp Time
+ LR11XX_RADIO_RAMP_304_US = 0x0F, //!< 304 us Ramp Time
+} lr11xx_radio_ramp_time_t;
+
+typedef enum
+{
+ LR11XX_RADIO_LORA_NETWORK_PRIVATE = 0x00, //!< LoRa private network
+ LR11XX_RADIO_LORA_NETWORK_PUBLIC = 0x01, //!< LoRa public network
+} lr11xx_radio_lora_network_type_t;
+
+typedef enum
+{
+ LR11XX_RADIO_LORA_SF5 = 0x05, //!< Spreading Factor 5
+ LR11XX_RADIO_LORA_SF6 = 0x06, //!< Spreading Factor 6
+ LR11XX_RADIO_LORA_SF7 = 0x07, //!< Spreading Factor 7
+ LR11XX_RADIO_LORA_SF8 = 0x08, //!< Spreading Factor 8
+ LR11XX_RADIO_LORA_SF9 = 0x09, //!< Spreading Factor 9
+ LR11XX_RADIO_LORA_SF10 = 0x0A, //!< Spreading Factor 10
+ LR11XX_RADIO_LORA_SF11 = 0x0B, //!< Spreading Factor 11
+ LR11XX_RADIO_LORA_SF12 = 0x0C, //!< Spreading Factor 12
+} lr11xx_radio_lora_sf_t;
+
+typedef enum
+{
+ LR11XX_RADIO_LORA_BW_10 = 0x08, //!< Bandwidth 10.42 kHz
+ LR11XX_RADIO_LORA_BW_15 = 0x01, //!< Bandwidth 15.63 kHz
+ LR11XX_RADIO_LORA_BW_20 = 0x09, //!< Bandwidth 20.83 kHz
+ LR11XX_RADIO_LORA_BW_31 = 0x02, //!< Bandwidth 31.25 kHz
+ LR11XX_RADIO_LORA_BW_41 = 0x0A, //!< Bandwidth 41.67 kHz
+ LR11XX_RADIO_LORA_BW_62 = 0x03, //!< Bandwidth 62.50 kHz
+ LR11XX_RADIO_LORA_BW_125 = 0x04, //!< Bandwidth 125.00 kHz
+ LR11XX_RADIO_LORA_BW_250 = 0x05, //!< Bandwidth 250.00 kHz
+ LR11XX_RADIO_LORA_BW_500 = 0x06, //!< Bandwidth 500.00 kHz
+ LR11XX_RADIO_LORA_BW_200 = 0x0D, //!< Bandwidth 203.00 kHz, 2G4 and compatible with LR112x chips only
+ LR11XX_RADIO_LORA_BW_400 = 0x0E, //!< Bandwidth 406.00 kHz, 2G4 and compatible with LR112x chips only
+ LR11XX_RADIO_LORA_BW_800 = 0x0F, //!< Bandwidth 812.00 kHz, 2G4 and compatible with LR112x chips only
+} lr11xx_radio_lora_bw_t;
+
+typedef enum
+{
+ LR11XX_RADIO_LORA_NO_CR = 0x00, //!< No Coding Rate
+ LR11XX_RADIO_LORA_CR_4_5 = 0x01, //!< Coding Rate 4/5 Short Interleaver
+ LR11XX_RADIO_LORA_CR_4_6 = 0x02, //!< Coding Rate 4/6 Short Interleaver
+ LR11XX_RADIO_LORA_CR_4_7 = 0x03, //!< Coding Rate 4/7 Short Interleaver
+ LR11XX_RADIO_LORA_CR_4_8 = 0x04, //!< Coding Rate 4/8 Short Interleaver
+ LR11XX_RADIO_LORA_CR_LI_4_5 = 0x05, //!< Coding Rate 4/5 Long Interleaver
+ LR11XX_RADIO_LORA_CR_LI_4_6 = 0x06, //!< Coding Rate 4/6 Long Interleaver
+ LR11XX_RADIO_LORA_CR_LI_4_8 = 0x07, //!< Coding Rate 4/8 Long Interleaver
+} lr11xx_radio_lora_cr_t;
+
+typedef enum
+{
+ LR11XX_RADIO_MODE_SLEEP = 0x00, //!< Sleep / Not recommended with LR1110 FW from 0x0303 to 0x0307 and LR1120 FW
+ //!< 0x0101 in case of transition from Rx to Tx in LoRa
+ LR11XX_RADIO_MODE_STANDBY_RC = 0x01, //!< Standby RC
+ LR11XX_RADIO_MODE_STANDBY_XOSC = 0x02, //!< Standby XOSC
+ LR11XX_RADIO_MODE_FS = 0x03 //!< Frequency Synthesis
+} lr11xx_radio_intermediary_mode_t;
+
+typedef enum
+{
+ LR11XX_RADIO_LORA_CRC_OFF = 0x00, //!< CRC deactivated
+ LR11XX_RADIO_LORA_CRC_ON = 0x01, //!< CRC activated
+} lr11xx_radio_lora_crc_t;
+
+typedef enum
+{
+ LR11XX_RADIO_LORA_PKT_EXPLICIT = 0x00, //!< Explicit header: transmitted over the air
+ LR11XX_RADIO_LORA_PKT_IMPLICIT = 0x01, //!< Implicit header: not transmitted over the air
+} lr11xx_radio_lora_pkt_len_modes_t;
+
+typedef enum
+{
+ LR11XX_RADIO_LORA_IQ_STANDARD = 0x00, //!< IQ standard
+ LR11XX_RADIO_LORA_IQ_INVERTED = 0x01, //!< IQ inverted
+} lr11xx_radio_lora_iq_t;
+
+typedef enum
+{
+ LR11XX_RADIO_PKT_NONE = 0x00, //!< State after cold start, Wi-Fi or GNSS capture
+ LR11XX_RADIO_PKT_TYPE_GFSK = 0x01, //!< GFSK modulation
+ LR11XX_RADIO_PKT_TYPE_LORA = 0x02, //!< LoRa modulation
+ LR11XX_RADIO_PKT_TYPE_BPSK = 0x03, //!< BPSK modulation
+ LR11XX_RADIO_PKT_TYPE_LR_FHSS = 0x04, //!< LR-FHSS modulation
+ LR11XX_RADIO_PKT_TYPE_RANGING = 0x05, //!< Ranging packet
+} lr11xx_radio_pkt_type_t;
+
+typedef enum
+{
+ LR11XX_RADIO_PA_REG_SUPPLY_VREG = 0x00, //!< Power amplifier supplied by the main regulator
+ LR11XX_RADIO_PA_REG_SUPPLY_VBAT = 0x01 //!< Power amplifier supplied by the battery
+} lr11xx_radio_pa_reg_supply_t;
+
+typedef enum
+{
+ LR11XX_RADIO_RX_DUTY_CYCLE_MODE_RX = 0x00, //!< LoRa/GFSK: Uses Rx for listening to packets
+ LR11XX_RADIO_RX_DUTY_CYCLE_MODE_CAD = 0x01, //!< Only in LoRa: Uses CAD to listen for over-the-air activity
+} lr11xx_radio_rx_duty_cycle_mode_t;
diff --git a/src/lib/LR1121Driver/LR1121_hal.cpp b/src/lib/LR1121Driver/LR1121_hal.cpp
new file mode 100644
index 0000000000..df76acae21
--- /dev/null
+++ b/src/lib/LR1121Driver/LR1121_hal.cpp
@@ -0,0 +1,194 @@
+#ifndef UNIT_TEST
+
+#include "LR1121_hal.h"
+#include "LR1121_Regs.h"
+#include "logging.h"
+#include
+
+LR1121Hal *LR1121Hal::instance = NULL;
+
+LR1121Hal::LR1121Hal()
+{
+ instance = this;
+}
+
+void LR1121Hal::end()
+{
+ detachInterrupt(GPIO_PIN_DIO1);
+ if (GPIO_PIN_DIO1_2 != UNDEF_PIN)
+ {
+ detachInterrupt(GPIO_PIN_DIO1_2);
+ }
+ SPIEx.end();
+ IsrCallback_1 = nullptr;
+ IsrCallback_2 = nullptr;
+}
+
+void LR1121Hal::init()
+{
+ DBGLN("Hal Init");
+
+ pinMode(GPIO_PIN_BUSY, INPUT);
+ if (GPIO_PIN_BUSY_2 != UNDEF_PIN)
+ {
+ pinMode(GPIO_PIN_BUSY_2, INPUT);
+ }
+
+ pinMode(GPIO_PIN_DIO1, INPUT);
+ if (GPIO_PIN_DIO1_2 != UNDEF_PIN)
+ {
+ pinMode(GPIO_PIN_DIO1_2, INPUT);
+ }
+
+ pinMode(GPIO_PIN_NSS, OUTPUT);
+ digitalWrite(GPIO_PIN_NSS, HIGH);
+ if (GPIO_PIN_NSS_2 != UNDEF_PIN)
+ {
+ pinMode(GPIO_PIN_NSS_2, OUTPUT);
+ digitalWrite(GPIO_PIN_NSS_2, HIGH);
+ }
+
+#ifdef PLATFORM_ESP32
+ SPIEx.begin(GPIO_PIN_SCK, GPIO_PIN_MISO, GPIO_PIN_MOSI, GPIO_PIN_NSS); // sck, miso, mosi, ss (ss can be any GPIO)
+ gpio_pullup_en((gpio_num_t)GPIO_PIN_MISO);
+ SPIEx.setFrequency(17500000);
+ SPIEx.setHwCs(true);
+ if (GPIO_PIN_NSS_2 != UNDEF_PIN)
+ {
+ pinMode(GPIO_PIN_NSS_2, OUTPUT);
+ digitalWrite(GPIO_PIN_NSS_2, HIGH);
+ spiAttachSS(SPIEx.bus(), 1, GPIO_PIN_NSS_2);
+ }
+ spiEnableSSPins(SPIEx.bus(), SX12XX_Radio_All);
+#elif defined(PLATFORM_ESP8266)
+ DBGLN("PLATFORM_ESP8266");
+ SPIEx.begin();
+ SPIEx.setHwCs(true);
+ SPIEx.setBitOrder(MSBFIRST);
+ SPIEx.setDataMode(SPI_MODE0);
+ SPIEx.setFrequency(17500000);
+#elif defined(PLATFORM_STM32)
+ DBGLN("Config SPI");
+ SPIEx.setBitOrder(MSBFIRST);
+ SPIEx.setDataMode(SPI_MODE0);
+ SPIEx.setMOSI(GPIO_PIN_MOSI);
+ SPIEx.setMISO(GPIO_PIN_MISO);
+ SPIEx.setSCLK(GPIO_PIN_SCK);
+ SPIEx.begin();
+ SPIEx.setClockDivider(SPI_CLOCK_DIV4); // 72 / 8 = 9 MHz
+#endif
+
+ attachInterrupt(digitalPinToInterrupt(GPIO_PIN_DIO1), this->dioISR_1, RISING);
+ if (GPIO_PIN_DIO1_2 != UNDEF_PIN)
+ {
+ attachInterrupt(digitalPinToInterrupt(GPIO_PIN_DIO1_2), this->dioISR_2, RISING);
+ }
+}
+
+void LR1121Hal::reset(void)
+{
+ DBGLN("LR1121 Reset");
+
+ if (GPIO_PIN_RST != UNDEF_PIN)
+ {
+ pinMode(GPIO_PIN_RST, OUTPUT);
+ digitalWrite(GPIO_PIN_RST, LOW);
+ if (GPIO_PIN_RST_2 != UNDEF_PIN)
+ {
+ pinMode(GPIO_PIN_RST_2, OUTPUT);
+ digitalWrite(GPIO_PIN_RST_2, LOW);
+ }
+ delay(1);
+ digitalWrite(GPIO_PIN_RST, HIGH);
+ if (GPIO_PIN_RST_2 != UNDEF_PIN)
+ {
+ digitalWrite(GPIO_PIN_RST_2, HIGH);
+ }
+ delay(300); // LR1121 busy is high for 230ms after reset. The WaitOnBusy timeout is only 1ms. So this long delay is required.
+ }
+
+ WaitOnBusy(SX12XX_Radio_All);
+}
+
+void ICACHE_RAM_ATTR LR1121Hal::WriteCommand(uint16_t command, uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber)
+{
+ WORD_ALIGNED_ATTR uint8_t OutBuffer[WORD_PADDED(size + 2)] = {
+ (uint8_t)((command & 0xFF00) >> 8),
+ (uint8_t)(command & 0x00FF),
+ };
+
+ memcpy(OutBuffer + 2, buffer, size);
+
+ WaitOnBusy(radioNumber);
+ SPIEx.write(radioNumber, OutBuffer, size + 2);
+}
+
+void ICACHE_RAM_ATTR LR1121Hal::WriteCommand(uint16_t command, SX12XX_Radio_Number_t radioNumber)
+{
+ WORD_ALIGNED_ATTR uint8_t OutBuffer[WORD_PADDED(2)] = {
+ (uint8_t)((command & 0xFF00) >> 8),
+ (uint8_t)(command & 0x00FF)
+ };
+
+ WaitOnBusy(radioNumber);
+ SPIEx.write(radioNumber, OutBuffer, 2);
+}
+
+void ICACHE_RAM_ATTR LR1121Hal::ReadCommand(uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber)
+{
+ WORD_ALIGNED_ATTR uint8_t InBuffer[WORD_PADDED(size)] = {0};
+
+ memcpy(InBuffer, buffer, size);
+
+ WaitOnBusy(radioNumber);
+ SPIEx.read(radioNumber, InBuffer, size);
+
+ memcpy(buffer, InBuffer, size);
+}
+
+bool ICACHE_RAM_ATTR LR1121Hal::WaitOnBusy(SX12XX_Radio_Number_t radioNumber)
+{
+ constexpr uint32_t wtimeoutUS = 1000U;
+ uint32_t startTime = 0;
+
+ while (true)
+ {
+ if (radioNumber == SX12XX_Radio_1)
+ {
+ if (digitalRead(GPIO_PIN_BUSY) == LOW) return true;
+ }
+ else if (radioNumber == SX12XX_Radio_2)
+ {
+ if (GPIO_PIN_BUSY_2 == UNDEF_PIN || digitalRead(GPIO_PIN_BUSY_2) == LOW) return true;
+ }
+ else if (radioNumber == SX12XX_Radio_All)
+ {
+ if (GPIO_PIN_BUSY_2 != UNDEF_PIN)
+ {
+ if (digitalRead(GPIO_PIN_BUSY) == LOW && digitalRead(GPIO_PIN_BUSY_2) == LOW) return true;
+ }
+ else
+ {
+ if (digitalRead(GPIO_PIN_BUSY) == LOW) return true;
+ }
+ }
+ // Use this time to call micros().
+ uint32_t now = micros();
+ if (startTime == 0) startTime = now;
+ if ((now - startTime) > wtimeoutUS) return false;
+ }
+}
+
+void ICACHE_RAM_ATTR LR1121Hal::dioISR_1()
+{
+ if (instance->IsrCallback_1)
+ instance->IsrCallback_1();
+}
+
+void ICACHE_RAM_ATTR LR1121Hal::dioISR_2()
+{
+ if (instance->IsrCallback_2)
+ instance->IsrCallback_2();
+}
+
+#endif // UNIT_TEST
diff --git a/src/lib/LR1121Driver/LR1121_hal.h b/src/lib/LR1121Driver/LR1121_hal.h
new file mode 100644
index 0000000000..9bea6aaf76
--- /dev/null
+++ b/src/lib/LR1121Driver/LR1121_hal.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "LR1121_Regs.h"
+#include "LR1121.h"
+
+class LR1121Hal
+{
+public:
+ static LR1121Hal *instance;
+
+ LR1121Hal();
+
+ void init();
+ void end();
+ void reset();
+
+ void ICACHE_RAM_ATTR WriteCommand(uint16_t opcode, SX12XX_Radio_Number_t radioNumber);
+ void ICACHE_RAM_ATTR WriteCommand(uint16_t opcode, uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber);
+
+ void ICACHE_RAM_ATTR ReadCommand(uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber);
+
+ bool ICACHE_RAM_ATTR WaitOnBusy(SX12XX_Radio_Number_t radioNumber);
+
+ static ICACHE_RAM_ATTR void dioISR_1();
+ static ICACHE_RAM_ATTR void dioISR_2();
+ void (*IsrCallback_1)();
+ void (*IsrCallback_2)();
+
+private:
+
+};
diff --git a/src/lib/LUA/lua.cpp b/src/lib/LUA/lua.cpp
index 9e33405aa8..dd5ab72169 100644
--- a/src/lib/LUA/lua.cpp
+++ b/src/lib/LUA/lua.cpp
@@ -5,12 +5,10 @@
#ifdef TARGET_RX
#include "telemetry.h"
-#endif
-
-extern CRSF crsf;
-#ifdef TARGET_RX
extern Telemetry telemetry;
+#else
+#include "CRSFHandset.h"
#endif
//LUA VARIABLES//
@@ -144,7 +142,7 @@ static uint8_t sendCRSFparam(crsf_frame_type_e frameType, uint8_t fieldChunk, st
#ifdef TARGET_TX
// Set the hidden flag
chunkBuffer[3] |= luaData->type & CRSF_FIELD_HIDDEN ? 0x80 : 0;
- if (crsf.elrsLUAmode) {
+ if (CRSFHandset::elrsLUAmode) {
chunkBuffer[3] |= luaData->type & CRSF_FIELD_ELRS_HIDDEN ? 0x80 : 0;
}
#else
@@ -197,7 +195,7 @@ static uint8_t sendCRSFparam(crsf_frame_type_e frameType, uint8_t fieldChunk, st
// 6 bytes CRSF header/CRC: Dest, Len, Type, ExtSrc, ExtDst, CRC
// 2 bytes Lua chunk header: FieldId, ChunksRemain
#ifdef TARGET_TX
- uint8_t chunkMax = CRSF::GetMaxPacketBytes() - 6 - 2;
+ uint8_t chunkMax = handset->GetMaxPacketBytes() - 6 - 2;
#else
uint8_t chunkMax = CRSF_MAX_PACKET_LEN - 6 - 2;
#endif
@@ -211,11 +209,11 @@ static uint8_t sendCRSFparam(crsf_frame_type_e frameType, uint8_t fieldChunk, st
chunkStart[0] = luaData->id; // FieldId
chunkStart[1] = chunkCnt - (fieldChunk + 1); // ChunksRemain
#ifdef TARGET_TX
- CRSF::packetQueueExtended(frameType, chunkStart, chunkSize + 2);
+ CRSFHandset::packetQueueExtended(frameType, chunkStart, chunkSize + 2);
#else
memcpy(paramInformation + sizeof(crsf_ext_header_t),chunkStart,chunkSize + 2);
- crsf.SetExtendedHeaderAndCrc(paramInformation, frameType, chunkSize + CRSF_FRAME_LENGTH_EXT_TYPE_CRC + 2, CRSF_ADDRESS_CRSF_RECEIVER, CRSF_ADDRESS_CRSF_TRANSMITTER);
+ CRSF::SetExtendedHeaderAndCrc(paramInformation, frameType, chunkSize + CRSF_FRAME_LENGTH_EXT_TYPE_CRC + 2, CRSF_ADDRESS_CRSF_RECEIVER, CRSF_ADDRESS_CRSF_TRANSMITTER);
telemetry.AppendTelemetryPackage(paramInformation);
#endif
@@ -261,7 +259,7 @@ static void updateElrsFlags()
{
setLuaWarningFlag(LUA_FLAG_MODEL_MATCH, connectionState == connected && connectionHasModelMatch == false);
setLuaWarningFlag(LUA_FLAG_CONNECTED, connectionState == connected);
- setLuaWarningFlag(LUA_FLAG_ISARMED, crsf.IsArmed());
+ setLuaWarningFlag(LUA_FLAG_ISARMED, handset->IsArmed());
}
void sendELRSstatus()
@@ -289,13 +287,13 @@ void sendELRSstatus()
uint8_t buffer[sizeof(tagLuaElrsParams) + strlen(warningInfo) + 1];
struct tagLuaElrsParams * const params = (struct tagLuaElrsParams *)buffer;
- params->pktsBad = crsf.BadPktsCountResult;
- params->pktsGood = htobe16(crsf.GoodPktsCountResult);
+ params->pktsBad = CRSFHandset::BadPktsCountResult;
+ params->pktsGood = htobe16(CRSFHandset::GoodPktsCountResult);
params->flags = luaWarningFlags;
// to support sending a params.msg, buffer should be extended by the strlen of the message
// and copied into params->msg (with trailing null)
strcpy(params->msg, warningInfo);
- crsf.packetQueueExtended(0x2E, &buffer, sizeof(buffer));
+ CRSFHandset::packetQueueExtended(0x2E, &buffer, sizeof(buffer));
}
void luaRegisterDevicePingCallback(void (*callback)())
@@ -305,7 +303,7 @@ void luaRegisterDevicePingCallback(void (*callback)())
#endif
-void ICACHE_RAM_ATTR luaParamUpdateReq(uint8_t type, uint8_t index, uint8_t arg)
+void luaParamUpdateReq(uint8_t type, uint8_t index, uint8_t arg)
{
parameterType = type;
parameterIndex = index;
@@ -411,6 +409,17 @@ bool luaHandleUpdateParameter()
}
break;
+#if defined(TARGET_RX)
+ // This is a bit of a hack, it just so happens that the parameterIndex and parameterArg parameters
+ // are in the same place as the bind command. This should be handled further up the receive chain
+ // but the call in Telemetry.ShouldCallEnterBind() only works if serial data is coming in so the
+ // whole stack needs a bit of a refactor to not have similar code duplicated all over
+ case CRSF_FRAMETYPE_COMMAND:
+ if (parameterIndex == CRSF_COMMAND_SUBCMD_RX && parameterArg == CRSF_COMMAND_SUBCMD_RX_BIND)
+ EnterBindingModeSafely();
+ break;
+#endif
+
default:
DBGLN("Unknown LUA %x", parameterType);
}
@@ -422,12 +431,12 @@ bool luaHandleUpdateParameter()
void sendLuaDevicePacket(void)
{
uint8_t deviceInformation[DEVICE_INFORMATION_LENGTH];
- crsf.GetDeviceInformation(deviceInformation, lastLuaField);
+ CRSF::GetDeviceInformation(deviceInformation, lastLuaField);
// does append header + crc again so substract size from length
#ifdef TARGET_TX
- crsf.packetQueueExtended(CRSF_FRAMETYPE_DEVICE_INFO, deviceInformation + sizeof(crsf_ext_header_t), DEVICE_INFORMATION_PAYLOAD_LENGTH);
+ CRSFHandset::packetQueueExtended(CRSF_FRAMETYPE_DEVICE_INFO, deviceInformation + sizeof(crsf_ext_header_t), DEVICE_INFORMATION_PAYLOAD_LENGTH);
#else
- crsf.SetExtendedHeaderAndCrc(deviceInformation, CRSF_FRAMETYPE_DEVICE_INFO, DEVICE_INFORMATION_FRAME_SIZE, CRSF_ADDRESS_CRSF_RECEIVER, CRSF_ADDRESS_CRSF_TRANSMITTER);
+ CRSF::SetExtendedHeaderAndCrc(deviceInformation, CRSF_FRAMETYPE_DEVICE_INFO, DEVICE_INFORMATION_FRAME_SIZE, CRSF_ADDRESS_CRSF_RECEIVER, CRSF_ADDRESS_CRSF_TRANSMITTER);
telemetry.AppendTelemetryPackage(deviceInformation);
#endif
}
diff --git a/src/lib/LUA/lua.h b/src/lib/LUA/lua.h
index 7d19b62324..3b996388f9 100644
--- a/src/lib/LUA/lua.h
+++ b/src/lib/LUA/lua.h
@@ -3,7 +3,6 @@
#include "targets.h"
#include "crsf_protocol.h"
-#include
enum lua_Flags{
//bit 0 and 1 are status flags, show up as the little icon in the lua top right corner
@@ -21,7 +20,7 @@ enum lua_Flags{
struct luaPropertiesCommon {
const char* const name; // display name
- const crsf_value_type_e type;
+ crsf_value_type_e type;
uint8_t id; // Sequential id assigned by enumeration
uint8_t parent; // id of parent folder
} PACKED;
@@ -58,16 +57,16 @@ struct luaItem_command {
struct luaPropertiesInt8 {
union {
- struct {
- int8_t value;
- const int8_t min;
- const int8_t max;
- } s;
struct {
uint8_t value;
const uint8_t min;
const uint8_t max;
} u;
+ struct {
+ int8_t value;
+ const int8_t min;
+ const int8_t max;
+ } s;
};
} PACKED;
@@ -79,18 +78,18 @@ struct luaItem_int8 {
struct luaPropertiesInt16 {
union {
- struct {
- int16_t value;
- const int16_t min;
- const int16_t max;
- } s;
struct {
uint16_t value;
const uint16_t min;
const uint16_t max;
} u;
+ struct {
+ int16_t value;
+ const int16_t min;
+ const int16_t max;
+ } s;
};
-}PACKED;
+} PACKED;
struct luaItem_int16 {
struct luaPropertiesCommon common;
@@ -98,13 +97,29 @@ struct luaItem_int16 {
const char* const units;
} PACKED;
+struct luaPropertiesFloat {
+ // value, min, max, and def are all signed, but stored as BE unsigned
+ uint32_t value;
+ const uint32_t min;
+ const uint32_t max;
+ const uint32_t def; // default value
+ const uint8_t precision;
+ const uint32_t step;
+} PACKED;
+
+struct luaItem_float {
+ struct luaPropertiesCommon common;
+ struct luaPropertiesFloat properties;
+ const char* const units;
+} PACKED;
+
struct luaItem_string {
- const struct luaPropertiesCommon common;
+ struct luaPropertiesCommon common;
const char* value;
} PACKED;
struct luaItem_folder {
- const struct luaPropertiesCommon common;
+ struct luaPropertiesCommon common;
char* dyn_name;
} PACKED;
@@ -122,6 +137,9 @@ uint8_t getLuaWarningFlags(void);
void luaRegisterDevicePingCallback(void (*callback)());
#endif
+#define LUA_FIELD_HIDE(fld) { fld.common.type = (crsf_value_type_e)((uint8_t)fld.common.type | CRSF_FIELD_HIDDEN); }
+#define LUA_FIELD_SHOW(fld) { fld.common.type = (crsf_value_type_e)((uint8_t)fld.common.type & ~CRSF_FIELD_HIDDEN); }
+
void sendLuaCommandResponse(struct luaItem_command *cmd, luaCmdStep_e step, const char *message);
extern void luaParamUpdateReq(uint8_t type, uint8_t index, uint8_t arg);
@@ -143,10 +161,13 @@ inline void setLuaInt8Value(struct luaItem_int8 *luaStruct, int8_t newvalue) {
luaStruct->properties.s.value = newvalue;
}
inline void setLuaUint16Value(struct luaItem_int16 *luaStruct, uint16_t newvalue) {
- luaStruct->properties.u.value = (newvalue >> 8) | (newvalue << 8);
+ luaStruct->properties.u.value = htobe16(newvalue);
}
inline void setLuaInt16Value(struct luaItem_int16 *luaStruct, int16_t newvalue) {
- luaStruct->properties.s.value = (newvalue >> 8) | (newvalue << 8);
+ luaStruct->properties.u.value = htobe16((uint16_t)newvalue);
+}
+inline void setLuaFloatValue(struct luaItem_float *luaStruct, int32_t newvalue) {
+ luaStruct->properties.value = htobe32((uint32_t)newvalue);
}
inline void setLuaStringValue(struct luaItem_string *luaStruct, const char *newvalue) {
luaStruct->value = newvalue;
diff --git a/src/lib/LUA/rx_devLUA.cpp b/src/lib/LUA/rx_devLUA.cpp
index 788705e8bf..d9ec6108b6 100644
--- a/src/lib/LUA/rx_devLUA.cpp
+++ b/src/lib/LUA/rx_devLUA.cpp
@@ -1,24 +1,22 @@
#ifdef TARGET_RX
#include "rxtx_devLua.h"
-#include "devCRSF.h"
+#include "helpers.h"
#include "devServoOutput.h"
+#include "deferred.h"
-extern void deferExecution(uint32_t ms, std::function f);
extern void reconfigureSerial();
-
-extern bool InLoanBindingMode;
-extern bool returnModelFromLoan;
+extern bool BindingModeRequest;
static char modelString[] = "000";
-static const char *pwmModes = "50Hz;60Hz;100Hz;160Hz;333Hz;400Hz;10kHzDuty;On/Off";
-static const char *txModes = "50Hz;60Hz;100Hz;160Hz;333Hz;400Hz;10kHzDuty;On/Off;Serial TX";
-static const char *rxModes = "50Hz;60Hz;100Hz;160Hz;333Hz;400Hz;10kHzDuty;On/Off;Serial RX";
+#if defined(GPIO_PIN_PWM_OUTPUTS)
+static char pwmModes[] = "50Hz;60Hz;100Hz;160Hz;333Hz;400Hz;10kHzDuty;On/Off;DShot;Serial RX;Serial TX;I2C SCL;I2C SDA";
+#endif
static struct luaItem_selection luaSerialProtocol = {
{"Protocol", CRSF_TEXT_SELECTION},
0, // value
- "CRSF;Inverted CRSF;SBUS;Inverted SBUS;SUMD;DJI RS Pro",
+ "CRSF;Inverted CRSF;SBUS;Inverted SBUS;SUMD;DJI RS Pro;HoTT Telemetry",
STR_EMPTYSPACE
};
@@ -38,13 +36,6 @@ static struct luaItem_selection luaTlmPower = {
};
#endif
-static struct luaItem_selection luaRateInitIdx = {
- {"Init Rate", CRSF_TEXT_SELECTION},
- 0, // value
- STR_LUA_PACKETRATES,
- STR_EMPTYSPACE
-};
-
#if defined(GPIO_PIN_ANT_CTRL)
static struct luaItem_selection luaAntennaMode = {
{"Ant. Mode", CRSF_TEXT_SELECTION},
@@ -64,6 +55,24 @@ static struct luaItem_selection luaDiversityMode = {
};
#endif
+static struct luaItem_folder luaTeamraceFolder = {
+ {"Team Race", CRSF_FOLDER},
+};
+
+static struct luaItem_selection luaTeamraceChannel = {
+ {"Channel", CRSF_TEXT_SELECTION},
+ 0, // value
+ "AUX2;AUX3;AUX4;AUX5;AUX6;AUX7;AUX8;AUX9;AUX10;AUX11;AUX12",
+ STR_EMPTYSPACE
+};
+
+static struct luaItem_selection luaTeamracePosition = {
+ {"Position", CRSF_TEXT_SELECTION},
+ 0, // value
+ "Disabled;1/Low;2;3;Mid;4;5;6/High",
+ STR_EMPTYSPACE
+};
+
//----------------------------Info-----------------------------------
static struct luaItem_string luaModelNumber = {
@@ -138,41 +147,137 @@ static struct luaItem_command luaSetFailsafe = {
//---------------------------- Output Mapping -----------------------------
-//---------------------------- Model Loan Out -----------------------------
-
-static struct luaItem_command luaLoanModel = {
- {"Loan Model", CRSF_COMMAND},
- lcsIdle, // step
+static struct luaItem_selection luaVolatileBind = {
+ {"Bind Storage", CRSF_TEXT_SELECTION},
+ 0, // value
+ "Persistent;Volatile",
STR_EMPTYSPACE
};
-static struct luaItem_command luaReturnModel = {
- {"Return Model", CRSF_COMMAND},
+static struct luaItem_command luaBindMode = {
+ {"Enter Bind Mode", CRSF_COMMAND},
lcsIdle, // step
STR_EMPTYSPACE
};
-//---------------------------- Model Loan Out -----------------------------
-
#if defined(GPIO_PIN_PWM_OUTPUTS)
-
static void luaparamMappingChannelOut(struct luaPropertiesCommon *item, uint8_t arg)
{
- setLuaUint8Value(&luaMappingChannelOut, arg);
- // Must trigger an event because this is not a persistent config item
- if (GPIO_PIN_PWM_OUTPUTS[arg-1] == 3)
- {
- luaMappingOutputMode.options = rxModes;
- }
- else if (GPIO_PIN_PWM_OUTPUTS[arg-1] == 1)
- {
- luaMappingOutputMode.options = txModes;
- }
- else
- {
- luaMappingOutputMode.options = pwmModes;
- }
- devicesTriggerEvent();
+ bool sclAssigned = false;
+ bool sdaAssigned = false;
+
+ const char *no1Option = ";";
+ const char *no2Options = ";;";
+ const char *dshot = ";DShot";
+ const char *serial_RX = ";Serial RX";
+ const char *serial_TX = ";Serial TX";
+ const char *i2c_SCL = ";I2C SCL;";
+ const char *i2c_SDA = ";;I2C SDA";
+ const char *i2c_BOTH = ";I2C SCL;I2C SDA";
+
+ const char *pModeString;
+
+
+ // find out if use once only modes have already been assigned
+ for (uint8_t ch = 0; ch < GPIO_PIN_PWM_OUTPUTS_COUNT; ch++)
+ {
+ if (ch == (arg -1))
+ continue;
+
+ eServoOutputMode mode = (eServoOutputMode)config.GetPwmChannel(ch)->val.mode;
+
+ if (mode == somSCL)
+ sclAssigned = true;
+
+ if (mode == somSDA)
+ sdaAssigned = true;
+ }
+
+ setLuaUint8Value(&luaMappingChannelOut, arg);
+
+ // When the selected output channel changes, update the available PWM modes for that pin
+ // Truncate the select options before the ; following On/Off
+ pwmModes[50] = '\0';
+
+#if defined(PLATFORM_ESP32)
+ // DShot output (1 option)
+ // ;DShot
+ // ESP8266 enum skips this, so it is never present
+ if (GPIO_PIN_PWM_OUTPUTS[arg-1] != 0) // DShot doesn't work with GPIO0, exclude it
+ {
+ pModeString = dshot;
+ }
+ else
+#endif
+ {
+ pModeString = no1Option;
+ }
+ strcat(pwmModes, pModeString);
+
+ // SerialIO outputs (1 option)
+ // ;[Serial RX] | [Serial TX]
+ if (GPIO_PIN_PWM_OUTPUTS[arg-1] == 3)
+ {
+ pModeString = serial_RX;
+ }
+ else if (GPIO_PIN_PWM_OUTPUTS[arg-1] == 1)
+ {
+ pModeString = serial_TX;
+ }
+ else
+ {
+ pModeString = no1Option;
+ }
+ strcat(pwmModes, pModeString);
+
+ // I2C pins (2 options)
+ // ;[I2C SCL] ;[I2C SDA]
+ if (GPIO_PIN_SCL != UNDEF_PIN || GPIO_PIN_SDA != UNDEF_PIN)
+ {
+ // If the target defines SCL/SDA then those pins MUST be used
+ if (GPIO_PIN_PWM_OUTPUTS[arg-1] == GPIO_PIN_SCL)
+ {
+ pModeString = i2c_SCL;
+ }
+ else if (GPIO_PIN_PWM_OUTPUTS[arg-1] == GPIO_PIN_SDA)
+ {
+ pModeString = i2c_SDA;
+ }
+ else
+ {
+ pModeString = no2Options;
+ }
+ }
+ else
+ {
+ // otherwise allow any pin to be either SCL or SDA but only once
+ if (sclAssigned && !sdaAssigned)
+ {
+ pModeString = i2c_SDA;
+ }
+ else if (sdaAssigned && !sclAssigned)
+ {
+ pModeString = i2c_SCL;
+ }
+ else if (!sclAssigned && !sdaAssigned)
+ {
+ pModeString = i2c_BOTH;
+ }
+ else
+ {
+ pModeString = no2Options;
+ }
+ }
+ strcat(pwmModes, pModeString);
+
+ // trim off trailing semicolons (assumes pwmModes has at least 1 non-semicolon)
+ for (auto lastPos = strlen(pwmModes)-1; pwmModes[lastPos] == ';'; lastPos--)
+ {
+ pwmModes[lastPos] = '\0';
+ }
+
+ // Trigger an event to update the related fields to represent the selected channel
+ devicesTriggerEvent();
}
static void luaparamMappingChannelIn(struct luaPropertiesCommon *item, uint8_t arg)
@@ -185,37 +290,43 @@ static void luaparamMappingChannelIn(struct luaPropertiesCommon *item, uint8_t a
config.SetPwmChannelRaw(ch, newPwmCh.raw);
}
-static uint8_t configureSerialPin(uint8_t pin, uint8_t sibling, uint8_t oldMode, uint8_t newMode)
+static void configureSerialPin(uint8_t sibling, uint8_t oldMode, uint8_t newMode)
{
for (int ch=0 ; chraw;
+
+ // If the new mode is serial, the sibling is also forced to serial
if (newMode == somSerial)
{
- newPin3Config.val.mode = somSerial;
+ siblingPinConfig.val.mode = somSerial;
}
- else
+ // If the new mode is not serial, and the sibling is serial, set the sibling to PWM (50Hz)
+ else if (siblingPinConfig.val.mode == somSerial)
{
- newPin3Config.val.mode = som50Hz;
+ siblingPinConfig.val.mode = som50Hz;
}
- config.SetPwmChannelRaw(ch, newPin3Config.raw);
+
+ config.SetPwmChannelRaw(ch, siblingPinConfig.raw);
break;
}
}
+
if (oldMode != newMode)
{
- deferExecution(100, [](){
+ deferExecutionMillis(100, [](){
reconfigureSerial();
});
}
- return newMode;
}
static void luaparamMappingOutputMode(struct luaPropertiesCommon *item, uint8_t arg)
{
+ UNUSED(item);
const uint8_t ch = luaMappingChannelOut.properties.u.value - 1;
rx_config_pwm_t newPwmCh;
newPwmCh.raw = config.GetPwmChannel(ch)->raw;
@@ -225,21 +336,18 @@ static void luaparamMappingOutputMode(struct luaPropertiesCommon *item, uint8_t
// Check if pin == 1/3 and do other pin adjustment accordingly
if (GPIO_PIN_PWM_OUTPUTS[ch] == 1)
{
- newPwmCh.val.mode = configureSerialPin(1, 3, oldMode, arg);
+ configureSerialPin(3, oldMode, newPwmCh.val.mode);
}
else if (GPIO_PIN_PWM_OUTPUTS[ch] == 3)
{
- newPwmCh.val.mode = configureSerialPin(3, 1, oldMode, arg);
- }
- else if (arg == somSerial)
- {
- newPwmCh.val.mode = oldMode;
+ configureSerialPin(1, oldMode, newPwmCh.val.mode);
}
config.SetPwmChannelRaw(ch, newPwmCh.raw);
}
static void luaparamMappingInverted(struct luaPropertiesCommon *item, uint8_t arg)
{
+ UNUSED(item);
const uint8_t ch = luaMappingChannelOut.properties.u.value - 1;
rx_config_pwm_t newPwmCh;
newPwmCh.raw = config.GetPwmChannel(ch)->raw;
@@ -264,11 +372,11 @@ static void luaparamSetFalisafe(struct luaPropertiesCommon *item, uint8_t arg)
newStep = lcsExecuting;
msg = "Setting failsafe";
- for (unsigned ch=0; ch<(unsigned)GPIO_PIN_PWM_OUTPUTS_COUNT; ++ch)
+ for (int ch=0; chraw;
// The value must fit into the 10 bit range of the failsafe
+ newPwmCh.raw = config.GetPwmChannel(ch)->raw;
newPwmCh.val.failsafe = CRSF_to_UINT10(constrain(ChannelData[config.GetPwmChannel(ch)->val.inputChannel], CRSF_CHANNEL_VALUE_MIN, CRSF_CHANNEL_VALUE_MAX));
//DBGLN("FSCH(%u) crsf=%u us=%u", ch, ChannelData[ch], newPwmCh.val.failsafe+988U);
config.SetPwmChannelRaw(ch, newPwmCh.raw);
@@ -285,12 +393,29 @@ static void luaparamSetFalisafe(struct luaPropertiesCommon *item, uint8_t arg)
#endif // GPIO_PIN_PWM_OUTPUTS
+#if defined(POWER_OUTPUT_VALUES)
+
+static void luaparamSetPower(struct luaPropertiesCommon* item, uint8_t arg)
+{
+ UNUSED(item);
+ uint8_t newPower = arg + POWERMGNT::getMinPower();
+ if (newPower > POWERMGNT::getMaxPower())
+ {
+ newPower = PWR_MATCH_TX;
+ }
+
+ config.SetPower(newPower);
+ // POWERMGNT::setPower() will be called in updatePower() in the main loop
+}
+
+#endif // POWER_OUTPUT_VALUES
+
static void registerLuaParameters()
{
registerLUAParameter(&luaSerialProtocol, [](struct luaPropertiesCommon* item, uint8_t arg){
config.SetSerialProtocol((eSerialProtocol)arg);
if (config.IsModified()) {
- deferExecution(100, [](){
+ deferExecutionMillis(100, [](){
reconfigureSerial();
});
}
@@ -320,33 +445,22 @@ static void registerLuaParameters()
#if defined(POWER_OUTPUT_VALUES)
luadevGeneratePowerOpts(&luaTlmPower);
- registerLUAParameter(&luaTlmPower, [](struct luaPropertiesCommon* item, uint8_t arg){
- POWERMGNT::setPower((PowerLevels_e)(arg + MinPower));
- // POWERMGNT will constrain the value to the proper level
- config.SetPower(POWERMGNT::currPower());
- });
+ registerLUAParameter(&luaTlmPower, &luaparamSetPower);
#endif
- registerLUAParameter(&luaRateInitIdx, [](struct luaPropertiesCommon* item, uint8_t arg) {
- uint8_t newRate = RATE_MAX - 1 - arg;
- config.SetRateInitialIdx(newRate);
- });
- registerLUAParameter(&luaLoanModel, [](struct luaPropertiesCommon* item, uint8_t arg){
- // Do it when polling for status i.e. going back to idle, because we're going to lose conenction to the TX
- if (arg == 6) {
- deferExecution(200, [](){ InLoanBindingMode = true; });
- }
- sendLuaCommandResponse(&luaLoanModel, arg < 5 ? lcsExecuting : lcsIdle, arg < 5 ? "Sending..." : "");
- });
- registerLUAParameter(&luaReturnModel, [](struct luaPropertiesCommon* item, uint8_t arg){
- // Do it when polling for status i.e. going back to idle, because we're going to lose conenction to the TX
- if (arg == 6) {
- deferExecution(200, []() { returnModelFromLoan = true; });
- }
- sendLuaCommandResponse(&luaReturnModel, arg < 5 ? lcsExecuting : lcsIdle, arg < 5 ? "Sending..." : "");
- });
+
+ // Teamrace
+ registerLUAParameter(&luaTeamraceFolder);
+ registerLUAParameter(&luaTeamraceChannel, [](struct luaPropertiesCommon* item, uint8_t arg) {
+ config.SetTeamraceChannel(arg + AUX2);
+ }, luaTeamraceFolder.common.id);
+ registerLUAParameter(&luaTeamracePosition, [](struct luaPropertiesCommon* item, uint8_t arg) {
+ config.SetTeamracePosition(arg);
+ }, luaTeamraceFolder.common.id);
+
#if defined(GPIO_PIN_PWM_OUTPUTS)
if (OPT_HAS_SERVO_OUTPUT)
{
+ luaparamMappingChannelOut(&luaMappingOutputMode.common, luaMappingChannelOut.properties.u.value);
registerLUAParameter(&luaMappingFolder);
registerLUAParameter(&luaMappingChannelOut, &luaparamMappingChannelOut, luaMappingFolder.common.id);
registerLUAParameter(&luaMappingChannelIn, &luaparamMappingChannelIn, luaMappingFolder.common.id);
@@ -356,9 +470,20 @@ static void registerLuaParameters()
}
#endif
+ registerLUAParameter(&luaVolatileBind, [](struct luaPropertiesCommon* item, uint8_t arg) {
+ config.SetVolatileBind(arg);
+ });
+ registerLUAParameter(&luaBindMode, [](struct luaPropertiesCommon* item, uint8_t arg){
+ // Complete when TX polls for status i.e. going back to idle, because we're going to lose connection
+ if (arg == lcsQuery) {
+ deferExecutionMillis(200, [](){ BindingModeRequest = true; });
+ }
+ sendLuaCommandResponse(&luaBindMode, arg < 5 ? lcsExecuting : lcsIdle, arg < 5 ? "Entering..." : "");
+ });
+
registerLUAParameter(&luaModelNumber);
registerLUAParameter(&luaELRSversion);
- registerLUAParameter(NULL);
+ registerLUAParameter(nullptr);
}
static int event()
@@ -378,9 +503,14 @@ static int event()
}
#if defined(POWER_OUTPUT_VALUES)
- setLuaTextSelectionValue(&luaTlmPower, config.GetPower() - MinPower);
+ // The last item (for MatchTX) will be MaxPower - MinPower + 1
+ uint8_t luaPwrVal = (config.GetPower() == PWR_MATCH_TX) ? POWERMGNT::getMaxPower() + 1 : config.GetPower();
+ setLuaTextSelectionValue(&luaTlmPower, luaPwrVal - POWERMGNT::getMinPower());
#endif
- setLuaTextSelectionValue(&luaRateInitIdx, RATE_MAX - 1 - config.GetRateInitialIdx());
+
+ // Teamrace
+ setLuaTextSelectionValue(&luaTeamraceChannel, config.GetTeamraceChannel() - AUX2);
+ setLuaTextSelectionValue(&luaTeamracePosition, config.GetTeamracePosition());
#if defined(GPIO_PIN_PWM_OUTPUTS)
if (OPT_HAS_SERVO_OUTPUT)
@@ -401,6 +531,7 @@ static int event()
itoa(config.GetModelId(), modelString, 10);
setLuaStringValue(&luaModelNumber, modelString);
}
+ setLuaTextSelectionValue(&luaVolatileBind, config.GetVolatileBind());
return DURATION_IMMEDIATELY;
}
@@ -408,7 +539,7 @@ static int timeout()
{
luaHandleUpdateParameter();
// Receivers can only `UpdateParamReq == true` every 4th packet due to the transmitter cadence in 1:2
- // Channels, Downlink Telem Slot, Uplink Telem (the write command), Downlink Telem Slot...
+ // Channels, Downlink Telemetry Slot, Uplink Telemetry (the write command), Downlink Telemetry Slot...
// (interval * 4 / 1000) or 1 second if not connected
return (connectionState == connected) ? ExpressLRS_currAirRate_Modparams->interval / 250 : 1000;
}
@@ -421,7 +552,7 @@ static int start()
}
device_t LUA_device = {
- .initialize = NULL,
+ .initialize = nullptr,
.start = start,
.event = event,
.timeout = timeout
diff --git a/src/lib/LUA/rxtx_devLua.cpp b/src/lib/LUA/rxtx_devLua.cpp
index 511181375d..3c9355c25f 100644
--- a/src/lib/LUA/rxtx_devLua.cpp
+++ b/src/lib/LUA/rxtx_devLua.cpp
@@ -1,11 +1,15 @@
#include "rxtx_devLua.h"
#include "POWERMGNT.h"
-char strPowerLevels[] = "10;25;50;100;250;500;1000;2000";
+char strPowerLevels[] = "10;25;50;100;250;500;1000;2000;MatchTX ";
const char STR_EMPTYSPACE[] = { 0 };
const char STR_LUA_PACKETRATES[] =
#if defined(RADIO_SX127X)
"D50Hz(-112dBm);25Hz(-123dBm);50Hz(-120dBm);100Hz(-117dBm);100Hz Full(-112dBm);200Hz(-112dBm)";
+#elif defined(RADIO_LR1121)
+ "50Hz 2.4G;100Hz Full 2.4G;150Hz 2.4G;250Hz 2.4G;333Hz Full 2.4G;500Hz 2.4G;" // 2.4G
+ "50Hz Low Band;100Hz Low Band;100Hz Full Low Band;200Hz Low Band;200Hz Full Low Band;250Hz Low Band;" // 900M
+ "X100Hz Full(-112dBm);X150Hz(-112dBm)"; // Dual
#elif defined(RADIO_SX128X)
"50Hz(-115dBm);100Hz Full(-112dBm);150Hz(-112dBm);250Hz(-108dBm);333Hz Full(-105dBm);500Hz(-105dBm);"
"D250(-104dBm);D500(-104dBm);F500(-104dBm);F1000(-104dBm)";
@@ -20,7 +24,7 @@ void luadevGeneratePowerOpts(luaItem_selection *luaPower)
char *out = strPowerLevels;
PowerLevels_e pwr = PWR_10mW;
// Count the semicolons to move `out` to point to the MINth item
- while (pwr < MinPower)
+ while (pwr < POWERMGNT::getMinPower())
{
while (*out++ != ';') ;
pwr = (PowerLevels_e)((unsigned int)pwr + 1);
@@ -41,4 +45,12 @@ void luadevGeneratePowerOpts(luaItem_selection *luaPower)
pwr = (PowerLevels_e)((unsigned int)pwr + 1);
}
*out = '\0';
+
+#if defined(TARGET_RX)
+ // The RX has the dynamic option added on to the end
+ // the space on the end is to make it display "MatchTX mW"
+ // but only if it has more than one power level
+ if (POWERMGNT::getMinPower() != POWERMGNT::getMaxPower())
+ strcat(strPowerLevels, ";MatchTX ");
+#endif
}
diff --git a/src/lib/LUA/tx_devLUA.cpp b/src/lib/LUA/tx_devLUA.cpp
index baf02b082a..a358533977 100644
--- a/src/lib/LUA/tx_devLUA.cpp
+++ b/src/lib/LUA/tx_devLUA.cpp
@@ -2,6 +2,7 @@
#include "rxtx_devLua.h"
#include "CRSF.h"
+#include "CRSFHandset.h"
#include "logging.h"
#include "OTA.h"
#include "FHSS.h"
@@ -29,8 +30,7 @@ static const char luastrDvrAux[] = "Off;" STR_LUA_ALLAUX_UPDOWN;
static const char luastrDvrDelay[] = "0s;5s;15s;30s;45s;1min;2min";
static const char luastrHeadTrackingEnable[] = "Off;On;" STR_LUA_ALLAUX_UPDOWN;
static const char luastrHeadTrackingStart[] = STR_LUA_ALLAUX;
-
-static const char luastrDisabled[] = "Disabled";
+static const char luastrOffOn[] = "Off;On";
#define HAS_RADIO (GPIO_PIN_SCK != UNDEF_PIN)
@@ -104,7 +104,7 @@ static struct luaItem_selection luaSwitch = {
static struct luaItem_selection luaModelMatch = {
{"Model Match", CRSF_TEXT_SELECTION},
0, // value
- "Off;On",
+ luastrOffOn,
modelMatchUnit
};
@@ -176,10 +176,11 @@ static struct luaItem_selection luaVtxBand = {
STR_EMPTYSPACE
};
-static struct luaItem_selection luaVtxChannel = {
- {"Channel", CRSF_TEXT_SELECTION},
+static struct luaItem_int8 luaVtxChannel = {
+ {"Channel", CRSF_UINT8},
0, // value
- "1;2;3;4;5;6;7;8",
+ 1, // min
+ 8, // max
STR_EMPTYSPACE
};
@@ -208,7 +209,7 @@ static struct luaItem_command luaVtxSend = {
struct luaItem_selection luaBluetoothTelem = {
{"BT Telemetry", CRSF_TEXT_SELECTION},
0, // value
- "Off;On",
+ luastrOffOn,
STR_EMPTYSPACE
};
#endif
@@ -222,7 +223,7 @@ static struct luaItem_folder luaBackpackFolder = {
static struct luaItem_selection luaBackpackEnable = {
{"Backpack", CRSF_TEXT_SELECTION},
0, // value
- "Off;On",
+ luastrOffOn,
STR_EMPTYSPACE};
#endif
@@ -256,6 +257,12 @@ static struct luaItem_selection luaHeadTrackingStartChannel = {
luastrHeadTrackingStart,
STR_EMPTYSPACE};
+static struct luaItem_selection luaBackpackTelemetry = {
+ {"Telemetry", CRSF_TEXT_SELECTION},
+ 0, // value
+ luastrOffOn,
+ STR_EMPTYSPACE};
+
static struct luaItem_string luaBackpackVersion = {
{"Version", CRSF_INFO},
backpackVersion};
@@ -270,8 +277,6 @@ extern void VtxTriggerSend();
extern void ResetPower();
extern uint8_t adjustPacketRateForBaud(uint8_t rate);
extern void SetSyncSpam();
-extern void EnterBindingMode();
-extern bool InBindingMode;
extern bool RxWiFiReadyToSend;
#if defined(USE_TX_BACKPACK)
extern bool TxBackpackWiFiReadyToSend;
@@ -283,7 +288,7 @@ extern void setWifiUpdateMode();
#endif
static void luadevUpdateModelID() {
- itoa(CRSF::getModelID(), modelMatchUnit+6, 10);
+ itoa(CRSFHandset::getModelID(), modelMatchUnit+6, 10);
strcat(modelMatchUnit, ")");
}
@@ -335,42 +340,49 @@ static void luadevUpdateBackpackOpts()
if (config.GetBackpackDisable())
{
// If backpack is disabled, set all the Backpack select options to "Disabled"
- luaDvrAux.options = luastrDisabled;
- luaDvrStartDelay.options = luastrDisabled;
- luaDvrStopDelay.options = luastrDisabled;
- luaHeadTrackingEnableChannel.options = luastrDisabled;
- luaHeadTrackingStartChannel.options = luastrDisabled;
+ LUA_FIELD_HIDE(luaDvrAux);
+ LUA_FIELD_HIDE(luaDvrStartDelay);
+ LUA_FIELD_HIDE(luaDvrStopDelay);
+ LUA_FIELD_HIDE(luaHeadTrackingEnableChannel);
+ LUA_FIELD_HIDE(luaHeadTrackingStartChannel);
+ LUA_FIELD_HIDE(luaBackpackTelemetry);
+ LUA_FIELD_HIDE(luaBackpackVersion);
}
else
{
- luaDvrAux.options = luastrDvrAux;
- luaDvrStartDelay.options = luastrDvrDelay;
- luaDvrStopDelay.options = luastrDvrDelay;
- luaHeadTrackingEnableChannel.options = luastrHeadTrackingEnable;
- luaHeadTrackingStartChannel.options = luastrHeadTrackingStart;
+ LUA_FIELD_SHOW(luaDvrAux);
+ LUA_FIELD_SHOW(luaDvrStartDelay);
+ LUA_FIELD_SHOW(luaDvrStopDelay);
+ LUA_FIELD_SHOW(luaHeadTrackingEnableChannel);
+ LUA_FIELD_SHOW(luaHeadTrackingStartChannel);
+ LUA_FIELD_SHOW(luaBackpackTelemetry);
+ LUA_FIELD_SHOW(luaBackpackVersion);
}
}
#if defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)
+static void setBleJoystickMode()
+{
+ connectionState = bleJoystick;
+}
+
static void luahandWifiBle(struct luaPropertiesCommon *item, uint8_t arg)
{
struct luaItem_command *cmd = (struct luaItem_command *)item;
- std::function setTargetState;
+ void (*setTargetState)();
connectionState_e targetState;
const char *textConfirm;
const char *textRunning;
if ((void *)item == (void *)&luaWebUpdate)
{
- setTargetState = setWifiUpdateMode;
+ setTargetState = &setWifiUpdateMode;
textConfirm = "Enter WiFi Update?";
textRunning = "WiFi Running...";
targetState = wifiUpdate;
}
else
{
- setTargetState = []() {
- connectionState = bleJoystick;
- };
+ setTargetState = &setBleJoystickMode;
textConfirm = "Start BLE Joystick?";
textRunning = "Joystick Running...";
targetState = bleJoystick;
@@ -416,7 +428,7 @@ static void luahandSimpleSendCmd(struct luaPropertiesCommon *item, uint8_t arg)
if ((void *)item == (void *)&luaBind)
{
msg = "Binding...";
- EnterBindingMode();
+ EnterBindingModeSafely();
}
else if ((void *)item == (void *)&luaVtxSend)
{
@@ -476,7 +488,7 @@ static void updateFolderName_VtxAdmin()
vtxFolderDynamicName[vtxFolderLabelOffset++] = folderNameSeparator[1];
// Channel
- vtxFolderLabelOffset += findLuaSelectionLabel(&luaVtxChannel, &vtxFolderDynamicName[vtxFolderLabelOffset], config.GetVtxChannel());
+ vtxFolderDynamicName[vtxFolderLabelOffset++] = '1' + config.GetVtxChannel();
// VTX Power
uint8_t vtxPwr = config.GetVtxPower();
@@ -521,9 +533,9 @@ static void updateFolderName_VtxAdmin()
****/
static void luadevUpdateBadGood()
{
- itoa(CRSF::BadPktsCountResult, luaBadGoodString, 10);
+ itoa(CRSFHandset::BadPktsCountResult, luaBadGoodString, 10);
strcat(luaBadGoodString, "/");
- itoa(CRSF::GoodPktsCountResult, luaBadGoodString + strlen(luaBadGoodString), 10);
+ itoa(CRSFHandset::GoodPktsCountResult, luaBadGoodString + strlen(luaBadGoodString), 10);
}
/***
@@ -556,6 +568,9 @@ static void registerLuaParameters()
{
if (HAS_RADIO) {
registerLUAParameter(&luaAirRate, [](struct luaPropertiesCommon *item, uint8_t arg) {
+#if defined(RADIO_LR1121) // Janky fix to order menu correctly
+ arg = (arg + 4) % RATE_MAX;
+#endif
if (arg < RATE_MAX)
{
uint8_t selectedRate = RATE_MAX - 1 - arg;
@@ -625,8 +640,8 @@ static void registerLuaParameters()
msp.makeCommand();
msp.function = MSP_SET_RX_CONFIG;
msp.addByte(MSP_ELRS_MODEL_ID);
- msp.addByte(newModelMatch ? CRSF::getModelID() : 0xff);
- CRSF::AddMspMessage(&msp);
+ msp.addByte(newModelMatch ? CRSFHandset::getModelID() : 0xff);
+ CRSF::AddMspMessage(&msp, CRSF_ADDRESS_CRSF_RECEIVER);
}
luadevUpdateModelID();
});
@@ -664,7 +679,7 @@ static void registerLuaParameters()
config.SetVtxBand(arg);
}, luaVtxFolder.common.id);
registerLUAParameter(&luaVtxChannel, [](struct luaPropertiesCommon *item, uint8_t arg) {
- config.SetVtxChannel(arg);
+ config.SetVtxChannel(arg - 1);
}, luaVtxFolder.common.id);
registerLUAParameter(&luaVtxPwr, [](struct luaPropertiesCommon *item, uint8_t arg) {
config.SetVtxPower(arg);
@@ -720,7 +735,6 @@ static void registerLuaParameters()
config.SetDvrStopDelay(arg);
},
luaBackpackFolder.common.id);
-
registerLUAParameter(
&luaHeadTrackingEnableChannel, [](luaPropertiesCommon *item, uint8_t arg) {
config.SetPTREnableChannel(arg);
@@ -731,6 +745,10 @@ static void registerLuaParameters()
config.SetPTRStartChannel(arg);
},
luaBackpackFolder.common.id);
+ registerLUAParameter(
+ &luaBackpackTelemetry, [](luaPropertiesCommon *item, uint8_t arg) {
+ config.SetBackpackTlmEnabled(arg);
+ }, luaBackpackFolder.common.id);
registerLUAParameter(&luaBackpackVersion, nullptr, luaBackpackFolder.common.id);
}
@@ -764,6 +782,9 @@ static int event()
return DURATION_NEVER;
}
uint8_t currentRate = adjustPacketRateForBaud(config.GetRate());
+#if defined(RADIO_LR1121) // Janky fix to order menu correctly
+ currentRate = (currentRate + 4) % RATE_MAX;
+#endif
setLuaTextSelectionValue(&luaAirRate, RATE_MAX - 1 - currentRate);
setLuaTextSelectionValue(&luaTlmRate, config.GetTlm());
setLuaTextSelectionValue(&luaSwitch, config.GetSwitchMode());
@@ -784,7 +805,7 @@ static int event()
setLuaTextSelectionValue(&luaDynamicPower, dynamic);
setLuaTextSelectionValue(&luaVtxBand, config.GetVtxBand());
- setLuaTextSelectionValue(&luaVtxChannel, config.GetVtxChannel());
+ setLuaUint8Value(&luaVtxChannel, config.GetVtxChannel() + 1);
setLuaTextSelectionValue(&luaVtxPwr, config.GetVtxPower());
setLuaTextSelectionValue(&luaVtxPit, config.GetVtxPitmode());
if (OPT_USE_TX_BACKPACK)
@@ -797,6 +818,7 @@ static int event()
setLuaTextSelectionValue(&luaDvrStopDelay, config.GetBackpackDisable() ? 0 : config.GetDvrStopDelay());
setLuaTextSelectionValue(&luaHeadTrackingEnableChannel, config.GetBackpackDisable() ? 0 : config.GetPTREnableChannel());
setLuaTextSelectionValue(&luaHeadTrackingStartChannel, config.GetBackpackDisable() ? 0 : config.GetPTRStartChannel());
+ setLuaTextSelectionValue(&luaBackpackTelemetry, config.GetBackpackTlmEnabled() ? 1 : 0);
setLuaStringValue(&luaBackpackVersion, backpackVersion);
}
#if defined(TARGET_TX_FM30)
@@ -821,7 +843,7 @@ static int start()
{
return DURATION_NEVER;
}
- CRSF::RecvParameterUpdate = &luaParamUpdateReq;
+ handset->registerParameterUpdateCallback(luaParamUpdateReq);
registerLuaParameters();
setLuaStringValue(&luaInfo, luaBadGoodString);
diff --git a/src/lib/MSP/msp.h b/src/lib/MSP/msp.h
index 066d71dbdb..6203128606 100644
--- a/src/lib/MSP/msp.h
+++ b/src/lib/MSP/msp.h
@@ -4,9 +4,8 @@
// TODO: MSP_PORT_INBUF_SIZE should be changed to
// dynamically allocate array length based on the payload size
-// Hardcoding payload size to 32 bytes for now, to cover a version
-// number from the backpack
-#define MSP_PORT_INBUF_SIZE 32
+// Hardcoding payload size to 64 bytes for now, to match CRSF TLM
+#define MSP_PORT_INBUF_SIZE 64
#define CHECK_PACKET_PARSING() \
if (packet->readError) {\
diff --git a/src/lib/MSP/msptypes.h b/src/lib/MSP/msptypes.h
index 63ee18bb71..3b6eb210f6 100644
--- a/src/lib/MSP/msptypes.h
+++ b/src/lib/MSP/msptypes.h
@@ -24,8 +24,9 @@
#define MSP_ELRS_SET_TX_BACKPACK_WIFI_MODE 0x0C
#define MSP_ELRS_SET_VRX_BACKPACK_WIFI_MODE 0x0D
#define MSP_ELRS_SET_RX_WIFI_MODE 0x0E
-#define MSP_ELRS_SET_RX_LOAN_MODE 0x0F
+//#define MSP_ELRS_SET_RX_LOAN_MODE 0x0F // REMOVED
#define MSP_ELRS_GET_BACKPACK_VERSION 0x10
+#define MSP_ELRS_BACKPACK_CRSF_TLM 0x11
#define MSP_ELRS_POWER_CALI_GET 0x20
#define MSP_ELRS_POWER_CALI_SET 0x21
diff --git a/src/lib/MSPVTX/devMSPVTX.cpp b/src/lib/MSPVTX/devMSPVTX.cpp
index 0fea1aedf2..4548a854c3 100644
--- a/src/lib/MSPVTX/devMSPVTX.cpp
+++ b/src/lib/MSPVTX/devMSPVTX.cpp
@@ -4,6 +4,7 @@
#include "devVTXSPI.h"
#include "freqTable.h"
#include "CRSF.h"
+#include "msptypes.h"
#include "hwTimer.h"
/**
@@ -17,6 +18,7 @@
#define FC_QUERY_PERIOD_MS 200 // poll every 200ms
#define MSP_VTX_FUNCTION_OFFSET 7
#define MSP_VTX_PAYLOAD_OFFSET 11
+#define MSP_VTX_TIMEOUT_NO_CONNECTION 5000
typedef enum
{
@@ -26,6 +28,7 @@ typedef enum
SET_RCE_PIT_MODE,
SEND_EEPROM_WRITE,
MONITORING,
+ STOP_MSPVTX,
MSP_STATE_MAX
} mspVtxState_e;
@@ -37,7 +40,7 @@ static uint8_t checkingIndex = 0;
static uint8_t pitMode = 0;
static uint8_t power = 0;
static uint8_t channel = 0;
-static uint8_t mspState = GET_VTX_TABLE_SIZE;
+static uint8_t mspState = STOP_MSPVTX;
static void sendCrsfMspToFC(uint8_t *mspFrame, uint8_t mspFrameSize)
{
@@ -217,7 +220,7 @@ void mspVtxProcessPacket(uint8_t *packet)
power = 1;
}
- if (power >= NUM_POWER_LEVELS)
+ if (power > NUM_POWER_LEVELS)
{
power = 3; // 25 mW
}
@@ -347,7 +350,15 @@ static void mspVtxStateUpdate(void)
void disableMspVtx(void)
{
- mspState = MSP_STATE_MAX;
+ mspState = STOP_MSPVTX;
+}
+
+static void initialize()
+{
+ if (OPT_HAS_VTX_SPI)
+ {
+ mspState = GET_VTX_TABLE_SIZE;
+ }
}
static int event(void)
@@ -361,6 +372,11 @@ static int event(void)
static int timeout(void)
{
+ if (mspState == STOP_MSPVTX || (mspState != MONITORING && millis() > MSP_VTX_TIMEOUT_NO_CONNECTION))
+ {
+ return DURATION_NEVER;
+ }
+
if (hwTimer::running && !hwTimer::isTick)
{
// Only run code during rx free time or when disconnected.
@@ -372,8 +388,8 @@ static int timeout(void)
}
device_t MSPVTx_device = {
- .initialize = NULL,
- .start = NULL,
+ .initialize = initialize,
+ .start = nullptr,
.event = event,
.timeout = timeout
};
diff --git a/src/lib/MeanAccumulator/MeanAccumulator.h b/src/lib/MeanAccumulator/MeanAccumulator.h
index 281a85cde6..658b2967b6 100644
--- a/src/lib/MeanAccumulator/MeanAccumulator.h
+++ b/src/lib/MeanAccumulator/MeanAccumulator.h
@@ -14,14 +14,19 @@ class MeanAccumulator
{
if (_count)
{
- IncrementType retVal = _accumulator / _count;
+ _previousMean = _accumulator / _count;
reset();
- return retVal;
+ return _previousMean;
}
return NoValueReturn;
}
+ IncrementType previousMean()
+ {
+ return _previousMean;
+ }
+
void reset()
{
_accumulator = 0;
@@ -36,4 +41,5 @@ class MeanAccumulator
private:
StorageType _accumulator;
StorageType _count;
+ IncrementType _previousMean;
};
\ No newline at end of file
diff --git a/src/lib/OPTIONS/hardware.cpp b/src/lib/OPTIONS/hardware.cpp
index ad59b12ab9..2e58354988 100644
--- a/src/lib/OPTIONS/hardware.cpp
+++ b/src/lib/OPTIONS/hardware.cpp
@@ -37,6 +37,7 @@ static const struct {
{HARDWARE_radio_nss, "radio_nss", INT},
{HARDWARE_radio_nss_2, "radio_nss_2", INT},
{HARDWARE_radio_rst, "radio_rst", INT},
+ {HARDWARE_radio_rst_2, "radio_rst_2", INT},
{HARDWARE_radio_sck, "radio_sck", INT},
{HARDWARE_radio_dcdc, "radio_dcdc", BOOL},
{HARDWARE_radio_rfo_hf, "radio_rfo_hf", BOOL},
@@ -59,6 +60,8 @@ static const struct {
{HARDWARE_power_pdet_slope, "power_pdet_slope", FLOAT},
{HARDWARE_power_control, "power_control", INT},
{HARDWARE_power_values, "power_values", ARRAY},
+ {HARDWARE_power_values2, "power_values2", ARRAY},
+ {HARDWARE_power_values_dual, "power_values_dual", ARRAY},
{HARDWARE_joystick, "joystick", INT},
{HARDWARE_joystick_values, "joystick_values", ARRAY},
{HARDWARE_five_way1, "five_way1", INT},
@@ -76,7 +79,7 @@ static const struct {
{HARDWARE_led_green_red, "led_green_red", INT},
{HARDWARE_led_red, "led_red", INT},
{HARDWARE_led_red_invert, "led_red_invert", BOOL},
- {HARDWARE_led_reg_green, "led_red_green", INT},
+ {HARDWARE_led_red_green, "led_red_green", INT},
{HARDWARE_led_rgb, "led_rgb", INT},
{HARDWARE_led_rgb_isgrb, "led_rgb_isgrb", BOOL},
{HARDWARE_ledidx_rgb_status, "ledidx_rgb_status", ARRAY},
@@ -127,6 +130,8 @@ static const struct {
{HARDWARE_vtx_sck, "vtx_sck", INT},
{HARDWARE_vtx_amp_vpd_25mW, "vtx_amp_vpd_25mW", ARRAY},
{HARDWARE_vtx_amp_vpd_100mW, "vtx_amp_vpd_100mW", ARRAY},
+ {HARDWARE_vtx_amp_pwm_25mW, "vtx_amp_pwm_25mW", ARRAY},
+ {HARDWARE_vtx_amp_pwm_100mW, "vtx_amp_pwm_100mW", ARRAY},
};
typedef union {
diff --git a/src/lib/OPTIONS/options.cpp b/src/lib/OPTIONS/options.cpp
index 2589aff10e..20f7a4654f 100644
--- a/src/lib/OPTIONS/options.cpp
+++ b/src/lib/OPTIONS/options.cpp
@@ -1,6 +1,5 @@
#include "targets.h"
#include "options.h"
-#include "helpers.h"
#include "logging.h"
@@ -26,6 +25,12 @@ const char *wifi_ap_password = "expresslrs";
const char *wifi_ap_address = "10.0.0.1";
#if !defined(TARGET_UNIFIED_TX) && !defined(TARGET_UNIFIED_RX)
+
+#if defined(TARGET_RX)
+// This is created by the build_flags.py and used by STM32 (ESP gets it from json)
+#include "flashdiscrim.h"
+#endif
+
const char device_name[] = DEVICE_NAME;
const char *product_name = (const char *)(target_name+4);
@@ -47,6 +52,10 @@ __attribute__ ((used)) static firmware_options_t flashedOptions = {
.domain = 4,
#elif defined(Regulatory_Domain_EU_433)
.domain = 5,
+ #elif defined(Regulatory_Domain_US_433)
+ .domain = 6,
+ #elif defined(Regulatory_Domain_US_433_WIDE)
+ .domain = 7,
#else
#error No regulatory domain defined, please define one in user_defines.txt
#endif
@@ -58,6 +67,11 @@ __attribute__ ((used)) static firmware_options_t flashedOptions = {
.hasUID = false,
.uid = {},
#endif
+#if defined(FLASH_DISCRIM)
+ .flash_discriminator = FLASH_DISCRIM,
+#else
+ .flash_discriminator = 0,
+#endif
#if defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)
#if defined(AUTO_WIFI_ON_INTERVAL)
.wifi_auto_on_interval = AUTO_WIFI_ON_INTERVAL * 1000,
@@ -82,6 +96,8 @@ __attribute__ ((used)) static firmware_options_t flashedOptions = {
.uart_baud = 100000,
#elif defined(USE_SUMD_PROTOCOL)
.uart_baud = 115200,
+#elif defined(USE_HOTT_TLM_PROTOCOL)
+ .uart_baud = 19200,
#elif defined(RCVR_UART_BAUD)
.uart_baud = RCVR_UART_BAUD,
#else
@@ -111,11 +127,7 @@ __attribute__ ((used)) static firmware_options_t flashedOptions = {
#else
.fan_min_runtime = 30,
#endif
-#if defined(UART_INVERTED) // Only on ESP32
- .uart_inverted = true,
-#else
- .uart_inverted = false,
-#endif
+ ._unused1 = false,
#if defined(UNLOCK_HIGHER_POWER)
.unlock_higher_power = true,
#else
@@ -180,10 +192,7 @@ bool options_init()
char product_name[ELRSOPTS_PRODUCTNAME_SIZE+1];
char device_name[ELRSOPTS_DEVICENAME_SIZE+1];
-
-// Discriminator value used to determine if the device has been reflashed and therefore
-// the SPIFSS settings are obsolete and the flashed settings should be used in preference
-uint32_t flash_discriminator;
+uint32_t logo_image;
firmware_options_t firmwareOptions;
@@ -200,12 +209,10 @@ void saveOptions(Stream &stream, bool customised)
{
DynamicJsonDocument doc(1024);
- if (firmwareOptions.hasUID)
+ if (firmwareOptions.wifi_auto_on_interval != -1)
{
- JsonArray uid = doc.createNestedArray("uid");
- copyArray(firmwareOptions.uid, sizeof(firmwareOptions.uid), uid);
+ doc["wifi-on-interval"] = firmwareOptions.wifi_auto_on_interval / 1000;
}
- doc["wifi-on-interval"] = firmwareOptions.wifi_auto_on_interval / 1000;
if (firmwareOptions.home_wifi_ssid[0])
{
doc["wifi-ssid"] = firmwareOptions.home_wifi_ssid;
@@ -214,7 +221,6 @@ void saveOptions(Stream &stream, bool customised)
#if defined(TARGET_UNIFIED_TX)
doc["tlm-interval"] = firmwareOptions.tlm_report_interval;
doc["fan-runtime"] = firmwareOptions.fan_min_runtime;
- doc["uart-inverted"] = firmwareOptions.uart_inverted;
doc["unlock-higher-power"] = firmwareOptions.unlock_higher_power;
doc["airport-uart-baud"] = firmwareOptions.uart_baud;
#else
@@ -224,7 +230,7 @@ void saveOptions(Stream &stream, bool customised)
doc["is-airport"] = firmwareOptions.is_airport;
doc["domain"] = firmwareOptions.domain;
doc["customised"] = customised;
- doc["flash-discriminator"] = flash_discriminator;
+ doc["flash-discriminator"] = firmwareOptions.flash_discriminator;
serializeJson(doc, stream);
}
@@ -309,14 +315,13 @@ static void options_LoadFromFlashOrFile(EspFlashStream &strmFlash)
{
firmwareOptions.hasUID = false;
}
- int32_t wifiInterval = doc["wifi-on-interval"] | -1;
+ int32_t wifiInterval = doc["wifi-on-interval"] | 60;
firmwareOptions.wifi_auto_on_interval = wifiInterval == -1 ? -1 : wifiInterval * 1000;
strlcpy(firmwareOptions.home_wifi_ssid, doc["wifi-ssid"] | "", sizeof(firmwareOptions.home_wifi_ssid));
strlcpy(firmwareOptions.home_wifi_password, doc["wifi-password"] | "", sizeof(firmwareOptions.home_wifi_password));
#if defined(TARGET_UNIFIED_TX)
firmwareOptions.tlm_report_interval = doc["tlm-interval"] | 240U;
firmwareOptions.fan_min_runtime = doc["fan-runtime"] | 30U;
- firmwareOptions.uart_inverted = doc["uart-inverted"] | true;
firmwareOptions.unlock_higher_power = doc["unlock-higher-power"] | false;
#if defined(USE_AIRPORT_AT_BAUD)
firmwareOptions.uart_baud = doc["airport-uart-baud"] | USE_AIRPORT_AT_BAUD;
@@ -336,12 +341,27 @@ static void options_LoadFromFlashOrFile(EspFlashStream &strmFlash)
firmwareOptions.lock_on_first_connection = doc["lock-on-first-connection"] | true;
#endif
firmwareOptions.domain = doc["domain"] | 0;
- flash_discriminator = doc["flash-discriminator"] | 0U;
+ firmwareOptions.flash_discriminator = doc["flash-discriminator"] | 0U;
builtinOptions.clear();
saveOptions(builtinOptions, doc["customised"] | false);
}
+/**
+ * @brief: Put a blank options.json into SPIFFS to force all options to the coded defaults in options_LoadFromFlashOrFile()
+*/
+void options_SetTrueDefaults()
+{
+ DynamicJsonDocument doc(128);
+ // The Regulatory Domain is retained, as there is no sensible default
+ doc["domain"] = firmwareOptions.domain;
+ doc["flash-discriminator"] = firmwareOptions.flash_discriminator;
+
+ File options = SPIFFS.open("/options.json", "w");
+ serializeJson(doc, options);
+ options.close();
+}
+
/**
* @brief: Initializes product_name / device_name either from flash or static values
* @return: true if the names came from flash, or false if the values are default
@@ -400,6 +420,12 @@ bool options_init()
options_LoadFromFlashOrFile(strmFlash);
// hardware.json
bool hasHardware = hardware_init(strmFlash);
+ // flash location of logo image in RGB565 format
+ logo_image = baseAddr + ESP.getSketchSize() +
+ ELRSOPTS_PRODUCTNAME_SIZE +
+ ELRSOPTS_DEVICENAME_SIZE +
+ ELRSOPTS_OPTIONS_SIZE +
+ ELRSOPTS_HARDWARE_SIZE;
debugFreeInitLogger();
diff --git a/src/lib/OPTIONS/options.h b/src/lib/OPTIONS/options.h
index db4ee49f84..bece7d4ba7 100644
--- a/src/lib/OPTIONS/options.h
+++ b/src/lib/OPTIONS/options.h
@@ -24,6 +24,8 @@ typedef struct _options {
uint8_t domain; // depends on radio chip
uint8_t hasUID;
uint8_t uid[6]; // MY_UID derived from MY_BINDING_PHRASE
+ uint32_t flash_discriminator; // Discriminator value used to determine if the device has been reflashed and therefore
+ // the SPIFSS settings are obsolete and the flashed settings should be used in preference
#if defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)
int32_t wifi_auto_on_interval;
char home_wifi_ssid[33];
@@ -39,7 +41,7 @@ typedef struct _options {
#if defined(TARGET_TX) || defined(UNIT_TEST)
uint32_t tlm_report_interval;
uint32_t fan_min_runtime;
- bool uart_inverted:1;
+ bool _unused1:1;
bool unlock_higher_power:1;
bool is_airport:1;
#if defined(GPIO_PIN_BUZZER)
@@ -60,14 +62,15 @@ constexpr size_t ELRSOPTS_HARDWARE_SIZE = 2048;
extern firmware_options_t firmwareOptions;
extern char product_name[];
extern char device_name[];
+extern uint32_t logo_image;
extern bool options_init();
extern String& getOptions();
extern String& getHardware();
extern void saveOptions();
-extern uint32_t flash_discriminator;
#include "EspFlashStream.h"
-extern bool options_HasStringInFlash(EspFlashStream &strmFlash);
+bool options_HasStringInFlash(EspFlashStream &strmFlash);
+void options_SetTrueDefaults();
#else
extern firmware_options_t firmwareOptions;
extern const char device_name[];
diff --git a/src/lib/OTA/OTA.cpp b/src/lib/OTA/OTA.cpp
index 8ffe61b401..219b994172 100644
--- a/src/lib/OTA/OTA.cpp
+++ b/src/lib/OTA/OTA.cpp
@@ -8,7 +8,8 @@
#include "OTA.h"
#include "common.h"
-#include
+#include "CRSF.h"
+#include
static_assert(sizeof(OTA_Packet4_s) == OTA4_PACKET_SIZE, "OTA4 packet stuct is invalid!");
static_assert(sizeof(OTA_Packet8_s) == OTA8_PACKET_SIZE, "OTA8 packet stuct is invalid!");
@@ -142,21 +143,7 @@ void ICACHE_RAM_ATTR GenerateChannelDataHybrid8(OTA_Packet_s * const otaPktPtr,
if (bitclearedSwitchIndex == 6)
value = CRSF_to_N(channelData[6 + 1 + 4], 16);
else
- {
- // AUX2-7 are Low Resolution, "7pos" 6+center (3-bit)
- // The output is mapped evenly across 6 output values (0-5)
- // with a special value 7 indicating the middle so it works
- // with switches with a middle position as well as 6-position
- const uint16_t CHANNEL_BIN_COUNT = 6;
- const uint16_t CHANNEL_BIN_SIZE = (CRSF_CHANNEL_VALUE_MAX - CRSF_CHANNEL_VALUE_MIN) / CHANNEL_BIN_COUNT;
- uint16_t ch = channelData[bitclearedSwitchIndex + 1 + 4];
- // If channel is within 1/4 a BIN of being in the middle use special value 7
- if (ch < (CRSF_CHANNEL_VALUE_MID-CHANNEL_BIN_SIZE/4)
- || ch > (CRSF_CHANNEL_VALUE_MID+CHANNEL_BIN_SIZE/4))
- value = CRSF_to_N(ch, CHANNEL_BIN_COUNT);
- else
- value = 7;
- } // If not 16-pos
+ value = CRSF_to_SWITCH3b(channelData[bitclearedSwitchIndex + 1 + 4]);
ota4->rc.switches =
TelemetryStatus << 6 |
@@ -417,7 +404,7 @@ bool ICACHE_RAM_ATTR UnpackChannelDataHybridWide(OTA_Packet_s const * const otaP
TelemetryStatus = (switchByte & 0b01000000) >> 6;
if (switchIndex == 7)
{
- CRSF::LinkStatistics.uplink_TX_Power = switchByte & 0b111111;
+ CRSF::updateUplinkPower(switchByte & 0b111111);
}
else
{
@@ -477,7 +464,7 @@ bool ICACHE_RAM_ATTR UnpackChannelData8ch(OTA_Packet_s const * const otaPktPtr,
UnpackChannels4x10ToUInt11(&ota8->rc.chHigh, &channelData[chDstHigh]);
#endif
// Restore the uplink_TX_Power range 0-7 -> 1-8
- CRSF::LinkStatistics.uplink_TX_Power = ota8->rc.uplinkPower + 1;
+ CRSF::updateUplinkPower(ota8->rc.uplinkPower + 1);
return ota8->rc.telemetryStatus;
}
#endif
@@ -491,6 +478,8 @@ bool ICACHE_RAM_ATTR ValidatePacketCrcFull(OTA_Packet_s * const otaPktPtr)
bool ICACHE_RAM_ATTR ValidatePacketCrcStd(OTA_Packet_s * const otaPktPtr)
{
+ uint8_t backupCrcHigh = otaPktPtr->std.crcHigh;
+
uint16_t const inCRC = ((uint16_t)otaPktPtr->std.crcHigh << 8) + otaPktPtr->std.crcLow;
// For smHybrid the CRC only has the packet type in byte 0
// For smWide the FHSS slot is added to the CRC in byte 0 on PACKET_TYPE_RCDATAs
@@ -506,6 +495,9 @@ bool ICACHE_RAM_ATTR ValidatePacketCrcStd(OTA_Packet_s * const otaPktPtr)
}
uint16_t const calculatedCRC =
ota_crc.calc((uint8_t*)otaPktPtr, OTA4_CRC_CALC_LEN, OtaCrcInitializer);
+
+ otaPktPtr->std.crcHigh = backupCrcHigh;
+
return inCRC == calculatedCRC;
}
@@ -579,10 +571,11 @@ void OtaUpdateSerializers(OtaSwitchMode_e const switchMode, uint8_t packetSize)
OtaSwitchModeCurrent = switchMode;
}
-void OtaPackAirportData(OTA_Packet_s * const otaPktPtr, FIFO_GENERIC * inputBuffer)
+void OtaPackAirportData(OTA_Packet_s * const otaPktPtr, FIFO *inputBuffer)
{
otaPktPtr->std.type = PACKET_TYPE_TLM;
+ inputBuffer->lock();
uint8_t count = inputBuffer->size();
if (OtaIsFullRes)
{
@@ -597,18 +590,19 @@ void OtaPackAirportData(OTA_Packet_s * const otaPktPtr, FIFO_GENERICpopBytes(otaPktPtr->std.airport.payload, count);
otaPktPtr->std.airport.type = ELRS_TELEMETRY_TYPE_DATA;
}
+ inputBuffer->unlock();
}
-void OtaUnpackAirportData(OTA_Packet_s const * const otaPktPtr, FIFO_GENERIC * outputBuffer)
+void OtaUnpackAirportData(OTA_Packet_s const * const otaPktPtr, FIFO *outputBuffer)
{
if (OtaIsFullRes)
{
uint8_t count = otaPktPtr->full.airport.count;
- outputBuffer->pushBytes(otaPktPtr->full.airport.payload, count);
+ outputBuffer->atomicPushBytes(otaPktPtr->full.airport.payload, count);
}
else
{
uint8_t count = otaPktPtr->std.airport.count;
- outputBuffer->pushBytes(otaPktPtr->std.airport.payload, count);
+ outputBuffer->atomicPushBytes(otaPktPtr->std.airport.payload, count);
}
}
diff --git a/src/lib/OTA/OTA.h b/src/lib/OTA/OTA.h
index 6cc3abf8eb..efe7ecbc9b 100644
--- a/src/lib/OTA/OTA.h
+++ b/src/lib/OTA/OTA.h
@@ -1,11 +1,13 @@
#ifndef H_OTA
#define H_OTA
-#include
#include
+
#include "crc.h"
-#include "devCRSF.h"
-#include "FIFO_GENERIC.h"
+#include "CRSF.h"
+#include "crsf_protocol.h"
+#include "telemetry_protocol.h"
+#include "FIFO.h"
#define OTA4_PACKET_SIZE 8U
#define OTA4_CRC_CALC_LEN offsetof(OTA_Packet4_s, crcLow)
@@ -164,8 +166,8 @@ void OtaUpdateSerializers(OtaSwitchMode_e const mode, uint8_t packetSize);
extern OtaSwitchMode_e OtaSwitchModeCurrent;
// CRC
-typedef std::function ValidatePacketCrc_t;
-typedef std::function GeneratePacketCrc_t;
+typedef bool (*ValidatePacketCrc_t)(OTA_Packet_s * const otaPktPtr);
+typedef void (*GeneratePacketCrc_t)(OTA_Packet_s * const otaPktPtr);
extern ValidatePacketCrc_t OtaValidatePacketCrc;
extern GeneratePacketCrc_t OtaGeneratePacketCrc;
// Value is implicit leading 1, comment is Koopman formatting (implicit trailing 1) https://users.ece.cmu.edu/~koopman/crc/
@@ -174,7 +176,7 @@ extern GeneratePacketCrc_t OtaGeneratePacketCrc;
#define ELRS_CRC16_POLY 0x3D65 // 0x9eb2
#if defined(TARGET_TX) || defined(UNIT_TEST)
-typedef std::function PackChannelData_t;
+typedef void (*PackChannelData_t)(OTA_Packet_s * const otaPktPtr, const uint32_t *channelData, bool TelemetryStatus, uint8_t tlmDenom);
extern PackChannelData_t OtaPackChannelData;
#if defined(UNIT_TEST)
void OtaSetHybrid8NextSwitchIndex(uint8_t idx);
@@ -183,12 +185,12 @@ void OtaSetFullResNextChannelSet(bool next);
#endif
#if defined(TARGET_RX) || defined(UNIT_TEST)
-typedef std::function UnpackChannelData_t;
+typedef bool (*UnpackChannelData_t)(OTA_Packet_s const * const otaPktPtr, uint32_t *channelData, uint8_t tlmDenom);
extern UnpackChannelData_t OtaUnpackChannelData;
#endif
-void OtaPackAirportData(OTA_Packet_s * const otaPktPtr, FIFO_GENERIC * inputBuffer);
-void OtaUnpackAirportData(OTA_Packet_s const * const otaPktPtr, FIFO_GENERIC * outputBuffer);
+void OtaPackAirportData(OTA_Packet_s * const otaPktPtr, FIFO *inputBuffer);
+void OtaUnpackAirportData(OTA_Packet_s const * const otaPktPtr, FIFO *outputBuffer);
#if defined(DEBUG_RCVR_LINKSTATS)
extern uint32_t debugRcvrLinkstatsPacketId;
diff --git a/src/lib/POWERMGNT/POWERMGNT.cpp b/src/lib/POWERMGNT/POWERMGNT.cpp
index b72cd8ed18..d197c03d76 100644
--- a/src/lib/POWERMGNT/POWERMGNT.cpp
+++ b/src/lib/POWERMGNT/POWERMGNT.cpp
@@ -1,4 +1,5 @@
#include "targets.h"
+#include "logging.h"
#include "POWERMGNT.h"
uint8_t powerToCrsfPower(PowerLevels_e Power)
@@ -20,6 +21,23 @@ uint8_t powerToCrsfPower(PowerLevels_e Power)
}
}
+PowerLevels_e crsfpowerToPower(uint8_t crsfpower)
+{
+ switch (crsfpower)
+ {
+ case 1: return PWR_10mW;
+ case 2: return PWR_25mW;
+ case 3: return PWR_100mW;
+ case 4: return PWR_500mW;
+ case 5: return PWR_1000mW;
+ case 6: return PWR_2000mW;
+ case 7: return PWR_250mW;
+ case 8: return PWR_50mW;
+ default:
+ return PWR_10mW;
+ }
+}
+
#ifndef UNIT_TEST
#include "common.h"
@@ -62,6 +80,7 @@ int8_t POWERMGNT::CurrentSX1280Power = 0;
#if defined(TARGET_UNIFIED_TX) || defined(TARGET_UNIFIED_RX)
static const int16_t *powerValues;
+static const int16_t *powerValuesDual;
#else
#if defined(POWER_OUTPUT_VALUES)
static const int16_t powerValues[] = POWER_OUTPUT_VALUES;
@@ -208,6 +227,10 @@ void POWERMGNT::init()
#if defined(TARGET_UNIFIED_TX) || defined(TARGET_UNIFIED_RX)
powerValues = POWER_OUTPUT_VALUES;
+ if (POWER_OUTPUT_VALUES_DUAL != nullptr)
+ {
+ powerValuesDual = POWER_OUTPUT_VALUES_DUAL;
+ }
#endif
#if defined(POWER_OUTPUT_DAC)
TxDAC.init();
@@ -249,36 +272,31 @@ void POWERMGNT::setDefaultPower()
void POWERMGNT::setPower(PowerLevels_e Power)
{
+ Power = constrain(Power, getMinPower(), getMaxPower());
if (Power == CurrentPower)
return;
- if (Power < MinPower)
- {
- Power = MinPower;
- }
- else if (Power > getMaxPower())
- {
- Power = getMaxPower();
- }
#if defined(POWER_OUTPUT_DAC)
// DAC is used e.g. for R9M, ES915TX and Voyager
- Radio.SetOutputPower(0b0000);
int mV = isDomain868() ? powerValues868[Power - MinPower] :powerValues[Power - MinPower];
TxDAC.setPower(mV);
#elif defined(POWER_OUTPUT_ANALOG)
- Radio.SetOutputPower(0b0000);
//Set DACs PA5 & PA4
analogWrite(GPIO_PIN_RFamp_APC1, 3350); //0-4095 2.7V
analogWrite(GPIO_PIN_RFamp_APC2, powerValues[Power - MinPower]);
-#elif defined(POWER_OUTPUT_DACWRITE) && !defined(TARGET_UNIFIED_TX) && !defined(TARGET_UNIFIED_RX)
- Radio.SetOutputPower(0b0000);
- dacWrite(GPIO_PIN_RFamp_APC2, powerValues[Power - MinPower]);
#else
#if defined(PLATFORM_ESP32)
if (POWER_OUTPUT_DACWRITE)
{
- Radio.SetOutputPower(0b0000);
+ if (POWER_OUTPUT_VALUES2 != nullptr)
+ {
+ Radio.SetOutputPower(POWER_OUTPUT_VALUES2[Power - MinPower]);
+ }
+ #if defined(PLATFORM_ESP32_S3)
+ ERRLN("ESP32-S3 does not have a DAC");
+ #else
dacWrite(GPIO_PIN_RFamp_APC2, powerValues[Power - MinPower]);
+ #endif
}
else
#endif
@@ -292,6 +310,14 @@ void POWERMGNT::setPower(PowerLevels_e Power)
Radio.SetOutputPower(CurrentSX1280Power);
}
#endif
+
+#if defined(RADIO_LR1121)
+ if (POWER_OUTPUT_VALUES_DUAL != nullptr)
+ {
+ Radio.SetOutputPower(powerValuesDual[Power - MinPower], false); // Set the high frequency power setting.
+ }
+#endif
+
CurrentPower = Power;
devicesTriggerEvent();
}
diff --git a/src/lib/POWERMGNT/POWERMGNT.h b/src/lib/POWERMGNT/POWERMGNT.h
index cd301c6846..a6d3afc653 100644
--- a/src/lib/POWERMGNT/POWERMGNT.h
+++ b/src/lib/POWERMGNT/POWERMGNT.h
@@ -17,16 +17,6 @@
#define DefaultPower PWR_50mW
#endif
-#if defined(Regulatory_Domain_EU_CE_2400)
- #undef MaxPower
- #define MaxPower PWR_100mW
-
- #if defined(HighPower)
- #undef HighPower
- #define HighPower MaxPower
- #endif
-#endif
-
#if !defined(HighPower)
#define HighPower MaxPower
#endif
@@ -41,10 +31,12 @@ typedef enum
PWR_500mW = 5,
PWR_1000mW = 6,
PWR_2000mW = 7,
- PWR_COUNT = 8
+ PWR_COUNT = 8,
+ PWR_MATCH_TX = PWR_COUNT,
} PowerLevels_e;
uint8_t powerToCrsfPower(PowerLevels_e Power);
+PowerLevels_e crsfpowerToPower(uint8_t crsfpower);
class PowerLevelContainer
{
@@ -70,35 +62,35 @@ class POWERMGNT : public PowerLevelContainer
public:
/**
* @brief Set the power level, constrained to MinPower..MaxPower
- *
+ *
* @param Power the power level to set
*/
static void setPower(PowerLevels_e Power);
/**
* @brief Increment to the next higher power level, capped at MaxPower
- *
+ *
* @return PowerLevels_e the new power level
*/
static PowerLevels_e incPower();
/**
* @brief Decrement to the next lower power level, capped at MinPower
- *
+ *
* @return PowerLevels_e the new power level
*/
static PowerLevels_e decPower();
/**
* @brief Get the currently selected power level
- *
+ *
* @return PowerLevels_e the currently selected power level
*/
static PowerLevels_e currPower() { return CurrentPower; }
/**
* @brief Get the MinPower level supported by this device
- *
+ *
* @return PowerLevels_e the minimum power level supported
*/
static PowerLevels_e getMinPower() { return MinPower; }
@@ -108,20 +100,28 @@ class POWERMGNT : public PowerLevelContainer
* For devices that support the HighPower override, i.e. R9M with the fan hack,
* the MaxPower is normally HighPower unless the 'unlock_higher_power' option
* is set at compile time.
- *
+ *
* @return PowerLevels_e the maximum power level supported
*/
static PowerLevels_e getMaxPower() {
+ PowerLevels_e power;
#if defined(TARGET_RX)
- return MaxPower;
+ power = MaxPower;
#else
- return firmwareOptions.unlock_higher_power ? MaxPower : HighPower;
+ power = firmwareOptions.unlock_higher_power ? MaxPower : HighPower;
+ #endif
+ #if defined(Regulatory_Domain_EU_CE_2400)
+ if (power > PWR_100mW)
+ {
+ power = PWR_100mW;
+ }
#endif
+ return power;
}
/**
* @brief Get the Default power level for this device
- *
+ *
* @return PowerLevels_e the default power level
*/
static PowerLevels_e getDefaultPower();
@@ -133,7 +133,7 @@ class POWERMGNT : public PowerLevelContainer
/**
* @brief Get the currently configured power level in dBm
- *
+ *
* @return uint8_t the dBm for the current power level
*/
static uint8_t getPowerIndBm();
@@ -152,7 +152,7 @@ class POWERMGNT : public PowerLevelContainer
* @brief Get the current value given to the SX1280 for it's output power.
* This value may have been adjusted up/dowm from nominal by the PDET routine, if supported
* by the device (i.e. modules with SKY85321 PA/LNA)
- *
+ *
* @return int8_t the current (adjusted) SX1280 power level
*/
static int8_t currentSX1280Output();
diff --git a/src/lib/PWM/PWM.cpp b/src/lib/PWM/PWM.cpp
new file mode 100644
index 0000000000..613428643b
--- /dev/null
+++ b/src/lib/PWM/PWM.cpp
@@ -0,0 +1,3 @@
+#include "PWM.h"
+
+PWMController PWM;
diff --git a/src/lib/PWM/PWM.h b/src/lib/PWM/PWM.h
new file mode 100644
index 0000000000..d92e06b0ef
--- /dev/null
+++ b/src/lib/PWM/PWM.h
@@ -0,0 +1,42 @@
+#include
+
+typedef int pwm_channel_t;
+
+class PWMController
+{
+public:
+ /**
+ * @brief Allocate a pin/frequency to a PWM channel
+ *
+ * @param pin the GPIO pin to output the PWM signal on
+ * @param frequency the output frequency of the PWM signal
+ * @return the allocated PWM channel or -1 if a channel cannot be assigned
+ */
+ pwm_channel_t allocate(uint8_t pin, uint32_t frequency);
+
+ /**
+ * @brief release a channel back to the allocator
+ *
+ * @param channel the channel to release
+ */
+ void release(pwm_channel_t channel);
+
+ /**
+ * @brief Set the duty cycle of the PWM signal
+ * The duty is in one-tenths of percent (i.e. value is 0-1000)
+ *
+ * @param channel the channel to adjust the signal on
+ * @param duty the high time percentage in tenths of a percent
+ */
+ void setDuty(pwm_channel_t channel, uint16_t duty);
+
+ /**
+ * @brief Set the output PWM signal high for the supplied microseconds
+ *
+ * @param channel the channel to adjust the signal on
+ * @param microseconds the high time in microseconds
+ */
+ void setMicroseconds(pwm_channel_t channel, uint16_t microseconds);
+};
+
+extern PWMController PWM;
diff --git a/src/lib/PWM/PWM_ESP32.cpp b/src/lib/PWM/PWM_ESP32.cpp
new file mode 100644
index 0000000000..7999a2b368
--- /dev/null
+++ b/src/lib/PWM/PWM_ESP32.cpp
@@ -0,0 +1,239 @@
+#include "PWM.h"
+
+#if defined(PLATFORM_ESP32)
+#include
+#include
+
+#include "logging.h"
+
+#if defined(PLATFORM_ESP32_S3)
+#define SPEED_MODE LEDC_LOW_SPEED_MODE
+#else
+#define SPEED_MODE LEDC_HIGH_SPEED_MODE
+#endif
+
+#define MCPWM_CHANNELS 12
+#define LEDC_CHANNELS 8
+
+#define MCPWM_CHANNEL_FLAG 0x100
+#define LEDC_CHANNEL_FLAG 0x200
+
+#define IS_MCPWM_CHANNEL(ch) (ch & MCPWM_CHANNEL_FLAG)
+#define IS_LEDC_CHANNEL(ch) (ch & LEDC_CHANNEL_FLAG)
+
+#define LEDC_CHANNEL(ch) (ch & 0xFF)
+#define MCPWM_CHANNEL(ch) (ch & 0xFF)
+
+static const struct
+{
+ mcpwm_unit_t unit;
+ mcpwm_io_signals_t signal;
+ mcpwm_timer_t timer;
+ mcpwm_generator_t generator;
+} mcpwm_config[] = {
+ {MCPWM_UNIT_0, MCPWM0A, MCPWM_TIMER_0, MCPWM_GEN_A},
+ {MCPWM_UNIT_0, MCPWM0B, MCPWM_TIMER_0, MCPWM_GEN_B},
+ {MCPWM_UNIT_0, MCPWM1A, MCPWM_TIMER_1, MCPWM_GEN_A},
+ {MCPWM_UNIT_0, MCPWM1B, MCPWM_TIMER_1, MCPWM_GEN_B},
+ {MCPWM_UNIT_0, MCPWM2A, MCPWM_TIMER_2, MCPWM_GEN_A},
+ {MCPWM_UNIT_0, MCPWM2B, MCPWM_TIMER_2, MCPWM_GEN_B},
+ {MCPWM_UNIT_1, MCPWM0A, MCPWM_TIMER_0, MCPWM_GEN_A},
+ {MCPWM_UNIT_1, MCPWM0B, MCPWM_TIMER_0, MCPWM_GEN_B},
+ {MCPWM_UNIT_1, MCPWM1A, MCPWM_TIMER_1, MCPWM_GEN_A},
+ {MCPWM_UNIT_1, MCPWM1B, MCPWM_TIMER_1, MCPWM_GEN_B},
+ {MCPWM_UNIT_1, MCPWM2A, MCPWM_TIMER_2, MCPWM_GEN_A},
+ {MCPWM_UNIT_1, MCPWM2B, MCPWM_TIMER_2, MCPWM_GEN_B},
+};
+
+static uint32_t mcpwm_frequencies[MCPWM_CHANNELS] = {0};
+
+static struct
+{
+ int8_t pin;
+ uint8_t resolution_bits;
+ uint32_t interval;
+} ledc_config[LEDC_CHANNELS];
+static uint32_t ledcTimerConfigs[LEDC_TIMER_MAX] = {0};
+
+/*
+ * Modified versions of the ledcSetup/ledcAttachPin from Arduino ESP32 Hal which allows
+ * control of the timer to use rather than being directly associated with the channel. This
+ * allows multiple channels to use the same timer within a LEDC group (high/low speed) when
+ * they are set to the same frequency.
+ */
+static void ledcSetupEx(uint8_t chan, ledc_timer_t timer, uint32_t freq, uint8_t bit_num)
+{
+ ledc_timer_config_t ledc_timer = {
+ .speed_mode = SPEED_MODE,
+ .duty_resolution = (ledc_timer_bit_t)bit_num,
+ .timer_num = timer,
+ .freq_hz = freq,
+ .clk_cfg = LEDC_USE_APB_CLK,
+ };
+ if (ledc_timer_config(&ledc_timer) != ESP_OK)
+ {
+ ERRLN("ledc setup failed!");
+ }
+}
+
+static void ledcAttachPinEx(uint8_t pin, uint8_t chan, ledc_timer_t timer)
+{
+ ledc_channel_config_t ledc_channel = {
+ .gpio_num = pin,
+ .speed_mode = SPEED_MODE,
+ .channel = (ledc_channel_t)chan,
+ .intr_type = LEDC_INTR_DISABLE,
+ .timer_sel = (ledc_timer_t)timer,
+ .duty = 0,
+ .hpoint = 0};
+ auto err = ledc_channel_config(&ledc_channel);
+ if (err != OK)
+ {
+ ERRLN("ledc_channel_config failed with error 0x%x on pin %d", err, pin);
+ }
+}
+
+pwm_channel_t PWMController::allocate(uint8_t pin, uint32_t frequency)
+{
+ // 1. see if we can allocate a MCPWM channel at this frequency
+ int channel = -1;
+ // 1a. see if theres a MCPWM already using this frequency we can piggy-back on
+ for (int i = 0; i < MCPWM_CHANNELS; i++)
+ {
+ if (mcpwm_frequencies[i] == frequency)
+ {
+ if (i % 2 == 0 && mcpwm_frequencies[i + 1] == 0)
+ {
+ channel = i + 1;
+ break;
+ }
+ if (i % 2 == 1 && mcpwm_frequencies[i - 1] == 0)
+ {
+ channel = i - 1;
+ break;
+ }
+ }
+ }
+ if (channel == -1)
+ {
+ // 1b. check if theres an unassigned pair we can allocate to this frequency
+ for (auto i = 0; i < MCPWM_CHANNELS; i += 2)
+ {
+ if (mcpwm_frequencies[i] == 0 && mcpwm_frequencies[i + 1] == 0)
+ {
+ channel = i;
+ break;
+ }
+ }
+ }
+ if (channel != -1)
+ {
+ // 1c. got one, so configure the module to output the signal on the given pin
+ mcpwm_config_t pwm_config = {
+ .frequency = frequency,
+ .cmpr_a = 0,
+ .duty_mode = MCPWM_DUTY_MODE_0,
+ .counter_mode = MCPWM_UP_COUNTER,
+ };
+ auto err = mcpwm_gpio_init(mcpwm_config[channel].unit, mcpwm_config[channel].signal, pin);
+ if (err != ESP_OK)
+ {
+ DBGLN("mcpwm_gpio_init failed with error 0x%x on pin %d", err, pin);
+ }
+ mcpwm_init(mcpwm_config[channel].unit, mcpwm_config[channel].timer, &pwm_config);
+ mcpwm_frequencies[channel] = frequency;
+ return channel | MCPWM_CHANNEL_FLAG;
+ }
+
+ // 2. try for a LEDC channel
+ for (int ch = 0; ch < LEDC_CHANNELS; ch++)
+ {
+ if (ledc_config[ch].resolution_bits == 0)
+ {
+ uint8_t bits = 0;
+ uint32_t clock = 80000000U / frequency; // APB clk src is 80Mhz
+ while (clock >>= 1)
+ {
+ ++bits;
+ }
+ if (bits >= LEDC_TIMER_BIT_MAX)
+ {
+ bits = LEDC_TIMER_BIT_MAX - 1;
+ }
+ for (auto timer_idx = 0; timer_idx < LEDC_TIMER_MAX; timer_idx++)
+ {
+ if (ledcTimerConfigs[timer_idx] == 0)
+ {
+ ledcTimerConfigs[timer_idx] = frequency;
+ ledcSetupEx(ch, (ledc_timer_t)timer_idx, frequency, bits);
+ }
+ if (ledcTimerConfigs[timer_idx] == frequency)
+ {
+ ledcAttachPinEx(pin, ch, (ledc_timer_t)timer_idx);
+ ledc_config[ch].pin = pin;
+ ledc_config[ch].resolution_bits = bits;
+ ledc_config[ch].interval = 1000000U / frequency;
+ DBGLN("allocate ledc_ch %d on pin %d using ledc_tim: %d, bits: %d", ch, pin, timer_idx, bits);
+ return ch | LEDC_CHANNEL_FLAG;
+ }
+ }
+ break;
+ }
+ }
+
+ // 3. bail out, nothing left
+ DBGLN("No MCPWM or LEDC channels available for frequency %dHz", frequency);
+ return -1;
+}
+
+void PWMController::release(pwm_channel_t channel)
+{
+ if (IS_MCPWM_CHANNEL(channel))
+ {
+ auto ch = MCPWM_CHANNEL(channel);
+ mcpwm_stop(mcpwm_config[ch].unit, mcpwm_config[ch].timer);
+ mcpwm_frequencies[ch] = 0;
+ }
+ else if (IS_LEDC_CHANNEL(channel))
+ {
+ auto ch = LEDC_CHANNEL(channel);
+ ledcDetachPin(ledc_config[ch].pin);
+ ledc_config[ch].pin = -1;
+ ledc_config[ch].resolution_bits = 0;
+ ledc_config[ch].interval = 0;
+ }
+ else
+ {
+ ERRLN("Invalid PWM channel %x", channel);
+ }
+}
+
+void PWMController::setDuty(pwm_channel_t channel, uint16_t duty)
+{
+ if (IS_LEDC_CHANNEL(channel))
+ {
+ auto ch = LEDC_CHANNEL(channel);
+ ledcWrite(ch, map(duty, 0, 1000, 0, (1 << ledc_config[ch].resolution_bits) - 1));
+ }
+ else if (IS_MCPWM_CHANNEL(channel))
+ {
+ auto ch = MCPWM_CHANNEL(channel);
+ mcpwm_set_duty(mcpwm_config[ch].unit, mcpwm_config[ch].timer, mcpwm_config[ch].generator, duty / 10.0f);
+ }
+}
+
+void PWMController::setMicroseconds(pwm_channel_t channel, uint16_t microseconds)
+{
+ if (IS_LEDC_CHANNEL(channel))
+ {
+ auto ch = LEDC_CHANNEL(channel);
+ ledcWrite(ch, map(microseconds, 0, ledc_config[ch].interval, 0, (1 << ledc_config[ch].resolution_bits) - 1));
+ }
+ else if (IS_MCPWM_CHANNEL(channel))
+ {
+ auto ch = MCPWM_CHANNEL(channel);
+ mcpwm_set_duty_in_us(mcpwm_config[ch].unit, mcpwm_config[ch].timer, mcpwm_config[ch].generator, microseconds);
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/lib/PWM/PWM_ESP8266.cpp b/src/lib/PWM/PWM_ESP8266.cpp
new file mode 100644
index 0000000000..ad3f9242ce
--- /dev/null
+++ b/src/lib/PWM/PWM_ESP8266.cpp
@@ -0,0 +1,55 @@
+#include "PWM.h"
+
+#if defined(PLATFORM_ESP8266)
+
+#include
+#include "waveform_8266.h"
+
+#define MAX_PWM_CHANNELS 13
+
+static uint16_t refreshInterval[MAX_PWM_CHANNELS] = {0};
+static int8_t pwm_gpio[MAX_PWM_CHANNELS] = {-1};
+
+pwm_channel_t PWMController::allocate(uint8_t pin, uint32_t frequency)
+{
+ for(int channel=0 ; channelhigh transition. For immediate change, stopWaveform()
// first, then it will immediately begin.
-void startWaveform8266(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS) {
- if ((pin > 16) || isFlashInterfacePin(pin) || (timeHighUS == 0)) {
+void startWaveform8266(uint8_t gpio, uint32_t timeHighUS, uint32_t timeLowUS) {
+ if ((gpio > 16) || isFlashInterfacePin(gpio)) {
return;
}
- Waveform *wave = &wvfState.waveform[pin];
+ Waveform *wave = &wvfState.waveform[gpio];
- uint32_t mask = 1<nextHighLowUs = (timeHighUS << 16) | timeLowUS;
@@ -157,14 +157,14 @@ void startWaveform8266(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS) {
}
// Stops a waveform on a pin
-void stopWaveform8266(uint8_t pin) {
+void stopWaveform8266(uint8_t gpio) {
// Can't possibly need to stop anything if there is no timer active
if (!timerRunning) {
return;
}
// 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<nextServiceCycle - now;
@@ -258,7 +258,7 @@ static IRAM_ATTR void timer1Interrupt() {
uint32_t *timeToUpdate;
wvfState.waveformState ^= mask;
if (wvfState.waveformState & mask) {
- if (i == 16) {
+ if (gpio == 16) { // Special handling for GPIO16
GP16O = 1;
}
GPOS = mask;
@@ -277,7 +277,7 @@ static IRAM_ATTR void timer1Interrupt() {
}
nextEdgeCycles = wave->timeHighCycles;
} else {
- if (i == 16) {
+ if (gpio == 16) { // Special handling for GPIO16
GP16O = 0;
}
GPOC = mask;
@@ -301,14 +301,14 @@ static IRAM_ATTR void timer1Interrupt() {
}
// 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();
+ now = GetCycleCountIRQ();
int32_t cycleDeltaNextEvent = nextEventCycle - now;
int32_t cyclesLeftTimeout = timeoutCycle - now;
- done = (cycleDeltaNextEvent > MINIRQTIME) || (cyclesLeftTimeout < 0);
+ done = (cycleDeltaNextEvent > cyclesLeftTimeout) || (cyclesLeftTimeout < 0);
} while (!done);
} // if (wvfState.waveformEnabled)
- int32_t nextEventCycles = nextEventCycle - GetCycleCountIRQ();
+ int32_t nextEventCycles = nextEventCycle - now;
if (nextEventCycles < MINIRQTIME) {
nextEventCycles = MINIRQTIME;
diff --git a/src/lib/PWM/waveform_8266.h b/src/lib/PWM/waveform_8266.h
new file mode 100644
index 0000000000..5cde2ea0e8
--- /dev/null
+++ b/src/lib/PWM/waveform_8266.h
@@ -0,0 +1,12 @@
+#pragma once
+
+void startWaveform8266(uint8_t gpio, uint32_t timeHighUS, uint32_t timeLowUS);
+void stopWaveform8266(uint8_t gpio);
+
+#define startWaveform DO_NOT_USE
+#define startWaveformClockCycles DO_NOT_USE
+#define stopWaveform DO_NOT_USE
+#define setTimer1Callback DO_NOT_USE
+#define _setPWMFreq DO_NOT_USE
+#define _stopPWM DO_NOT_USE
+#define _setPWM DO_NOT_USE
diff --git a/src/lib/SCREEN/OLED/XBMStrings.h b/src/lib/SCREEN/OLED/XBMStrings.h
index fdc7b59153..037161a5e5 100644
--- a/src/lib/SCREEN/OLED/XBMStrings.h
+++ b/src/lib/SCREEN/OLED/XBMStrings.h
@@ -238,8 +238,127 @@ const unsigned char rate_img32[] =
0x3f, 0x00, 0x00, 0x00
};
-// size 44x44
+// 64x64
+const unsigned char switch_img64[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0xC0, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0xC0, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x60, 0xF0, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0x1F, 0x06, 0x60, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x06,
+ 0x60, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x30, 0x06, 0x60, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x60, 0x06, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06,
+ 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x60, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x60, 0x06, 0x60, 0x86, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06,
+ 0x60, 0xC6, 0xFF, 0x0F, 0xFE, 0xFF, 0x61, 0x06, 0x60, 0xC6, 0x01, 0x18,
+ 0x06, 0x00, 0x61, 0x06, 0x60, 0xC6, 0x01, 0x18, 0x06, 0x00, 0x61, 0x06,
+ 0x60, 0xC6, 0x03, 0x10, 0x06, 0x00, 0x61, 0x06, 0x60, 0xC6, 0x03, 0x30,
+ 0x06, 0x00, 0x61, 0x06, 0x60, 0xC6, 0x06, 0x30, 0x06, 0x0F, 0x61, 0x06,
+ 0x60, 0xC6, 0x06, 0x60, 0x06, 0x0D, 0x61, 0x06, 0x60, 0xC6, 0xFC, 0x7F,
+ 0x86, 0x89, 0x61, 0x06, 0x60, 0xC6, 0xFC, 0x7F, 0x86, 0x89, 0x61, 0x06,
+ 0x60, 0xC6, 0x06, 0x60, 0x06, 0x8F, 0x61, 0x06, 0x60, 0xC6, 0x06, 0x20,
+ 0x06, 0x87, 0x61, 0x06, 0x60, 0xC6, 0x03, 0x20, 0x06, 0x80, 0x61, 0x06,
+ 0x60, 0xC6, 0x43, 0x30, 0x06, 0x80, 0x61, 0x06, 0x60, 0xC6, 0x41, 0x30,
+ 0x0E, 0x82, 0x61, 0x06, 0x60, 0xC6, 0x41, 0x30, 0x0E, 0x86, 0x61, 0x06,
+ 0x60, 0xC6, 0x40, 0x30, 0x1E, 0x06, 0x61, 0x06, 0x60, 0xC6, 0x00, 0x30,
+ 0x1E, 0x06, 0x61, 0x06, 0x60, 0xC6, 0x00, 0x30, 0x36, 0x00, 0x63, 0x06,
+ 0x60, 0xC6, 0xE0, 0x30, 0x36, 0x00, 0x63, 0x06, 0x60, 0xC6, 0xF0, 0x31,
+ 0x26, 0x00, 0x63, 0x06, 0x60, 0xC6, 0xB0, 0x31, 0x66, 0x00, 0x63, 0x06,
+ 0x60, 0xC6, 0xB0, 0x31, 0xE6, 0xFF, 0x63, 0x06, 0x60, 0xC6, 0xB0, 0x31,
+ 0x66, 0x00, 0x63, 0x06, 0x60, 0xC6, 0xE0, 0x30, 0x36, 0x00, 0x63, 0x06,
+ 0x60, 0xC6, 0x40, 0x30, 0x36, 0x80, 0x61, 0x06, 0x60, 0xC6, 0x00, 0x30,
+ 0x1E, 0x80, 0x61, 0x06, 0x60, 0xC6, 0x00, 0x30, 0x1E, 0x80, 0x61, 0x06,
+ 0x60, 0xC6, 0x00, 0x30, 0x0E, 0xC0, 0x60, 0x06, 0x60, 0xC6, 0xFF, 0x3F,
+ 0xFE, 0xFF, 0x60, 0x06, 0x60, 0x86, 0xFF, 0x3F, 0xFC, 0x7F, 0x60, 0x06,
+ 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x60, 0x06, 0x00, 0x00,
+ 0x00, 0x00, 0x60, 0x06, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06,
+ 0x60, 0x04, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x60, 0x0C, 0x00, 0x00,
+ 0x00, 0x00, 0x60, 0x06, 0x60, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x38, 0x06,
+ 0x60, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x1F, 0x06, 0x60, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
+ 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x80, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
+
+// 32x32
+const unsigned char switch_img32[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x08, 0x00, 0x00, 0x10,
+ 0xC8, 0xFF, 0xFF, 0x23, 0x68, 0x00, 0x00, 0x24, 0x28, 0x00, 0x00, 0x24,
+ 0x28, 0x00, 0x00, 0x28, 0xA8, 0x3F, 0xFE, 0x29, 0xA8, 0x61, 0x02, 0x29,
+ 0xA8, 0x41, 0x02, 0x29, 0xA8, 0x43, 0x32, 0x29, 0xA8, 0xFE, 0x32, 0x29,
+ 0xA8, 0x43, 0x32, 0x29, 0xA8, 0x41, 0x02, 0x29, 0xA8, 0x49, 0x02, 0x29,
+ 0xA8, 0x40, 0x12, 0x29, 0xA8, 0x40, 0x06, 0x29, 0xA8, 0x4C, 0x0E, 0x29,
+ 0xA8, 0x5C, 0xFE, 0x29, 0xA8, 0x4C, 0x06, 0x29, 0xA8, 0x40, 0x82, 0x28,
+ 0xA8, 0x7F, 0xFE, 0x28, 0x28, 0x00, 0x00, 0x28, 0x28, 0x00, 0x00, 0x28,
+ 0x28, 0x00, 0x00, 0x24, 0xE8, 0xFF, 0xFF, 0x27, 0x08, 0x00, 0x00, 0x10,
+ 0xF8, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
+
+// 64x64
+const unsigned char antenna_img64[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0xE0, 0x00, 0x00,
+ 0x00, 0x80, 0x0F, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x0F, 0x00,
+ 0x00, 0xE0, 0x03, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x07, 0x00,
+ 0x00, 0xE0, 0x03, 0x00, 0x00, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0xE1, 0x80,
+ 0x01, 0x8F, 0x0F, 0x00, 0x00, 0xF0, 0xF1, 0xF0, 0x0F, 0x1F, 0x0F, 0x00,
+ 0x00, 0xF0, 0xF8, 0xF8, 0x3F, 0x1F, 0x1F, 0x00, 0x00, 0xF8, 0xF8, 0xFC,
+ 0x7F, 0x3E, 0x1E, 0x00, 0x00, 0x78, 0x7C, 0xFE, 0x7F, 0x3C, 0x1E, 0x00,
+ 0x00, 0x78, 0x3C, 0x3E, 0xF8, 0x7C, 0x1E, 0x00, 0x00, 0x78, 0x3C, 0x1F,
+ 0xF0, 0x78, 0x3E, 0x00, 0x00, 0x78, 0x3C, 0x0F, 0xF0, 0x78, 0x3C, 0x00,
+ 0x00, 0x78, 0x3E, 0x0F, 0xF0, 0x78, 0x3C, 0x00, 0x00, 0x78, 0x3C, 0x0F,
+ 0xF0, 0x78, 0x3C, 0x00, 0x00, 0x78, 0x3C, 0x1F, 0xF0, 0x78, 0x3E, 0x00,
+ 0x00, 0x78, 0x3C, 0x3E, 0xF8, 0x7C, 0x1E, 0x00, 0x00, 0x78, 0x7C, 0xFE,
+ 0x7F, 0x3C, 0x1E, 0x00, 0x00, 0xF8, 0xF8, 0xFC, 0x7F, 0x3E, 0x1E, 0x00,
+ 0x00, 0xF0, 0xF8, 0xF8, 0x3F, 0x1F, 0x1F, 0x00, 0x00, 0xF0, 0xF1, 0xF0,
+ 0x0F, 0x1F, 0x0F, 0x00, 0x00, 0xE0, 0xE1, 0xF0, 0x1F, 0x8F, 0x0F, 0x00,
+ 0x00, 0xE0, 0x03, 0xF0, 0x1F, 0xC0, 0x07, 0x00, 0x00, 0xC0, 0x07, 0xF8,
+ 0x1E, 0xC0, 0x07, 0x00, 0x00, 0xC0, 0x0F, 0x78, 0x3E, 0xE0, 0x03, 0x00,
+ 0x00, 0x80, 0x0F, 0x7C, 0x3C, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x07, 0x3C,
+ 0x3C, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x7C, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x3E, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E,
+ 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0xFC, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
+ 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0xF7, 0xEF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0xE7,
+ 0xE7, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xE7, 0xCF, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0xC0, 0xF3, 0xDF, 0x07, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFB,
+ 0x9F, 0x07, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0xFE, 0x07, 0x00, 0x00,
+ 0x00, 0x00, 0xE0, 0x7F, 0xFC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F,
+ 0xF8, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x1F, 0xF0, 0x0F, 0x00, 0x00,
+ 0x00, 0x00, 0xF0, 0x0F, 0xF0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07,
+ 0xE0, 0x1F, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x03, 0xC0, 0x3F, 0x00, 0x00,
+ 0x00, 0x00, 0xFC, 0x01, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x01,
+ 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x7F, 0x00, 0x00,
+ 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF,
+ 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
+ 0x00, 0x00, 0x1F, 0x00, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00,
+ 0x00, 0xF0, 0x01, 0x00, 0x00, 0x80, 0x0F, 0x00, 0x00, 0xE0, 0x01, 0x00,
+ 0x00, 0x80, 0x07, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x07, 0x00,
+ 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
+
+//32x32
+const unsigned char antenna_img32[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x80, 0x03, 0x80, 0x01,
+ 0xC0, 0x09, 0xA0, 0x03, 0xC0, 0xCC, 0x33, 0x03, 0xC0, 0xEE, 0x67, 0x07,
+ 0xE0, 0x76, 0x6C, 0x06, 0x60, 0x36, 0x6C, 0x06, 0x60, 0x36, 0x6C, 0x06,
+ 0xC0, 0x76, 0x6E, 0x06, 0xC0, 0xEE, 0x77, 0x03, 0xC0, 0xCD, 0x33, 0x03,
+ 0x80, 0xC1, 0x87, 0x03, 0x80, 0xE3, 0xC6, 0x01, 0x00, 0x60, 0x86, 0x00,
+ 0x00, 0x60, 0x0E, 0x00, 0x00, 0x70, 0x0E, 0x00, 0x00, 0xF0, 0x0F, 0x00,
+ 0x00, 0xF8, 0x1B, 0x00, 0x00, 0xD8, 0x1B, 0x00, 0x00, 0xF8, 0x3F, 0x00,
+ 0x00, 0x7C, 0x3E, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x1C, 0x78, 0x00,
+ 0x00, 0x1E, 0x70, 0x00, 0x00, 0xFE, 0x7F, 0x00, 0x00, 0xFF, 0xFF, 0x00,
+ 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, };
+// size 44x44
const unsigned char wifi_img64[] =
{
0x00, 0x00, 0xc0, 0x1f, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x03, 0x00, 0x00, 0xc0,
diff --git a/src/lib/SCREEN/OLED/oleddisplay.cpp b/src/lib/SCREEN/OLED/oleddisplay.cpp
index 20061576e6..3899192fef 100644
--- a/src/lib/SCREEN/OLED/oleddisplay.cpp
+++ b/src/lib/SCREEN/OLED/oleddisplay.cpp
@@ -117,10 +117,29 @@ void OLEDDisplay::displaySplashScreen()
#else
#ifdef USE_OLED_SPI_SMALL
if (OPT_USE_OLED_SPI_SMALL)
- u8g2->drawXBM(48, 0, 32, 32, elrs32);
+ {
+ auto constexpr sz = 128 * 32 / 8;
+ uint8_t image[sz];
+ if (spi_flash_read(logo_image, image, sz) == ESP_OK)
+ {
+ u8g2->drawXBM(0, 0, 128, 32, image);
+ }
+ }
else
#endif
- u8g2->drawXBM(32, 0, 64, 64, elrs64);
+ {
+ auto constexpr sz = 128 * 64 / 8;
+ uint8_t image[sz];
+ if (spi_flash_read(logo_image, image, sz) == ESP_OK)
+ {
+ u8g2->drawXBM(0, 0, 128, 64, image);
+ }
+
+ char buffer[50];
+ snprintf(buffer, sizeof(buffer), "ELRS-%.6s", version);
+ u8g2->setFont(u8g2_font_profont10_mr);
+ drawCentered(60, buffer);
+ }
#endif
u8g2->sendBuffer();
}
@@ -381,7 +400,7 @@ void OLEDDisplay::displaySending()
static void helperDrawImage(menu_item_t menu)
{
if (OPT_USE_OLED_SPI_SMALL)
- {
+ {
// Adjust these to move them around on the screen
int x_pos = 65;
@@ -391,6 +410,12 @@ static void helperDrawImage(menu_item_t menu)
case STATE_PACKET:
u8g2->drawXBM(x_pos, y_pos, 32, 22, rate_img32);
break;
+ case STATE_SWITCH:
+ u8g2->drawXBM(x_pos, y_pos, 32, 32, switch_img32);
+ break;
+ case STATE_ANTENNA:
+ u8g2->drawXBM(x_pos, y_pos, 32, 32, antenna_img32);
+ break;
case STATE_POWER:
case STATE_POWER_MAX:
case STATE_POWER_DYNAMIC:
@@ -450,6 +475,12 @@ static void helperDrawImage(menu_item_t menu)
case STATE_PACKET:
u8g2->drawXBM(x_pos, y_pos, 64, 44, rate_img64);
break;
+ case STATE_SWITCH:
+ u8g2->drawXBM(x_pos, y_pos, 64, 64, switch_img64);
+ break;
+ case STATE_ANTENNA:
+ u8g2->drawXBM(x_pos, y_pos, 64, 64, antenna_img64);
+ break;
case STATE_POWER:
case STATE_POWER_MAX:
case STATE_POWER_DYNAMIC:
diff --git a/src/lib/SCREEN/TFT/logos.h b/src/lib/SCREEN/TFT/logos.h
index 858e193a4e..883781bf10 100644
--- a/src/lib/SCREEN/TFT/logos.h
+++ b/src/lib/SCREEN/TFT/logos.h
@@ -1,540 +1,5 @@
#include
-#ifdef TARGET_AXIS_THOR_2400_TX
-const unsigned short vendor_logo[0x2120] PROGMEM ={
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0030 (48)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DE, // 0x0040 (64)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0050 (80)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0060 (96)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0070 (112)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DE, // 0x0080 (128)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0090 (144)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00A0 (160)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00B0 (176)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DE, // 0x00C0 (192)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00D0 (208)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00E0 (224)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x00F0 (240)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DE, // 0x0100 (256)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0110 (272)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0120 (288)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0130 (304)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DE, // 0x0140 (320)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0150 (336)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xC618, 0xC618, // 0x0160 (352)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0170 (368)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xFFFF, 0xFFFF, 0xC618, 0xC618, 0xC618, // 0x0180 (384)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xC618, // 0x0190 (400)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01A0 (416)
-0xFFFF, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01B0 (432)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xC618, // 0x01C0 (448)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x01D0 (464)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xFFFF, 0xFFFF, // 0x01E0 (480)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x01F0 (496)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x8410, 0x0000, 0x0000, 0x0000, // 0x0200 (512)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0210 (528)
-0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xC618, 0x8410, 0x8000, 0x8000, // 0x0220 (544)
-0x8410, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x8410, 0x8000, 0x8000, // 0x0230 (560)
-0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8410, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0240 (576)
-0x8410, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0xFFFF, 0xFFFF, // 0x0250 (592)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x8410, 0x8410, 0x0000, 0x0000, // 0x0260 (608)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0270 (624)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, // 0x0280 (640)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0290 (656)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x02A0 (672)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02B0 (688)
-0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xF800, 0xF800, // 0x02C0 (704)
-0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xF800, 0xF800, 0xF800, 0xF800, // 0x02D0 (720)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, // 0x02E0 (736)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xFFFF, 0xFFFF, // 0x02F0 (752)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0300 (768)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0310 (784)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, // 0x0320 (800)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0330 (816)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0340 (832)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0350 (848)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xC618, 0xF800, // 0x0360 (864)
-0xF800, 0xF800, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, // 0x0370 (880)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, // 0x0380 (896)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, // 0x0390 (912)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x03A0 (928)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x03B0 (944)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, // 0x03C0 (960)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03D0 (976)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x03E0 (992)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x03F0 (1008)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xF800, // 0x0400 (1024)
-0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, // 0x0410 (1040)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, // 0x0420 (1056)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, // 0x0430 (1072)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0440 (1088)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0450 (1104)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, // 0x0460 (1120)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0470 (1136)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0480 (1152)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0490 (1168)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, // 0x04A0 (1184)
-0xF800, 0xF800, 0xF800, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, // 0x04B0 (1200)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0xF800, // 0x04C0 (1216)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, // 0x04D0 (1232)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x04E0 (1248)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x04F0 (1264)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, // 0x0500 (1280)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0510 (1296)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0520 (1312)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0530 (1328)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, // 0x0540 (1344)
-0xF800, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, // 0x0550 (1360)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, 0xF800, // 0x0560 (1376)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0570 (1392)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0580 (1408)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0590 (1424)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, // 0x05A0 (1440)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x05B0 (1456)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x05C0 (1472)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x05D0 (1488)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, // 0x05E0 (1504)
-0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, // 0x05F0 (1520)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF800, 0xF800, // 0x0600 (1536)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0610 (1552)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0620 (1568)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0630 (1584)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0640 (1600)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0650 (1616)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0660 (1632)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0670 (1648)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8400, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0680 (1664)
-0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, // 0x0690 (1680)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0xF800, 0xF800, // 0x06A0 (1696)
-0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x06B0 (1712)
-0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x06C0 (1728)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x06D0 (1744)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x06E0 (1760)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x06F0 (1776)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0700 (1792)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0710 (1808)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0720 (1824)
-0xC618, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, // 0x0730 (1840)
-0xF800, 0xF800, 0xF800, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x8410, 0x8410, // 0x0740 (1856)
-0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0750 (1872)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0760 (1888)
-0x0000, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, // 0x0770 (1904)
-0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0780 (1920)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0790 (1936)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x07A0 (1952)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, // 0x07B0 (1968)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x07C0 (1984)
-0x0000, 0xFFFF, 0xC618, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, // 0x07D0 (2000)
-0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x07E0 (2016)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x07F0 (2032)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0800 (2048)
-0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0810 (2064)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0820 (2080)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0830 (2096)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0840 (2112)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0850 (2128)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0860 (2144)
-0x0000, 0x8410, 0xFFFF, 0x8000, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, // 0x0870 (2160)
-0xF800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0880 (2176)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0890 (2192)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, // 0x08A0 (2208)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x08B0 (2224)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x08C0 (2240)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x08D0 (2256)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x08E0 (2272)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, // 0x08F0 (2288)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0900 (2304)
-0x0000, 0x0000, 0xC618, 0xC618, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, // 0x0910 (2320)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x8410, 0x8410, 0x8410, // 0x0920 (2336)
-0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0930 (2352)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0940 (2368)
-0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, // 0x0950 (2384)
-0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0960 (2400)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0970 (2416)
-0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0980 (2432)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, // 0x0990 (2448)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x09A0 (2464)
-0x0000, 0x0000, 0x8410, 0xFFFF, 0x8410, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0xFFFF, // 0x09B0 (2480)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, // 0x09C0 (2496)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x09D0 (2512)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x09E0 (2528)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x09F0 (2544)
-0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0A00 (2560)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0A10 (2576)
-0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0A20 (2592)
-0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, // 0x0A30 (2608)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0A40 (2624)
-0x0000, 0x0000, 0x0000, 0xC618, 0xC618, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, // 0x0A50 (2640)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0A60 (2656)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0A70 (2672)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0A80 (2688)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0A90 (2704)
-0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0AA0 (2720)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0AB0 (2736)
-0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, // 0x0AC0 (2752)
-0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, // 0x0AD0 (2768)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0AE0 (2784)
-0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0AF0 (2800)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0B00 (2816)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0B10 (2832)
-0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x8000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0B20 (2848)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0B30 (2864)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0B40 (2880)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0B50 (2896)
-0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, // 0x0B60 (2912)
-0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, // 0x0B70 (2928)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0B80 (2944)
-0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xC618, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0B90 (2960)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0BA0 (2976)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0BB0 (2992)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0BC0 (3008)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0BD0 (3024)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0BE0 (3040)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0BF0 (3056)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, // 0x0C00 (3072)
-0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0C10 (3088)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0C20 (3104)
-0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xF800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0C30 (3120)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0C40 (3136)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0C50 (3152)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0C60 (3168)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0C70 (3184)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0C80 (3200)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, // 0x0C90 (3216)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0CA0 (3232)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0CB0 (3248)
-0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0CC0 (3264)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0CD0 (3280)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0CE0 (3296)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0CF0 (3312)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0D00 (3328)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0D10 (3344)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0D20 (3360)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, // 0x0D30 (3376)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0D40 (3392)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0D50 (3408)
-0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0D60 (3424)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0D70 (3440)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0D80 (3456)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0D90 (3472)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0DA0 (3488)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0DB0 (3504)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0DC0 (3520)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, // 0x0DD0 (3536)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0DE0 (3552)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0DF0 (3568)
-0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0E00 (3584)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xF800, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0E10 (3600)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0E20 (3616)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0E30 (3632)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0E40 (3648)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0E50 (3664)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0E60 (3680)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, // 0x0E70 (3696)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0E80 (3712)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xC618, 0xFFFF, 0xC618, 0xC618, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0E90 (3728)
-0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0EA0 (3744)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xC618, 0xF800, 0xF800, 0xF800, 0xF800, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0EB0 (3760)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0EC0 (3776)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0ED0 (3792)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xC618, 0x8410, 0x8410, 0x8410, // 0x0EE0 (3808)
-0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0EF0 (3824)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0F00 (3840)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, // 0x0F10 (3856)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0F20 (3872)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0F30 (3888)
-0x0000, 0x0000, 0x0000, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0F40 (3904)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0x8000, 0xF800, 0xF800, 0xF800, 0xC618, 0xFFFF, 0xFFFF, // 0x0F50 (3920)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0F60 (3936)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0F70 (3952)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0F80 (3968)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0F90 (3984)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0FA0 (4000)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0FB0 (4016)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x0FC0 (4032)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0FD0 (4048)
-0x0000, 0x0000, 0x0000, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0FE0 (4064)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xC618, 0xF800, 0xF800, 0xF800, 0xF800, 0xFFFF, 0xFFFF, // 0x0FF0 (4080)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1000 (4096)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x1010 (4112)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x1020 (4128)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1030 (4144)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1040 (4160)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1050 (4176)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1060 (4192)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1070 (4208)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1080 (4224)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x8410, 0xF800, 0xF800, 0xF800, 0x8410, 0xFFFF, // 0x1090 (4240)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x10A0 (4256)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x10B0 (4272)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x10C0 (4288)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x10D0 (4304)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x10E0 (4320)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x10F0 (4336)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1100 (4352)
-0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1110 (4368)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1120 (4384)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xC618, 0xF800, 0xF800, 0xF800, 0xF800, 0xFFFF, // 0x1130 (4400)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1140 (4416)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1150 (4432)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1160 (4448)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1170 (4464)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1180 (4480)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1190 (4496)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x11A0 (4512)
-0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x11B0 (4528)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x11C0 (4544)
-0xC618, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x8410, 0xF800, 0xF800, 0xF800, 0x8410, // 0x11D0 (4560)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x11E0 (4576)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x11F0 (4592)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1200 (4608)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1210 (4624)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1220 (4640)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1230 (4656)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1240 (4672)
-0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1250 (4688)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, // 0x1260 (4704)
-0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xC618, 0xF800, 0xF800, 0xF800, 0xF800, // 0x1270 (4720)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1280 (4736)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1290 (4752)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x12A0 (4768)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x12B0 (4784)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x12C0 (4800)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x12D0 (4816)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x12E0 (4832)
-0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x12F0 (4848)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, // 0x1300 (4864)
-0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x8410, 0xF800, 0xF800, 0xF800, // 0x1310 (4880)
-0xF800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1320 (4896)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1330 (4912)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1340 (4928)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1350 (4944)
-0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1360 (4960)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1370 (4976)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1380 (4992)
-0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1390 (5008)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, // 0x13A0 (5024)
-0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xC618, 0xF800, 0xF800, 0xF800, // 0x13B0 (5040)
-0xF800, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x13C0 (5056)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x13D0 (5072)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x13E0 (5088)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x13F0 (5104)
-0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1400 (5120)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1410 (5136)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1420 (5152)
-0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1430 (5168)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1440 (5184)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xC618, 0xF800, 0xF800, // 0x1450 (5200)
-0xF800, 0xF800, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1460 (5216)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1470 (5232)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1480 (5248)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1490 (5264)
-0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x14A0 (5280)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x14B0 (5296)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x14C0 (5312)
-0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x14D0 (5328)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x14E0 (5344)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xF800, 0xF800, // 0x14F0 (5360)
-0xF800, 0xF800, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1500 (5376)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1510 (5392)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1520 (5408)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, // 0x1530 (5424)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1540 (5440)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, // 0x1550 (5456)
-0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, // 0x1560 (5472)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1570 (5488)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1580 (5504)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0xFFFF, 0xC618, 0xF800, // 0x1590 (5520)
-0x8410, 0x8410, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, // 0x15A0 (5536)
-0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, // 0x15B0 (5552)
-0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, // 0x15C0 (5568)
-0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0xC618, 0xFFFF, 0xFFFF, // 0x15D0 (5584)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x15E0 (5600)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x15F0 (5616)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1600 (5632)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1610 (5648)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1620 (5664)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1630 (5680)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1640 (5696)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1650 (5712)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1660 (5728)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1670 (5744)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1680 (5760)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1690 (5776)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x16A0 (5792)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, // 0x16B0 (5808)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x16C0 (5824)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x16D0 (5840)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x16E0 (5856)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x16F0 (5872)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1700 (5888)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1710 (5904)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1720 (5920)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1730 (5936)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1740 (5952)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, // 0x1750 (5968)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1760 (5984)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1770 (6000)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1780 (6016)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1790 (6032)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x17A0 (6048)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x17B0 (6064)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x17C0 (6080)
-0xFFFF, 0xFFFF, 0xC618, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0xFFFF, 0xFFFF, 0xC618, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, // 0x17D0 (6096)
-0x8410, 0xFFFF, 0xFFFF, 0xC618, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0xC618, 0xFFFF, 0xC618, 0x8410, // 0x17E0 (6112)
-0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0xC618, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, // 0x17F0 (6128)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, // 0x1800 (6144)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1810 (6160)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1820 (6176)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1830 (6192)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1840 (6208)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1850 (6224)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1860 (6240)
-0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1870 (6256)
-0x0000, 0x0000, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x0000, // 0x1880 (6272)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, // 0x1890 (6288)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0x8410, 0x0000, // 0x18A0 (6304)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x18B0 (6320)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x18C0 (6336)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x18D0 (6352)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x18E0 (6368)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x18F0 (6384)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1900 (6400)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1910 (6416)
-0x0000, 0x0000, 0xC618, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xC618, // 0x1920 (6432)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, // 0x1930 (6448)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, // 0x1940 (6464)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1950 (6480)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1960 (6496)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1970 (6512)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1980 (6528)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1990 (6544)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x19A0 (6560)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, // 0x19B0 (6576)
-0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, // 0x19C0 (6592)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0x8410, 0x0000, // 0x19D0 (6608)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xC618, // 0x19E0 (6624)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x0000, // 0x19F0 (6640)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1A00 (6656)
-0x0000, 0x0000, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1A10 (6672)
-0x0000, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1A20 (6688)
-0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, 0x8410, // 0x1A30 (6704)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1A40 (6720)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, // 0x1A50 (6736)
-0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, // 0x1A60 (6752)
-0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, // 0x1A70 (6768)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, // 0x1A80 (6784)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xC618, 0x8410, 0x8410, 0x8410, 0x8410, 0x0000, // 0x1A90 (6800)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1AA0 (6816)
-0x0000, 0x0000, 0xFFFF, 0xC618, 0x0000, 0x0000, 0xC618, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1AB0 (6832)
-0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, // 0x1AC0 (6848)
-0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x8410, 0x8410, 0x8410, 0xFFFF, 0xFFFF, // 0x1AD0 (6864)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1AE0 (6880)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, // 0x1AF0 (6896)
-0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, // 0x1B00 (6912)
-0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0x8410, // 0x1B10 (6928)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, // 0x1B20 (6944)
-0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x8410, 0x0000, 0x0000, // 0x1B30 (6960)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1B40 (6976)
-0x0000, 0x0000, 0x8410, 0xFFFF, 0xC618, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1B50 (6992)
-0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xC618, 0x8410, 0xFFFF, 0x0000, 0x0000, 0xC618, // 0x1B60 (7008)
-0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0x0000, 0x0000, 0x8410, 0x0000, 0x8410, 0x0000, // 0x1B70 (7024)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1B80 (7040)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, // 0x1B90 (7056)
-0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, // 0x1BA0 (7072)
-0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1BB0 (7088)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, // 0x1BC0 (7104)
-0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, // 0x1BD0 (7120)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1BE0 (7136)
-0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1BF0 (7152)
-0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x8410, 0x0000, 0xC618, 0xC618, 0x0000, 0xFFFF, // 0x1C00 (7168)
-0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0x8410, 0x0000, 0x0410, 0x8410, 0x8410, 0xFFFF, 0x0000, // 0x1C10 (7184)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1C20 (7200)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0x8410, 0x0000, 0x0000, // 0x1C30 (7216)
-0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1C40 (7232)
-0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, // 0x1C50 (7248)
-0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, // 0x1C60 (7264)
-0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1C70 (7280)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xC618, 0x8410, 0x8410, 0x8410, 0x8000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1C80 (7296)
-0x0000, 0x0000, 0x0000, 0xC618, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, // 0x1C90 (7312)
-0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x8410, 0x0000, 0x8410, 0xFFFF, 0x8410, 0xFFFF, // 0x1CA0 (7328)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x8410, 0x8000, 0x8000, 0xC618, 0xFFFF, 0x0000, // 0x1CB0 (7344)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1CC0 (7360)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0x0000, 0x0000, // 0x1CD0 (7376)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1CE0 (7392)
-0x8410, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8000, 0xC618, 0xFFFF, // 0x1CF0 (7408)
-0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, // 0x1D00 (7424)
-0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1D10 (7440)
-0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1D20 (7456)
-0x0000, 0x0000, 0x8410, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, // 0x1D30 (7472)
-0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xC618, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0x8410, // 0x1D40 (7488)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, // 0x1D50 (7504)
-0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1D60 (7520)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0x0000, // 0x1D70 (7536)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1D80 (7552)
-0x0000, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, // 0x1D90 (7568)
-0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, // 0x1DA0 (7584)
-0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1DB0 (7600)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1DC0 (7616)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1DD0 (7632)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1DE0 (7648)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1DF0 (7664)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1E00 (7680)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0x8410, // 0x1E10 (7696)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xC618, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1E20 (7712)
-0x0000, 0x8410, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, // 0x1E30 (7728)
-0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1E40 (7744)
-0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1E50 (7760)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1E60 (7776)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1E70 (7792)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1E80 (7808)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1E90 (7824)
-0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1EA0 (7840)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, 0xFFFF, 0xFFFF, // 0x1EB0 (7856)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1EC0 (7872)
-0x0000, 0x0000, 0xC618, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x8410, // 0x1ED0 (7888)
-0xFFFF, 0xFFFF, 0xC618, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1EE0 (7904)
-0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0x8410, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1EF0 (7920)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1F00 (7936)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1F10 (7952)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1F20 (7968)
-0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x1F30 (7984)
-0x0000, 0x0000, 0x0000, 0x8410, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1F40 (8000)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xFFFF, 0xFFFF, // 0x1F50 (8016)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x1F60 (8032)
-0xC618, 0xC618, 0xC618, 0xFFFF, 0xFFFF, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x1F70 (8048)
-0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x1F80 (8064)
-0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x1F90 (8080)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x1FA0 (8096)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x1FB0 (8112)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x1FC0 (8128)
-0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, 0xC618, // 0x1FD0 (8144)
-0xC618, 0xC618, 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1FE0 (8160)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x1FF0 (8176)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2000 (8192)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2010 (8208)
-0xFFFF, 0xFFFF, 0xF7DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2020 (8224)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2030 (8240)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2040 (8256)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2050 (8272)
-0xFFFF, 0xFFFF, 0xF7DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2060 (8288)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2070 (8304)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2080 (8320)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2090 (8336)
-0xFFFF, 0xFFFF, 0xF7DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x20A0 (8352)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x20B0 (8368)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x20C0 (8384)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x20D0 (8400)
-0xFFFF, 0xFFFF, 0xF7DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x20E0 (8416)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x20F0 (8432)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2100 (8448)
-0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2110 (8464)
-0xFFFF, 0xFFFF, 0xF7DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x2120 (8480)
-};
-#endif
-
// 60x60x1 monochrome, extended to 64x60 for drawBitmap()
const unsigned char elrs_banner_bmp[] PROGMEM ={
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00,
@@ -798,6 +263,462 @@ const unsigned short elrs_rate[0xE10] PROGMEM ={
};
+const unsigned short elrs_switch[0xE10] PROGMEM = {
+ 0x0000, 0x0000, 0x3271, 0x3A93, 0x42F4, 0x3AF5, 0x42F4, 0x42F5, 0x42F4, 0x3AF5, 0x4315, 0x42F5, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5,
+ 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x42F5, 0x42F4, 0x42F5, 0x4314, 0x3AF4, 0x42F4, 0x3AD4, 0x3AB2, 0x3251, 0x0000, 0x0000, 0x0004, 0x4A8D, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x42F4, 0x426E, 0x0003, 0x3272, 0x42F4, 0x4315, 0x42F5, 0x7C56, 0x955B, 0xA59A, 0xADDC,
+ 0xB61B, 0xADDB, 0xADDB, 0xADBB, 0xB5FB, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xB5FC,
+ 0xB61B, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xB5FC,
+ 0xB61B, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xB5FC, 0xB61B, 0xADFB, 0xADDB, 0xADBB, 0xADDB, 0xADBB, 0xADDB, 0xADBB, 0xA59B, 0x7C57,
+ 0x4315, 0x4315, 0x42F4, 0x3273, 0x3AB3, 0x4B14, 0x52F0, 0xEF9F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE79F, 0x52F0, 0x4315, 0x3AB3,
+ 0x42F4, 0x4315, 0x8474, 0xF7FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DF, 0x9D59, 0x7C56, 0x7436, 0x6C16, 0x6C16, 0x6BF6, 0x6C16, 0x63F6,
+ 0x63F5, 0x63F6, 0x63F6, 0x5BD6, 0x63D6, 0x5BB6, 0x63B5, 0x5BB6, 0x63D5, 0x5BD6, 0x63D5, 0x5BB6, 0x63D5, 0x5BB6, 0x63D5, 0x5BB6,
+ 0x63D5, 0x5BD6, 0x63D6, 0x5BB6, 0x63B5, 0x5BB6, 0x63D5, 0x5BD6, 0x63D5, 0x63D6, 0x6BF6, 0x63D6, 0x6BF6, 0x6BF6, 0x6C16, 0x6C16,
+ 0x7416, 0x7437, 0x7C76, 0x953A, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7413, 0x4315, 0x3AD5, 0x42D5, 0x4B15, 0xAD78, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0x9D5B, 0x5311, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x4AF2, 0x9D7B, 0xFFFF, 0xFFFF, 0xFFFF, 0x9CF6, 0x42F5, 0x42F4, 0x42F4, 0x4315, 0xB5B9, 0xFFFF, 0xFFFF, 0xA557, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4316, 0xAD76, 0xFFFF,
+ 0xFFFF, 0x9D17, 0x4315, 0x42F5, 0x42D5, 0x4B15, 0xB5D9, 0xFFFF, 0xEFDF, 0x5310, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x5311, 0xEFDF, 0xFFFF, 0xAD57, 0x42F5, 0x42F4,
+ 0x42F4, 0x4315, 0xB5F9, 0xFFFF, 0x9D7C, 0x4B13, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B12, 0x955C, 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xB5D9, 0xFFFF,
+ 0x7459, 0x4B13, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x4AF3, 0x7C78, 0xFFFF, 0xAD77, 0x42F5, 0x42F4, 0x42F5, 0x4315, 0xB5F9, 0xFFFF, 0x6C38, 0x4314, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B13, 0x6C38,
+ 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xB5D9, 0xFFFF, 0x6C18, 0x4B13, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4B14, 0x7437, 0xFFFF, 0xAD77, 0x42F5, 0x42F4,
+ 0x4315, 0x4315, 0xB5F9, 0xFFFF, 0x6C17, 0x4314, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B13, 0x6C18, 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xB5D9, 0xFFFF,
+ 0x63F7, 0x4B13, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x5BB3, 0x6414, 0x63F4, 0x6C14, 0x6414, 0x6C34, 0x6C54, 0x7453, 0x6C54, 0x7453,
+ 0x6C34, 0x6C33, 0x6414, 0x6C33, 0x6416, 0x5373, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4315, 0x5355, 0x63F4, 0x6414, 0x63F4, 0x6C14,
+ 0x6414, 0x6C34, 0x6C54, 0x7453, 0x6C34, 0x7453, 0x6C54, 0x6C53, 0x6C34, 0x6C33, 0x6414, 0x6C33, 0x5BD7, 0x4B13, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x4AF4, 0x6BF7, 0xFFFF, 0xAD77, 0x42F5, 0x42F4, 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x63F7, 0x4314, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x9E0D, 0x9E2F, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E,
+ 0x9E12, 0x7CEE, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BB7, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E,
+ 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0x95D6, 0x4B6D, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x63F8,
+ 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xB5D9, 0xFFFF, 0x63F7, 0x4B13, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DEE, 0xA62E,
+ 0xC675, 0xFFFB, 0xFFDF, 0xFFDE, 0xFFDF, 0xFFDE, 0xFFDF, 0xFFDE, 0xFFDF, 0xFFDE, 0xFFDF, 0xFFDE, 0xBEF3, 0xA60F, 0x4B36, 0x4B14,
+ 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xA5F1, 0xE7B7, 0xFFDF, 0xFFDE, 0xFFDF, 0xFFDE, 0xFFDF, 0xFFDE, 0xFFDF, 0xFFDE, 0xFFDF, 0xFFDE,
+ 0xFFDF, 0xFFDE, 0xF7FC, 0xC675, 0x95B6, 0x4B6D, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4AF4, 0x6BF7, 0xFFFF, 0xAD78, 0x42F5, 0x42F4,
+ 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x63F7, 0x4314, 0x4315, 0x4315, 0x4315, 0x4315, 0x9E0E, 0x9E2F, 0xAE10, 0xDFB8, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDFB7, 0xA5F1, 0x63F6, 0x4314, 0x4315, 0x4315, 0x4B14, 0x5BB7,
+ 0xAE10, 0xDFB8, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFD, 0xBE76,
+ 0x95D6, 0x4B6D, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x63D8, 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xBDF9, 0xFFFF,
+ 0x63D7, 0x4B14, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCE, 0xB690, 0xA60E, 0xB690, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7FB, 0xB612, 0x8516, 0x4B4F, 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xA5F1, 0xE7D7, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFD, 0xC675, 0x95B6, 0x4B6D, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x4B14, 0x63F7, 0xFFFF, 0xAD98, 0x42F5, 0x42F4, 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x63D7, 0x4314, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x9DEE, 0xB6B2, 0xBED1, 0xA60F, 0xE73A, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFD, 0xC6B7, 0x9DF6, 0x53AD, 0x4315, 0x4315, 0x4B14, 0x5BB7, 0xAE10, 0xDFB8, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xEF9C, 0xF7DE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFD, 0xBE76, 0x9596, 0x434E, 0x4315, 0x4315, 0x4315, 0x42F5, 0x4B14, 0x5BD7,
+ 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xBDF9, 0xFFFF, 0x5BD7, 0x4B14, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCF, 0xB6B1,
+ 0xE7F9, 0xADF1, 0xB613, 0xF7FB, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEF5B, 0x9DF4, 0x6C4E,
+ 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xA5F1, 0xE7D7, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEFFB, 0xBE53, 0xA62E, 0xA64D, 0xDF1A, 0xFFFE,
+ 0xFFFF, 0xFFFF, 0xFFFC, 0xBE54, 0x8D56, 0x4B4E, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4314, 0x63D7, 0xFFFF, 0xAD98, 0x42F5, 0x42F4,
+ 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x63D7, 0x4314, 0x4315, 0x4315, 0x4315, 0x4315, 0x9DEE, 0xB6B2, 0xFFFD, 0xCEB7, 0xA60F, 0xCF35,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xA651, 0x8D8E, 0x4315, 0x4315, 0x4B14, 0x5BB7,
+ 0xAE10, 0xDFB8, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC713, 0xA610, 0xBED1, 0xA630, 0xAE11, 0xEFFA, 0xFFFF, 0xFFFF, 0xFFFC, 0xB614,
+ 0x8536, 0x434F, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BD7, 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xBDF9, 0xFFFF,
+ 0x5BD7, 0x4B14, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCF, 0xB6B1, 0xFFFF, 0xFFDE, 0xA64F, 0xA64E, 0xCEF6, 0xD716, 0xCEF6, 0xD716,
+ 0xCEF6, 0xD716, 0xCEF6, 0xD716, 0xCEF6, 0xD716, 0xAE90, 0xA62E, 0x4B56, 0x4B14, 0x4314, 0x63D6, 0xA5F1, 0xE7D7, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xA64F, 0xAE4E, 0xFFFD, 0xDEF8, 0xA5F0, 0xD775, 0xFFFF, 0xFFFF, 0xFFFC, 0xB612, 0x7CB6, 0x4B50, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xFFFF, 0xAD98, 0x42F5, 0x42F4, 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x63D7, 0x4314, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x9DEE, 0xB6B2, 0xFFFF, 0xFFFF, 0xB6D1, 0x9E2F, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E,
+ 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0x5BD6, 0x4314, 0x4B14, 0x5BB7, 0xAE10, 0xDFB8, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xA62E, 0xAE90,
+ 0xFFFE, 0xDF3B, 0xA610, 0xCF55, 0xFFFF, 0xFFFF, 0xF7FB, 0xA5D2, 0x7497, 0x4332, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BD7,
+ 0xFFFF, 0xAD99, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xBDFA, 0xFFFF, 0x5BB7, 0x4B14, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCF, 0xB6B1,
+ 0xFFFF, 0xFFDE, 0xA64F, 0xA64E, 0xDF39, 0xE779, 0xE75A, 0xE779, 0xE75A, 0xE779, 0xE75A, 0xE779, 0xE75A, 0xE779, 0xBED2, 0xA60F,
+ 0x5376, 0x4B14, 0x4314, 0x63D6, 0xA5F1, 0xE7D7, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xAE6F, 0xA64E, 0xF7FC, 0xD6D7, 0xA5F0, 0xDF96,
+ 0xFFFF, 0xFFFF, 0xEFFA, 0xADF1, 0x6C57, 0x4B32, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xFFFF, 0xAD98, 0x42F5, 0x42F4,
+ 0x4315, 0x4315, 0xBE19, 0xFFFF, 0x5BD7, 0x4314, 0x4315, 0x4315, 0x4315, 0x4315, 0x9DEE, 0xB6B2, 0xFFFD, 0xD6D9, 0xA60F, 0xC734,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBEF2, 0xA60F, 0x4B35, 0x4315, 0x4B14, 0x5BB7,
+ 0xAE10, 0xDFB8, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xCF54, 0xA610, 0xAE6F, 0xA62F, 0xB612, 0xF7FB, 0xFFFF, 0xFFFF, 0xEFF9, 0xA5F2,
+ 0x6C57, 0x4313, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BD7, 0xFFFF, 0xAD99, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xBDFA, 0xFFFF,
+ 0x5BB7, 0x4B14, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCF, 0xB6B1, 0xF7FB, 0xB612, 0xADF2, 0xF7FA, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xAE71, 0x9DEE, 0x4315, 0x4B15, 0x4314, 0x63D6, 0xA5F1, 0xE7D7, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xF7FD, 0xCEB6, 0xA62E, 0xAE6F, 0xEF7C, 0xFFFE, 0xFFFF, 0xFFFF, 0xEFFA, 0xADF1, 0x6C37, 0x4B32, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xFFFF, 0xAD98, 0x42F5, 0x42F4, 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x5BD7, 0x4314, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x9DEE, 0xB6B2, 0xCF55, 0xA610, 0xCED7, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xA612, 0x7D0F, 0x4315, 0x4315, 0x4B14, 0x5BB7, 0xAE10, 0xDFB8, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEFFA, 0xA5F2, 0x6C37, 0x4313, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BD7,
+ 0xFFFF, 0xAD99, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xBDFA, 0xFFFF, 0x5BB7, 0x4B13, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCF, 0xB6B1,
+ 0xA64F, 0xA64E, 0xF7BE, 0xFFFF, 0xFFFF, 0xFFFF, 0xD756, 0xB651, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7BD, 0x9DF4, 0x746E,
+ 0x42F5, 0x4B15, 0x4314, 0x5BB6, 0xA5F0, 0xDF96, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xEFFA, 0xADF1, 0x6C37, 0x4B32, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xFFFF, 0xB598, 0x42F5, 0x42F4,
+ 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x5BD6, 0x4314, 0x4315, 0x4315, 0x4315, 0x4315, 0x9DEE, 0xA670, 0xA62F, 0xC713, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xBEF2, 0xA60F, 0xF79D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xE75C, 0x9DF5, 0x642E, 0x4315, 0x4315, 0x4B33, 0x53B6,
+ 0xA62F, 0xB6D2, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xD718, 0xE7BB, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7FA, 0xA5D2,
+ 0x7477, 0x4332, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BB7, 0xFFFF, 0xAD99, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xBDFA, 0xFFFF,
+ 0x5BB7, 0x4B13, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DEE, 0xA64E, 0xADF2, 0xEFF9, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBED2, 0xA60F,
+ 0xEF9D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xE73A, 0x9DD6, 0x63ED, 0x42F5, 0x4B15, 0x4314, 0x63D6, 0x9E2E, 0xA64E, 0xF79E, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xF7BD, 0xA60F, 0xC6F2, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7FB, 0xADF1, 0x7477, 0x4B31, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x4314, 0x63B6, 0xFFFF, 0xB598, 0x4315, 0x4315, 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x63D7, 0x4314, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x9E0D, 0x9E2F, 0xCEB7, 0xFFFD, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC6F2, 0xA60F, 0xF7BD, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFE, 0xD6F9, 0x9DF6, 0x53CE, 0x4315, 0x4315, 0x4B14, 0x5BB7, 0xA64D, 0x9E2E, 0xCEB7, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xEF9D,
+ 0xA62F, 0xBEF3, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0xAE13, 0x8D57, 0x434F, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BB7,
+ 0xFFFF, 0xAD99, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xBDFA, 0xFFFF, 0x5BB7, 0x4B14, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DEE, 0xA62E,
+ 0xF79D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEFBB, 0xDF18, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xD6D8, 0x9DD6, 0x5BCD,
+ 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xA62E, 0xAE6E, 0xADF2, 0xF7FA, 0xFFFF, 0xFFFF, 0xFFFF, 0xF79D, 0xA60F, 0xC6F2, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFD, 0xBE54, 0x9DD6, 0x4B6C, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4314, 0x63B6, 0xFFFF, 0xB598, 0x42F5, 0x42F4,
+ 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x63D7, 0x4314, 0x4315, 0x4315, 0x4315, 0x4315, 0x9DEE, 0xA650, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFD, 0xCEB8, 0x9DF6, 0x53AD, 0x4315, 0x4315, 0x4B14, 0x5BB7,
+ 0xA62F, 0xBF13, 0xA62F, 0xC714, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, 0xB652, 0xD757, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFD, 0xC697,
+ 0x9DF6, 0x4B8D, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BB7, 0xFFFF, 0xAD99, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xBDFA, 0xFFFF,
+ 0x5BB7, 0x4B13, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCF, 0xB6B1, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFD, 0xD6B7, 0x9DD6, 0x5BAD, 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xA5F1, 0xDFB7, 0xA64F, 0xA64E,
+ 0xF7BE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xD6D8, 0x9DD6, 0x5BCD, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xFFFF, 0xAD98, 0x42F5, 0x42F4, 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x5BD7, 0x4314, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x9DEE, 0xB6B2, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xE77C, 0xAE6F, 0xA62F, 0xCEB6, 0xF7FD, 0xFFFF, 0xFFFF,
+ 0xFFFD, 0xCEB8, 0x9DF6, 0x53AD, 0x4315, 0x4315, 0x4B14, 0x5BB7, 0xAE10, 0xDFB8, 0xCF54, 0xA610, 0xD6D8, 0xFFFE, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xDF3B, 0x9DF5, 0x5C0E, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BB7,
+ 0xFFFF, 0xAD99, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xBDF9, 0xFFFF, 0x5BB7, 0x4B14, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCF, 0xB6B1,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7FB, 0xB612, 0xA62E, 0xAE6F, 0xA610, 0xCF54, 0xFFFF, 0xFFFF, 0xFFFD, 0xCEB7, 0x9DD6, 0x5BAD,
+ 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xA5F1, 0xE7D7, 0xF7FB, 0xB612, 0xADF2, 0xF7FA, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xF79D, 0x9DF4, 0x6C6E, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xFFFF, 0xAD98, 0x42F5, 0x42F4,
+ 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x63D7, 0x4314, 0x4315, 0x4315, 0x4315, 0x4315, 0x9DEE, 0xB6B2, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xD796, 0xA5F1, 0xD6F7, 0xF7FD, 0xA64E, 0xA670, 0xFFFF, 0xFFFF, 0xFFFD, 0xCED8, 0x9DF6, 0x53AE, 0x4315, 0x4315, 0x4B14, 0x5BB7,
+ 0xAE10, 0xDFB8, 0xFFFD, 0xCED8, 0xA62F, 0xBEF3, 0xE77A, 0xDF7A, 0xE77A, 0xDF7A, 0xE77A, 0xDF7A, 0xE77A, 0xDF7A, 0xE779, 0xDF5A,
+ 0xA632, 0x7D0E, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BD7, 0xFFFF, 0xAD99, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xB5D9, 0xFFFF,
+ 0x63D7, 0x4B14, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCF, 0xB6B1, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xCF35, 0xAE0F, 0xE73B, 0xFFFE,
+ 0xAE90, 0xA62E, 0xFFFF, 0xFFFF, 0xFFFE, 0xDEF8, 0x9DD6, 0x5BCD, 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xA5F1, 0xE7D7, 0xFFFF, 0xF7BD,
+ 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9E10, 0x958D, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xFFFF, 0xAD98, 0x42F5, 0x42F4, 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x63D7, 0x4314, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x9DEE, 0xB6B2, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xD796, 0xA610, 0xD719, 0xF7FE, 0xA64F, 0xA64F, 0xFFFF, 0xFFFF,
+ 0xFFFE, 0xDF1A, 0x9DF6, 0x53CE, 0x4315, 0x4315, 0x4B14, 0x5BB7, 0xAE10, 0xDFB8, 0xFFFD, 0xCEB8, 0xA62E, 0xAEB1, 0xCF16, 0xCF16,
+ 0xCF16, 0xCF16, 0xCF16, 0xCF16, 0xCF16, 0xCF16, 0xCF36, 0xC6F5, 0x9E13, 0x6CAE, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BD7,
+ 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xB5D9, 0xFFFF, 0x63D7, 0x4B14, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCF, 0xB6B1,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEFFA, 0xADF1, 0xA630, 0xBEB1, 0xA60F, 0xC713, 0xFFFF, 0xFFFF, 0xFFFE, 0xE73A, 0x9DD6, 0x5BED,
+ 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xA5F1, 0xE7D7, 0xEFFA, 0xAE11, 0xAE13, 0xF7FA, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFE, 0xDEF8, 0x9DD6, 0x63ED, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xFFFF, 0xAD98, 0x42F5, 0x42F4,
+ 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x63F7, 0x4314, 0x4315, 0x4315, 0x4315, 0x4315, 0x9DEE, 0xB6B2, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFE, 0xDF3B, 0xA64E, 0x9E2E, 0xBE54, 0xEFFB, 0xFFFF, 0xFFFF, 0xFFFE, 0xE75C, 0x9DF5, 0x5BEE, 0x4315, 0x4315, 0x4B14, 0x5BB7,
+ 0xAE10, 0xDFB8, 0xBEF2, 0xA62F, 0xDF3A, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0xB634,
+ 0x9DD6, 0x4B6D, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x5BD7, 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xB5D9, 0xFFFF,
+ 0x63D7, 0x4B13, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCF, 0xB6B1, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DE, 0xF79C,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEF9C, 0x9DF5, 0x640D, 0x42F5, 0x4B15, 0x4314, 0x63D6, 0xA5F0, 0xD775, 0xA60E, 0xAE8F,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEFF9, 0xADF1, 0x7477, 0x4B31, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x4B14, 0x63F7, 0xFFFF, 0xAD98, 0x42F5, 0x42F4, 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0x63F7, 0x4314, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x9DEE, 0xB6B2, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xEF9D, 0x9DF5, 0x640E, 0x4315, 0x4315, 0x4B14, 0x5BB7, 0xA62E, 0xAE70, 0xAE10, 0xDFB8, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xCF55, 0xA610, 0x5396, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x63D7,
+ 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xB5D9, 0xFFFF, 0x63F7, 0x4B13, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DCF, 0xB6B1,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF79D, 0x9DF5, 0x6C4D,
+ 0x42F5, 0x4B15, 0x4314, 0x63D6, 0x9E2E, 0xA62D, 0xBE55, 0xFFFC, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xAE71, 0x9DEE, 0x4315, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4AF4, 0x63F7, 0xFFFF, 0xAD78, 0x42F5, 0x42F4,
+ 0x4315, 0x4315, 0xB5F9, 0xFFFF, 0x6C17, 0x4314, 0x4315, 0x4315, 0x4315, 0x4315, 0x9DEE, 0xAE92, 0xFFFE, 0xFFDF, 0xFFFE, 0xFFDF,
+ 0xFFFE, 0xFFDF, 0xFFFE, 0xFFDF, 0xFFFE, 0xFFDF, 0xFFFE, 0xFFDF, 0xFFFD, 0xEF7C, 0x9DF4, 0x644E, 0x4315, 0x4315, 0x4B14, 0x5BB7,
+ 0xA64E, 0x9E2E, 0xEF9C, 0xF7FE, 0xFFFE, 0xFFDF, 0xFFFE, 0xFFDF, 0xFFFE, 0xFFDF, 0xFFFE, 0xFFDF, 0xFFFE, 0xEF9D, 0x9E14, 0x6C6E,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x63F8, 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xB5D9, 0xFFFF,
+ 0x6BF8, 0x4B13, 0x42F5, 0x4B15, 0x4315, 0x4B15, 0x9DEE, 0xA62E, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D,
+ 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9DF5, 0x6C4E, 0x42F5, 0x4B15, 0x4314, 0x5BB6, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D,
+ 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0x9DD6, 0x5BAD, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x4AF4, 0x6BF7, 0xFFFF, 0xAD77, 0x42F5, 0x42F4, 0x4315, 0x4315, 0xB5D9, 0xFFFF, 0x6C17, 0x4314, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x5BF4, 0x6415, 0x6414, 0x6414, 0x6414, 0x6414, 0x6414, 0x6414, 0x6C34, 0x6414, 0x6C34, 0x6414, 0x6C34, 0x6414,
+ 0x6C34, 0x6414, 0x6416, 0x4B74, 0x4315, 0x4315, 0x4315, 0x4B36, 0x63F3, 0x6414, 0x6414, 0x6414, 0x6414, 0x6414, 0x6434, 0x6414,
+ 0x6414, 0x6414, 0x6414, 0x6414, 0x6414, 0x63F4, 0x5BF6, 0x4314, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B14, 0x63F8,
+ 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xB5D9, 0xFFFF, 0x6C18, 0x4B13, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4B14, 0x6C17, 0xFFFF, 0xAD77, 0x42F5, 0x42F4,
+ 0x42F5, 0x4315, 0xB5D9, 0xFFFF, 0x6C38, 0x4314, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B13, 0x6C18, 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xB5B9, 0xFFFF,
+ 0x7438, 0x4B13, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x4AF3, 0x7438, 0xFFFF, 0xAD77, 0x42F5, 0x42F4, 0x42F5, 0x4315, 0xB5D9, 0xFFFF, 0x7C99, 0x4B13, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4B13, 0x7479,
+ 0xFFFF, 0xA578, 0x4315, 0x42F5, 0x42D5, 0x4B15, 0xB5B9, 0xFFFF, 0x9D7C, 0x5312, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x4B13, 0x9D7C, 0xFFFF, 0xAD77, 0x42F5, 0x42F4,
+ 0x42F4, 0x4315, 0xB5B9, 0xFFFF, 0xF7FF, 0x5311, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x5310, 0xEFDF, 0xFFFF, 0xA558, 0x4315, 0x42F5, 0x42F5, 0x4B15, 0xAD78, 0xFFFF,
+ 0xFFFF, 0xB597, 0x4315, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B15, 0xAD77, 0xFFFF, 0xFFFF, 0xA516, 0x42F5, 0x42F4, 0x42F4, 0x4315, 0xA557, 0xF7FF, 0xFFFF, 0xFFFF, 0x9D9B, 0x4B12,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315,
+ 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x4315, 0x5331, 0xA5BC, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0x94D6, 0x4315, 0x3AF5, 0x3AD4, 0x4B14, 0x7C34, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xB5FC, 0x8C96, 0x7C57, 0x7C56,
+ 0x7437, 0x7436, 0x6C16, 0x7436, 0x6C16, 0x7416, 0x6BF6, 0x7416, 0x6C16, 0x6BF5, 0x63F6, 0x6BF5, 0x63D6, 0x6BF6, 0x63D6, 0x6BF5,
+ 0x6BF6, 0x6C15, 0x6BF6, 0x6C15, 0x63D6, 0x63D5, 0x63D6, 0x6BD5, 0x63D6, 0x6BD5, 0x63D6, 0x6BF6, 0x63D6, 0x6BF5, 0x63D6, 0x63D5,
+ 0x63D6, 0x6BF6, 0x6BF6, 0x6C16, 0x6C16, 0x7C56, 0x7C76, 0x9D59, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7FF, 0x7C13, 0x4315, 0x42F4,
+ 0x3AB3, 0x4315, 0x5310, 0xDF7F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE79F, 0x4AF1, 0x4B14, 0x3AB4, 0x3A72, 0x4AF3, 0x4315, 0x4314,
+ 0x7416, 0x9519, 0x9D3A, 0xA59A, 0xA57B, 0xA59A, 0xA57B, 0xA59A, 0xA59A, 0xADDB, 0xADBB, 0xB5DB, 0xADBB, 0xB5FB, 0xB5FC, 0xBE1B,
+ 0xB5FC, 0xB5DA, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB,
+ 0xADBB, 0xB5DB, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB, 0xADBB, 0xB5DB,
+ 0xADBB, 0xAD9A, 0x953A, 0x7C36, 0x42F5, 0x4B14, 0x42F4, 0x3AB2, 0x0003, 0x4AAE, 0x42F4, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335,
+ 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335,
+ 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335,
+ 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335, 0x4B15, 0x4335,
+ 0x4B15, 0x4315, 0x4A6D, 0x0004, 0x0000, 0x0000, 0x3292, 0x42B2, 0x3AF5, 0x42F4, 0x3B15, 0x42F4, 0x3B15, 0x42F5, 0x3B15, 0x42F4,
+ 0x3B15, 0x42F5, 0x3B15, 0x42F5, 0x3B15, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5,
+ 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5, 0x4315, 0x42F5,
+ 0x4315, 0x42F5, 0x3B15, 0x42F4, 0x3AF5, 0x42D4, 0x3AF5, 0x42D4, 0x3AF5, 0x42D4, 0x3AF5, 0x42D4, 0x3AB2, 0x3A70, 0x0000, 0x0000,
+};
+
+const unsigned short elrs_antenna[0xE10] PROGMEM = {
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFBD, 0xEF7C, 0xFFFE, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7BD, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDF96, 0xA611, 0xA62E, 0xAE91, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC712, 0xA60F, 0xAE10, 0xD777, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xEF7B, 0xA62E, 0xA62D, 0x9E2E, 0xA62D, 0xE75B, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7BD,
+ 0x9E2E, 0xA62D, 0xA62E, 0xA64D, 0xE75B, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEFFA, 0xAE13, 0xA64D, 0x9E2E,
+ 0xA64E, 0x9E2E, 0xF7BD, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7DF, 0xA64E, 0x9E2E, 0xA64D, 0x9E2E,
+ 0xAE32, 0xEFFA, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xB6B1, 0xA62E, 0x9E2E, 0xA62D, 0xA610, 0xCF33, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xCF55, 0xAE0F, 0x9E2E, 0xA62D, 0xA60F, 0xB6B0, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFE, 0xE75B, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xC675, 0xF7FD, 0xFFFF, 0xFFFF, 0xCE9D, 0xBE3C, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xC65C, 0xBE3C, 0xFFFF, 0xFFFF, 0xFFFD, 0xC6B7, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xDF3A, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7FC, 0xBE54,
+ 0x9E2E, 0xA62D, 0xA62E, 0xAE6F, 0xFFDF, 0xFFFF, 0xF7FF, 0x94D6, 0x42F5, 0x4B15, 0x7C34, 0xF7FF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xD69A, 0xA513, 0x8431, 0x8C30, 0x9CD3, 0xD679, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7FF, 0x94D5, 0x42F5, 0x4B15, 0x7C35, 0xF7FF,
+ 0xFFFF, 0xFFFF, 0xAE90, 0xA62E, 0x9E2E, 0xA62D, 0xB613, 0xFFFB, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE7D7, 0xA5F1, 0xA64D, 0x9E2E, 0xA610, 0xD777,
+ 0xFFFF, 0xFFFF, 0xA5BD, 0x4B13, 0x4315, 0x4315, 0x5311, 0xDF7F, 0xFFFF, 0xFFFF, 0xD69A, 0x5AEC, 0x31A6, 0x31A7, 0x31A6, 0x31A7,
+ 0x31A6, 0x31A7, 0x528A, 0xC618, 0xFFFF, 0xFFFF, 0xEFDF, 0x5311, 0x4315, 0x4315, 0x4B13, 0x84FB, 0xFFFF, 0xFFFF, 0xE7D7, 0xA5F1,
+ 0xA64D, 0x9E2E, 0xA610, 0xD797, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBEF3, 0xA60F, 0x9E2E, 0xA62D, 0xB634, 0xFFFC, 0xFFFF, 0xEF3D, 0x4315, 0x4B15,
+ 0x42F5, 0x4B15, 0x6392, 0xF7FF, 0xFFFF, 0xB575, 0x39A7, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0x9492, 0xFFFF, 0xF7FF, 0x73F3, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0xD69B, 0xFFFF, 0xFFFD, 0xC675, 0x9E2E, 0xA62D, 0xA60F, 0xBED1,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xA66E, 0x9E2E, 0xA64D, 0x9E2E, 0xDF19, 0xFFFE, 0xFFFF, 0x8CB6, 0x4315, 0x4315, 0x4315, 0x4316, 0xDEDC, 0xFFFF,
+ 0xC617, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x9CF4, 0xFFFF, 0xDF1D,
+ 0x4B56, 0x4315, 0x4315, 0x4315, 0x7413, 0xF7FF, 0xFFFE, 0xE75B, 0xA64D, 0x9E2E, 0xA64E, 0x9E2E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF79C, 0x9E2E, 0xA62D,
+ 0x9E2E, 0xA62D, 0xF79E, 0xFFFF, 0xDF7F, 0x5311, 0x42F5, 0x4B15, 0x4B13, 0xA5BD, 0xFFFF, 0xF79E, 0x4208, 0x39A6, 0x3186, 0x39A6,
+ 0x3186, 0x528A, 0x8C31, 0x8C50, 0x5ACB, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39C6, 0xDEFC, 0xFFFF, 0xAE1E, 0x5312, 0x42F5, 0x4B15,
+ 0x4B12, 0xCEDF, 0xFFFF, 0xFFFF, 0xA64E, 0xA62D, 0x9E2E, 0xA62D, 0xE75B, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xDF1A, 0xA64D, 0x9E2E, 0xA62E, 0xAE90, 0xFFFF, 0xFFFF,
+ 0xAE1E, 0x4B13, 0x4315, 0x4315, 0x6392, 0xEFDF, 0xFFFF, 0x9CF4, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x94B2, 0xFFDF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xAD76, 0x39C7, 0x31A7, 0x31A6, 0x31A7, 0x8410, 0xFFFF, 0xF7FF, 0x6BD3, 0x4315, 0x4315, 0x4B13, 0x955C, 0xFFFF, 0xFFFF,
+ 0xBED1, 0xA60F, 0xA64D, 0x9E2E, 0xD6F8, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xD6D8, 0x9E2E, 0xA62D, 0xA60F, 0xBED1, 0xFFFF, 0xFFFF, 0x84DB, 0x4B13, 0x42F5, 0x4B15,
+ 0x94F6, 0xFFFF, 0xFFFF, 0x5ACB, 0x3186, 0x39A6, 0x3186, 0x738E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x9492, 0x39A6,
+ 0x3186, 0x39A6, 0x41E8, 0xFFFF, 0xFFFF, 0xAD78, 0x42F5, 0x4B15, 0x4314, 0x6C18, 0xFFFF, 0xFFFF, 0xC714, 0xA60F, 0x9E2E, 0xA62D,
+ 0xC697, 0xFFFD, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFD, 0xCEB8, 0xA64D, 0x9E2E, 0xA62F, 0xC714, 0xFFFF, 0xFFFF, 0x7459, 0x4314, 0x4315, 0x4315, 0xBE19, 0xFFFF, 0xFFFF, 0x31C7,
+ 0x31A6, 0x31A7, 0x31A6, 0xD69A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEF5D, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0xE71C,
+ 0xFFFF, 0xCE9C, 0x4315, 0x4315, 0x4B14, 0x5397, 0xFFFF, 0xFFFF, 0xCF55, 0xA610, 0xA64D, 0x9E2E, 0xC675, 0xFFFD, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFD, 0xCEB6, 0x9E2E, 0xA62D,
+ 0xA610, 0xCF34, 0xFFFF, 0xFFFF, 0x63F8, 0x4B14, 0x42F5, 0x4B15, 0xC63A, 0xFFFF, 0xF79E, 0x39A6, 0x3186, 0x39A6, 0x3186, 0xEF5D,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x39C7, 0x3186, 0x39A6, 0x3186, 0xDEBA, 0xFFFF, 0xDEFC, 0x42F5, 0x4B15,
+ 0x42F5, 0x4B35, 0xFFFF, 0xFFFF, 0xD776, 0xAE10, 0x9E2E, 0xA62D, 0xBE55, 0xFFFC, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFD, 0xCEB8, 0xA64D, 0x9E2E, 0xA62F, 0xC714, 0xFFFF, 0xFFFF,
+ 0x7459, 0x4314, 0x4315, 0x4315, 0xBDF9, 0xFFFF, 0xFFFF, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0xD69A, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xEF7D, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0xE71C, 0xFFFF, 0xCE9C, 0x4315, 0x4315, 0x4B15, 0x5397, 0xFFFF, 0xFFFF,
+ 0xD775, 0xA610, 0xA64D, 0x9E2E, 0xC675, 0xFFFD, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xD6D8, 0x9E2E, 0xA62D, 0xA60F, 0xBED1, 0xFFFF, 0xFFFF, 0x84DB, 0x4B13, 0x42F5, 0x4B15,
+ 0x94D6, 0xFFFF, 0xFFFF, 0x5ACA, 0x3186, 0x39A6, 0x3186, 0x840F, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x9CB3, 0x39A6,
+ 0x3186, 0x39A6, 0x41E8, 0xFFFF, 0xFFFF, 0xB598, 0x42F5, 0x4B15, 0x4314, 0x6C18, 0xFFFF, 0xFFFF, 0xC714, 0xA60F, 0x9E2E, 0xA62D,
+ 0xC696, 0xFFFD, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFE, 0xDF3B, 0xA64D, 0x9E2E, 0xA62E, 0xAE90, 0xFFFF, 0xFFFF, 0xAE1E, 0x4B13, 0x4315, 0x4315, 0x6372, 0xEFDF, 0xFFFF, 0x9CD3,
+ 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0xA514, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xB597, 0x39C7, 0x31A7, 0x31A6, 0x31A7, 0x840F, 0xFFFF,
+ 0xF7FF, 0x73F4, 0x4315, 0x4315, 0x4B13, 0x8D3C, 0xFFFF, 0xFFFF, 0xBED2, 0xA60F, 0xA64D, 0x9E2E, 0xD6F8, 0xFFFE, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF79D, 0x9E2E, 0xA62D,
+ 0x9E2E, 0xA62D, 0xF79E, 0xFFFF, 0xDF7F, 0x5311, 0x42F5, 0x4B15, 0x4B13, 0xA59C, 0xFFFF, 0xF79D, 0x41E8, 0x39A6, 0x3186, 0x39A6,
+ 0x3186, 0x5AAA, 0x8C51, 0x9491, 0x5AEC, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39C6, 0xDEFC, 0xFFFF, 0xBE5F, 0x5312, 0x42F5, 0x4B15,
+ 0x4AF2, 0xC6BF, 0xFFFF, 0xFFFF, 0xA64E, 0xA62D, 0x9E2E, 0xA62D, 0xE73B, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xA66E, 0x9E2E, 0xA64D, 0x9E2E, 0xDF19, 0xFFFE,
+ 0xFFFF, 0x8496, 0x4315, 0x4315, 0x4315, 0x4316, 0xD6DC, 0xFFFF, 0xBDD7, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7,
+ 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x9CF4, 0xFFFF, 0xE75E, 0x5376, 0x4315, 0x4315, 0x4315, 0x6BD3, 0xEFFF, 0xFFFE, 0xE75C,
+ 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xFFDE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC6F3, 0xA60F, 0x9E2E, 0xA62D, 0xB634, 0xFFFC, 0xFFFF, 0xE73D, 0x4315, 0x4B15,
+ 0x42F5, 0x4B15, 0x6392, 0xF7FF, 0xFFFF, 0xAD34, 0x39A7, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0x8C51, 0xFFFF, 0xF7FF, 0x7C34, 0x42F5, 0x4B15, 0x42F5, 0x4B15, 0xC65B, 0xFFFF, 0xFFFD, 0xC696, 0x9E2E, 0xA62D, 0xA60F, 0xB6B0,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xE7D8, 0xA5F1, 0xA64D, 0x9E2E, 0xA610, 0xD777, 0xFFFF, 0xFFFF, 0x9D9D, 0x4B13, 0x4315, 0x4315, 0x5311, 0xDF5F,
+ 0xFFFF, 0xFFFF, 0xCE79, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0xB597, 0xFFFF, 0xFFFF, 0xF7FF, 0x5311,
+ 0x4315, 0x4315, 0x4B13, 0x7C9A, 0xFFFF, 0xFFFF, 0xE7D8, 0xA5F1, 0xA64D, 0x9E2E, 0xA610, 0xCF76, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFC, 0xBE54,
+ 0x9E2E, 0xA62D, 0xA62E, 0xAE6F, 0xFFDF, 0xFFFF, 0xF7FF, 0x94B5, 0x42F5, 0x4B15, 0x7C14, 0xF7FF, 0xFFFF, 0xFFFF, 0xD67A, 0x39A6,
+ 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0xB596, 0xFFFF, 0xFFFF, 0xF7FF, 0x8C95, 0x42F5, 0x4B15, 0x73D3, 0xF7DF,
+ 0xFFFF, 0xFFFF, 0xB6B1, 0xA62E, 0x9E2E, 0xA62D, 0xAE13, 0xF7FB, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xE75C, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E,
+ 0xC695, 0xF7FD, 0xFFFF, 0xFFFF, 0xC65C, 0xB61C, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8430, 0x31A7, 0x31A6, 0x31A7, 0x5ACA, 0x738E,
+ 0x31A6, 0x31A7, 0x31A6, 0x632D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, 0xB5FB, 0xADDB, 0xFFDF, 0xFFFF, 0xFFFD, 0xCED8, 0xA64D, 0x9E2E,
+ 0xA64D, 0x9E2E, 0xD719, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBED2, 0xA60E, 0x9E2E, 0xA62D, 0xA610, 0xCF33, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFBE, 0x41E8, 0x39A6, 0x3186, 0x39A6, 0xA514, 0xC617, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0xE71C, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xD776, 0xAE10, 0x9E2E, 0xA62D, 0xA60E, 0xB68F, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7FA, 0xAE13, 0xA64D, 0x9E2E, 0xA64D, 0x9E2E, 0xF7BD, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xB597, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0xEF7D, 0xFFDF, 0x4A28, 0x31A7, 0x31A6, 0x31A7, 0x9CD3, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, 0xA66E, 0x9E2E, 0xA64D, 0x9E2E, 0xAE11, 0xE7D9, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xEF7C, 0xA64E, 0xA62D, 0x9E2E, 0xA62D, 0xE75C, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x6B2C, 0x3186, 0x39A6,
+ 0x3186, 0x738E, 0xFFFF, 0xFFFF, 0x8C51, 0x39A6, 0x3186, 0x39A6, 0x4A49, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF7BD,
+ 0x9E2E, 0xA62D, 0x9E2E, 0xA62D, 0xDF1A, 0xFFFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDFB7, 0xA611,
+ 0xA62E, 0xAE91, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEF3C, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0xBDF8, 0xFFFF, 0xFFFF,
+ 0xDEFB, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0xCE59, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBED1, 0xA60F, 0xA60F, 0xC734,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDE, 0xEF7C, 0xFFFE, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x9CB3, 0x39A6, 0x3186, 0x39A6, 0x4208, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF, 0x62EB, 0x3186, 0x39A6,
+ 0x3186, 0x83EF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFE, 0xEF7B, 0xEF9D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0x5289, 0x31A7, 0x31A6, 0x31A7, 0x9471, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xA555, 0x31A6, 0x31A7, 0x31A6, 0x39C7, 0xF7BE, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xCE59, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0xC618, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE6FB, 0x3186, 0x39A6, 0x3186, 0x39A6, 0xAD76, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7BF0, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x4A49, 0xE73D, 0xFFFF, 0xFFFF,
+ 0xF7BE, 0x5ACC, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x630C, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xF79E, 0x41E7, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x5AAA, 0xF79E, 0xFFFF, 0x6B4D, 0x39A6, 0x3186, 0x39A6,
+ 0x3186, 0x39A6, 0x3186, 0xE6FB, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xB595, 0x31A7,
+ 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x6B2C, 0x7BCF, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x9492,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x630C, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x4A48, 0xFFDF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE71C, 0x39A6, 0x31A7, 0x31A6, 0x31A7, 0xB596, 0x4A6A, 0x31A6, 0x31A7, 0x31A6, 0x31A7,
+ 0x31A6, 0x31A7, 0x4208, 0xBDD7, 0x39A6, 0x31A7, 0x31A6, 0x31A7, 0xC638, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0x9CB2, 0x3186, 0x39A6, 0x3186, 0x4A49, 0xFFFF, 0xE6FB, 0x4208, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39C6, 0xCE59, 0xFFFF,
+ 0x630C, 0x39A6, 0x3186, 0x39A6, 0x7BAF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x4A49, 0x31A6, 0x31A7,
+ 0x31A6, 0x94B3, 0xFFFF, 0xCE59, 0x39E7, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0xB596, 0xFFFF, 0xB595, 0x31A7, 0x31A6, 0x31A7,
+ 0x39C7, 0xEF7E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC618, 0x39A6, 0x3186, 0x39A6, 0x3186, 0xE71B, 0xD6BB, 0x4207,
+ 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x39A7, 0xC617, 0xF79E, 0x41E7, 0x3186, 0x39A6, 0x3186, 0xAD55, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7BCF, 0x31A7, 0x31A6, 0x31A7, 0x630C, 0xE73D, 0x4A49, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7,
+ 0x31A6, 0x31A7, 0x31A6, 0x39E8, 0xD6BA, 0x7BD0, 0x31A6, 0x31A7, 0x31A6, 0x5AEC, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF79D,
+ 0x39C7, 0x39A6, 0x3186, 0x39A6, 0x9CD3, 0x528A, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x7BCF, 0x9CB2, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0x4229, 0xB575, 0x3186, 0x39A6, 0x3186, 0x39A6, 0xDEDB, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xA555, 0x31A6, 0x31A7, 0x31A6, 0x31A7,
+ 0x528A, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x6B4E, 0xFFFF, 0xFFFF, 0x8410, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x4A6A, 0x39E7, 0x31A7,
+ 0x31A6, 0x31A7, 0x9471, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x62EB, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0x5ACB, 0xFFBE, 0xFFFF, 0xFFFF, 0xFFFF, 0x7BAE, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x4208, 0xFFDF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xDEFB, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x4A6A, 0xEF7D, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xF7DF, 0x632C, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0xBDF8, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8C51, 0x39A6,
+ 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x4208, 0xE71C, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xF77E, 0x5AAA,
+ 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x738E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, 0x4A28, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7,
+ 0x31A6, 0x39E8, 0xD6BA, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xE73D, 0x4A49, 0x31A7, 0x31A6, 0x31A7,
+ 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0xEF7D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xC5F7, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x39A7, 0xCE38, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDEDB, 0x4208, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0xA514, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0x738E, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0xB5B6, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xCE5A, 0x39E7, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x5ACA, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xEF7E, 0x39C6, 0x3186, 0x39A6,
+ 0x3186, 0x39A6, 0x3186, 0xA513, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xBDD7, 0x39C6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0xDEBA, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xA534, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x4208,
+ 0x4228, 0x4208, 0x4228, 0x4208, 0x4228, 0x4208, 0x4228, 0x4208, 0x4228, 0x4208, 0x4228, 0x4208, 0x4228, 0x4208, 0x4228, 0x31A7,
+ 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x8451, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x52AB, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0x3186, 0x4208, 0xFFBF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xD6BB,
+ 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7,
+ 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0x31A6, 0x31A7, 0xBDD7, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8C50, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6,
+ 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x3186, 0x39A6, 0x6B6E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, 0x4208, 0x31A6, 0x31A7, 0x31A6, 0x8451, 0xCE79, 0xCE59, 0xCE79, 0xCE59,
+ 0xCE79, 0xCE59, 0xCE79, 0xCE59, 0xCE79, 0xCE59, 0xCE79, 0xCE59, 0xCE79, 0xCE59, 0xCE79, 0xCE59, 0xCE79, 0xCE59, 0xCE79, 0xCE59,
+ 0xCE79, 0xCE59, 0x9CF3, 0x31A7, 0x31A6, 0x31A7, 0x39C7, 0xE75D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xBDB7, 0x39A6, 0x3186, 0x39A6, 0x39A7, 0xEF5C, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFDF, 0x4208,
+ 0x3186, 0x39A6, 0x3186, 0xA4F3, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x736D, 0x31A7,
+ 0x31A6, 0x31A7, 0x6B6D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8431, 0x31A6, 0x31A7, 0x31A6, 0x528A,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x4A49, 0x39A6, 0x3186, 0x39A6, 0xBDB7, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xDEBA, 0x3186, 0x39A6, 0x3186, 0x39A6, 0xF7BF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x8C10, 0x31A6, 0x39A6, 0x528A, 0xFFDE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x6B2C, 0x31A6, 0x39A6, 0x632C, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xB596, 0x9D14, 0xF77D, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+ 0xF79E, 0xA514, 0xA535, 0xF79E, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
+};
+
const unsigned short elrs_power[0xE10] PROGMEM ={
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16)
0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0020 (32)
diff --git a/src/lib/SCREEN/TFT/tftdisplay.cpp b/src/lib/SCREEN/TFT/tftdisplay.cpp
index 4d7c72d7a4..db2cf08bcf 100644
--- a/src/lib/SCREEN/TFT/tftdisplay.cpp
+++ b/src/lib/SCREEN/TFT/tftdisplay.cpp
@@ -17,6 +17,8 @@ extern WiFiMode_t wifiMode;
const uint16_t *main_menu_icons[] = {
elrs_rate,
+ elrs_switch,
+ elrs_antenna,
elrs_power,
elrs_ratio,
elrs_motion,
@@ -127,11 +129,10 @@ void TFTDisplay::init()
pinMode(GPIO_PIN_TFT_BL, OUTPUT);
}
bus = new Arduino_ESP32SPI(GPIO_PIN_TFT_DC, GPIO_PIN_TFT_CS, GPIO_PIN_TFT_SCLK, GPIO_PIN_TFT_MOSI, GFX_NOT_DEFINED, HSPI);
- gfx = new Arduino_ST7735(bus, GPIO_PIN_TFT_RST, 1 /* rotation */, true , 80, 160, 26, 1, 26, 1);
+ gfx = new Arduino_ST7735(bus, GPIO_PIN_TFT_RST, OPT_OLED_REVERSED ? 3 : 1 /* rotation */, true , 80, 160, 26, 1, 26, 1);
gfx->begin();
doScreenBackLight(SCREEN_BACKLIGHT_ON);
- displaySplashScreen();
}
void TFTDisplay::doScreenBackLight(screen_backlight_t state)
@@ -169,7 +170,12 @@ void TFTDisplay::displaySplashScreen()
{
gfx->fillScreen(WHITE);
- gfx->draw16bitRGBBitmap(0, 0, vendor_logo, INIT_PAGE_LOGO_X, INIT_PAGE_LOGO_Y);
+ size_t sz = INIT_PAGE_LOGO_X * INIT_PAGE_LOGO_Y;
+ uint16_t image[sz];
+ if (spi_flash_read(logo_image, image, sz * 2) == ESP_OK)
+ {
+ gfx->draw16bitRGBBitmap(0, 0, image, INIT_PAGE_LOGO_X, INIT_PAGE_LOGO_Y);
+ }
gfx->fillRect(SCREEN_FONT_GAP, INIT_PAGE_FONT_START_Y - INIT_PAGE_FONT_PADDING,
SCREEN_X - SCREEN_FONT_GAP*2, SCREEN_NORMAL_FONT_SIZE + INIT_PAGE_FONT_PADDING*2, BLACK);
diff --git a/src/lib/SCREEN/devScreen.cpp b/src/lib/SCREEN/devScreen.cpp
index e3fcac84d9..abd2f937b7 100644
--- a/src/lib/SCREEN/devScreen.cpp
+++ b/src/lib/SCREEN/devScreen.cpp
@@ -8,6 +8,7 @@
#include "TFT/tftdisplay.h"
#include "devButton.h"
+#include "handset.h"
FiniteStateMachine state_machine(entry_fsm);
@@ -70,7 +71,7 @@ static int handle(void)
#endif
#ifdef HAS_FIVE_WAY_BUTTON
- if (!CRSF::IsArmed())
+ if (!handset->IsArmed())
{
int key;
bool isLongPressed;
diff --git a/src/lib/SCREEN/devScreen.h b/src/lib/SCREEN/devScreen.h
index 2877a7036f..2e94c3e6a3 100644
--- a/src/lib/SCREEN/devScreen.h
+++ b/src/lib/SCREEN/devScreen.h
@@ -1,6 +1,5 @@
#pragma once
#include "device.h"
-#include "CRSF.h"
extern device_t Screen_device;
diff --git a/src/lib/SCREEN/display.cpp b/src/lib/SCREEN/display.cpp
index 62971eb9b0..b284dbd8bc 100644
--- a/src/lib/SCREEN/display.cpp
+++ b/src/lib/SCREEN/display.cpp
@@ -1,6 +1,8 @@
#include "display.h"
#include "helpers.h"
+extern bool OtaIsFullRes;
+
const char *Display::message_string[] = {
"ExpressLRS",
"[ Connected ]",
@@ -10,6 +12,8 @@ const char *Display::message_string[] = {
const char *Display::main_menu_strings[][2] = {
{"PACKET", "RATE"},
+ {"SWITCH", "MODE"},
+ {"ANTENNA", "MODE"},
{"TX", "POWER"},
{"TELEM", "RATIO"},
{"MOTION", "DETECT"},
@@ -47,6 +51,27 @@ const char *rate_string[] = {
"100 Full",
"50Hz"
};
+#elif defined(RADIO_LR1121)
+static const char *rate_string[] = {
+ // 900
+ "200Hz",
+ "100 Full",
+ "100Hz",
+ "50Hz",
+ // 2.4
+ "500Hz",
+ "333 Full",
+ "250Hz",
+ "150Hz",
+ "100 Full",
+ "50Hz",
+ // Dual
+ "X150Hz",
+ "X100 Full",
+ // 900
+ "250Hz",
+ "200 Full",
+};
#else
static const char *rate_string[] = {
"200Hz",
@@ -58,6 +83,24 @@ static const char *rate_string[] = {
};
#endif
+static const char *switch_mode[] = {
+ "Wide",
+ "Hybrid",
+};
+
+static const char *switch_mode_full[] = {
+ "8Ch",
+ "16Ch /2",
+ "12Ch Mix"
+};
+
+static const char *antenna_mode[] = {
+ "Gemini",
+ "Ant 1",
+ "Ant 2",
+ "Switch",
+};
+
static const char *power_string[] = {
"10mW",
"25mW",
@@ -166,6 +209,14 @@ int Display::getValueCount(menu_item_t menu)
{
case STATE_PACKET:
return ARRAY_SIZE(rate_string);
+ case STATE_SWITCH:
+ if (OtaIsFullRes)
+ {
+ return ARRAY_SIZE(switch_mode_full);
+ }
+ return ARRAY_SIZE(switch_mode);
+ case STATE_ANTENNA:
+ return ARRAY_SIZE(antenna_mode);
case STATE_TELEMETRY:
return ARRAY_SIZE(ratio_string);
case STATE_POWERSAVE:
@@ -198,6 +249,14 @@ const char *Display::getValue(menu_item_t menu, uint8_t value_index)
{
case STATE_PACKET:
return rate_string[value_index];
+ case STATE_SWITCH:
+ if (OtaIsFullRes)
+ {
+ return switch_mode_full[value_index];
+ }
+ return switch_mode[value_index];
+ case STATE_ANTENNA:
+ return antenna_mode[value_index];
case STATE_TELEMETRY:
return ratio_string[value_index];
case STATE_TELEMETRY_CURR:
diff --git a/src/lib/SCREEN/fsm.h b/src/lib/SCREEN/fsm.h
index 8ba7bfc379..f6f14f5ae7 100644
--- a/src/lib/SCREEN/fsm.h
+++ b/src/lib/SCREEN/fsm.h
@@ -1,4 +1,3 @@
-#include
#include
#include "targets.h"
@@ -37,8 +36,8 @@ typedef struct fsm_state_event_s
typedef struct fsm_state_entry_s
{
fsm_state_t state;
- std::function available;
- std::function entry;
+ bool (*available)();
+ void (*entry)(bool init);
uint16_t timeout;
fsm_state_event_t const *events;
uint8_t event_count;
diff --git a/src/lib/SCREEN/menu.cpp b/src/lib/SCREEN/menu.cpp
index 1d46567975..7a8fb414c5 100644
--- a/src/lib/SCREEN/menu.cpp
+++ b/src/lib/SCREEN/menu.cpp
@@ -6,8 +6,9 @@
#include "helpers.h"
#include "logging.h"
#include "POWERMGNT.h"
-#include "CRSF.h"
+#include "handset.h"
#include "OTA.h"
+#include "deferred.h"
#ifdef HAS_THERMAL
#include "thermal.h"
@@ -17,8 +18,6 @@ extern Thermal thermal;
extern FiniteStateMachine state_machine;
-extern void EnterBindingMode();
-extern bool InBindingMode;
extern bool RxWiFiReadyToSend;
extern bool TxBackpackWiFiReadyToSend;
extern bool VRxBackpackWiFiReadyToSend;
@@ -28,7 +27,6 @@ extern void setWifiUpdateMode();
extern void SetSyncSpam();
extern uint8_t adjustPacketRateForBaud(uint8_t rate);
extern uint8_t adjustSwitchModeForAirRate(OtaSwitchMode_e eSwitchMode, uint8_t packetSize);
-extern void deferExecution(uint32_t ms, std::function f);
extern Display *display;
@@ -79,7 +77,7 @@ static void displayIdleScreen(bool init)
message_index_t disp_message;
if (connectionState == noCrossfire || connectionState > FAILURE_STATES) {
disp_message = MSG_ERROR;
- } else if(CRSF::IsArmed()) {
+ } else if(handset->IsArmed()) {
disp_message = MSG_ARMED;
} else if(connectionState == connected) {
if (connectionHasModelMatch) {
@@ -141,6 +139,16 @@ static void setupValueIndex(bool init)
values_max = display->getValueCount((menu_item_t)state_machine.getParentState())-1;
values_index = config.GetRate();
break;
+ case STATE_SWITCH:
+ values_min = 0;
+ values_max = display->getValueCount((menu_item_t)state_machine.getParentState())-1;
+ values_index = config.GetSwitchMode();
+ break;
+ case STATE_ANTENNA:
+ values_min = 0;
+ values_max = display->getValueCount((menu_item_t)state_machine.getParentState())-1;
+ values_index = config.GetAntennaMode();
+ break;
case STATE_TELEMETRY:
values_min = 0;
values_max = display->getValueCount((menu_item_t)state_machine.getParentState())-1;
@@ -159,7 +167,7 @@ static void setupValueIndex(bool init)
case STATE_POWER_MAX:
values_min = MinPower;
- values_max = MaxPower;
+ values_max = POWERMGNT::getMaxPower();
values_index = config.GetPower();
break;
case STATE_POWER_DYNAMIC:
@@ -220,16 +228,33 @@ static void saveValueIndex(bool init)
// If the switch mode is going to change, block the change while connected
if (newSwitchMode == OtaSwitchModeCurrent || connectionState == disconnected)
{
- deferExecution(100, [actualRate, newSwitchMode](){
+ deferExecutionMillis(100, [actualRate, newSwitchMode](){
config.SetRate(actualRate);
config.SetSwitchMode(newSwitchMode);
+ OtaUpdateSerializers((OtaSwitchMode_e)newSwitchMode, ExpressLRS_currAirRate_Modparams->PayloadLength);
SetSyncSpam();
});
}
break;
}
+ case STATE_SWITCH: {
+ // Only allow changing switch mode when disconnected since we need to guarantee
+ // the pack and unpack functions are matched
+ if (connectionState == disconnected)
+ {
+ deferExecutionMillis(100, [val](){
+ config.SetSwitchMode(val);
+ OtaUpdateSerializers((OtaSwitchMode_e)val, ExpressLRS_currAirRate_Modparams->PayloadLength);
+ SetSyncSpam();
+ });
+ }
+ break;
+ }
+ case STATE_ANTENNA:
+ config.SetAntennaMode(values_index);
+ break;
case STATE_TELEMETRY:
- deferExecution(100, [val](){
+ deferExecutionMillis(100, [val](){
config.SetTlm(val);
SetSyncSpam();
});
@@ -409,7 +434,7 @@ static void executeBind(bool init)
{
if (init)
{
- EnterBindingMode();
+ EnterBindingModeSafely();
display->displayBindStatus();
return;
}
@@ -575,6 +600,8 @@ fsm_state_event_t const wifi_menu_events[] = {MENU_EVENTS(wifi_menu_fsm)};
fsm_state_entry_t const main_menu_fsm[] = {
{STATE_PACKET, nullptr, displayMenuScreen, 20000, value_menu_events, ARRAY_SIZE(value_menu_events)},
+ {STATE_SWITCH, nullptr, displayMenuScreen, 20000, value_menu_events, ARRAY_SIZE(value_menu_events)},
+ {STATE_ANTENNA, [](){return isDualRadio();}, displayMenuScreen, 20000, value_menu_events, ARRAY_SIZE(value_menu_events)},
{STATE_POWER, nullptr, displayMenuScreen, 20000, power_menu_events, ARRAY_SIZE(power_menu_events)},
{STATE_TELEMETRY, [](){return !firmwareOptions.is_airport;}, displayMenuScreen, 20000, value_menu_events, ARRAY_SIZE(value_menu_events)},
#ifdef HAS_GSENSOR
diff --git a/src/lib/SCREEN/menu.h b/src/lib/SCREEN/menu.h
index bb206d51b8..ed58f9dec3 100644
--- a/src/lib/SCREEN/menu.h
+++ b/src/lib/SCREEN/menu.h
@@ -6,6 +6,8 @@
enum fsm_state_s {
// This first ones have menu text & icons (and are used as array indexes)
STATE_PACKET = 0,
+ STATE_SWITCH,
+ STATE_ANTENNA,
STATE_POWER,
STATE_TELEMETRY,
STATE_POWERSAVE,
diff --git a/src/lib/SPIEx/SPIEx.cpp b/src/lib/SPIEx/SPIEx.cpp
new file mode 100644
index 0000000000..a381379602
--- /dev/null
+++ b/src/lib/SPIEx/SPIEx.cpp
@@ -0,0 +1,98 @@
+#include "SPIEx.h"
+
+#if defined(PLATFORM_ESP32)
+#include
+#endif
+
+void ICACHE_RAM_ATTR SPIExClass::_transfer(uint8_t cs_mask, uint8_t *data, uint32_t size, bool reading)
+{
+#if defined(PLATFORM_ESP32)
+ spi_dev_t *spi = *(reinterpret_cast(bus()));
+ // wait for SPI to become non-busy
+ while(spi->cmd.usr) {}
+
+ // Set the CS pins which we want controlled by the SPI module for this operation
+ spiDisableSSPins(bus(), ~cs_mask);
+ spiEnableSSPins(bus(), cs_mask);
+
+#if defined(PLATFORM_ESP32_S3)
+ spi->ms_dlen.ms_data_bitlen = (size*8)-1;
+#else
+ spi->mosi_dlen.usr_mosi_dbitlen = ((size * 8) - 1);
+ spi->miso_dlen.usr_miso_dbitlen = ((size * 8) - 1);
+#endif
+
+ // write the data to the SPI FIFO
+ const uint32_t words = (size + 3) / 4;
+ auto * const wordsBuf = reinterpret_cast(data);
+ for(int i=0; idata_buf[i] = wordsBuf[i]; //copy buffer to spi fifo
+ }
+
+#if defined(PLATFORM_ESP32_S3)
+ spi->cmd.update = 1;
+ while (spi->cmd.update);
+#endif
+ // start the SPI module
+ spi->cmd.usr = 1;
+
+ if (reading)
+ {
+ // wait for SPI write to complete
+ while(spi->cmd.usr) {}
+
+ for(int i=0; idata_buf[i]; //copy spi fifo to buffer
+ }
+ }
+#elif defined(PLATFORM_ESP8266)
+ // we support only one hardware controlled CS pin, so theres nothing to do to configure it at this point
+
+ // wait for SPI to become non-busy
+ while(SPI1CMD & SPIBUSY) {}
+
+ // Set in/out Bits to transfer
+ const uint32_t mask = ~((SPIMMOSI << SPILMOSI) | (SPIMMISO << SPILMISO));
+ const auto bits = (size * 8) - 1;
+ SPI1U1 = ((SPI1U1 & mask) | ((bits << SPILMOSI) | (bits << SPILMISO)));
+
+ // write the data to the SPI FIFO
+ volatile uint32_t * const fifoPtr = &SPI1W0;
+ const uint8_t outSize = ((size + 3) / 4);
+ uint32_t * const dataPtr = (uint32_t*) data;
+ for(int i=0; i
+
+/**
+ * @brief An extension to the platform SPI class that provides some performance enhancements.
+ *
+ * 1. The provided data buffer is expected to be word-aligned and word-padded, this allow the
+ * data to be copied to the SPI fifo in word-sized chunks.
+ * 2. The wait for non-busy is removed from the end of the write call so the processor can
+ * continue to do work while the SPI module is pumping the data from the FIFO to the external
+ * device.
+ */
+class SPIExClass : public SPIClass
+{
+public:
+#if defined(PLATFORM_ESP32)
+ explicit SPIExClass(uint8_t spi_bus=HSPI) : SPIClass(spi_bus) {}
+#else
+ explicit SPIExClass() : SPIClass() {}
+#endif
+
+ /**
+ * @brief Perform an SPI read operation on the SPI bus.
+ *
+ * If the SPI bus is busy the processor waits for the current operation to complete before starting
+ * this operation.
+ * Once the SPI bus is free, the data is copied to the SPI fifo and the processor waits for the operation
+ * to complete then copies the incoming data back into the provided data buffer.
+ *
+ * @param cs_mask mask of CS pins to enable for this operation
+ * @param data word-aligned and padded data buffer
+ * @param size the number of bytes to be read into in the data buffer
+ */
+ void inline ICACHE_RAM_ATTR read(uint8_t cs_mask, uint8_t *data, uint32_t size) { _transfer(cs_mask, data, size, true); }
+
+ /**
+ * @brief Perform an SPI write operation on the SPI bus.
+ *
+ * If the SPI bus is busy the processor waits for the current operation to complete before starting
+ * this operation.
+ * One the SPI bus is not busy, it copies the data to the SPI fifo then returns without waiting for
+ * the operation to complete allowing the processor to do other work.
+ *
+ * @param cs_mask mask of CS pins to enable for this operation
+ * @param data word-aligned and padded data buffer
+ * @param size the number of bytes to be written to the SPI device
+ */
+ void inline ICACHE_RAM_ATTR write(uint8_t cs_mask, uint8_t * data, uint32_t size) { _transfer(cs_mask, data, size, false); }
+
+private:
+ void _transfer(uint8_t cs_mask, uint8_t *data, uint32_t size, bool reading);
+};
+
+extern SPIExClass SPIEx;
diff --git a/src/lib/SX127xDriver/SX127x.cpp b/src/lib/SX127xDriver/SX127x.cpp
index b613044277..c0996b707c 100644
--- a/src/lib/SX127xDriver/SX127x.cpp
+++ b/src/lib/SX127xDriver/SX127x.cpp
@@ -52,7 +52,7 @@ SX127xDriver::SX127xDriver(): SX12xxDriverCommon()
lastSuccessfulPacketRadio = SX12XX_Radio_1;
}
-bool SX127xDriver::Begin()
+bool SX127xDriver::Begin(uint32_t minimumFrequency, uint32_t maximumFrequency)
{
hal.init();
hal.IsrCallback_1 = &SX127xDriver::IsrCallback_1;
@@ -86,9 +86,21 @@ bool SX127xDriver::Begin()
}
ConfigLoraDefaults();
- // Force the next power update
+ // Force the next power update, and use the defaults for RFO_HF or PA_BOOST
pwrCurrent = PWRPENDING_NONE;
- SetOutputPower(0);
+#if defined(TARGET_UNIFIED_RX) || defined(TARGET_UNIFIED_TX)
+ if (POWER_OUTPUT_VALUES2 == nullptr)
+#endif
+ {
+ if (OPT_USE_SX1276_RFO_HF)
+ {
+ SetOutputPower(SX127X_MAX_OUTPUT_POWER_RFO_HF);
+ }
+ else
+ {
+ SetOutputPower(SX127X_MAX_OUTPUT_POWER);
+ }
+ }
CommitOutputPower();
return true;
@@ -224,13 +236,15 @@ void SX127xDriver::SetSyncWord(uint8_t syncWord)
void SX127xDriver::SetOutputPower(uint8_t Power)
{
uint8_t pwrNew;
+ Power &= SX127X_PA_POWER_MASK;
+
if (OPT_USE_SX1276_RFO_HF)
{
- pwrNew = SX127X_PA_SELECT_RFO | SX127X_MAX_OUTPUT_POWER_RFO_HF | Power;
+ pwrNew = SX127X_PA_SELECT_RFO | Power;
}
else
{
- pwrNew = SX127X_PA_SELECT_BOOST | SX127X_MAX_OUTPUT_POWER | Power;
+ pwrNew = SX127X_PA_SELECT_BOOST | Power;
}
if ((pwrPending == PWRPENDING_NONE && pwrCurrent != pwrNew) || pwrPending != pwrNew)
@@ -244,7 +258,7 @@ void ICACHE_RAM_ATTR SX127xDriver::CommitOutputPower()
if (pwrPending == PWRPENDING_NONE)
return;
- pwrCurrent = pwrPending;
+ pwrCurrent = pwrPending & 0xFF;
pwrPending = PWRPENDING_NONE;
hal.writeRegister(SX127X_REG_PA_CONFIG, pwrCurrent, SX12XX_Radio_All);
}
@@ -332,7 +346,7 @@ void ICACHE_RAM_ATTR SX127xDriver::SetRxTimeoutUs(uint32_t interval)
timeoutSymbols = interval / symbolTimeUs;
hal.writeRegisterBits(SX127X_REG_SYMB_TIMEOUT_MSB, timeoutSymbols >> 8, SX127X_REG_SYMB_TIMEOUT_MSB_MASK, SX12XX_Radio_All); // set the timeout MSB
hal.writeRegister(SX127X_REG_SYMB_TIMEOUT_LSB, timeoutSymbols & 0xFF, SX12XX_Radio_All);
- DBGLN("SetRxTimeout(%u), symbolTime=%uus symbols=%u", interval, (uint32_t)symbolTimeUs, timeoutSymbols)
+ DBGLN("SetRxTimeout(%u), symbolTime=%uus symbols=%u", interval, (uint32_t)symbolTimeUs, timeoutSymbols);
}
}
@@ -390,7 +404,7 @@ void ICACHE_RAM_ATTR SX127xDriver::TXnb(uint8_t * data, uint8_t size, SX12XX_Rad
// }
transmittingRadio = radioNumber;
-
+
SetMode(SX127x_OPMODE_STANDBY, SX12XX_Radio_All);
if (radioNumber == SX12XX_Radio_NONE)
@@ -473,9 +487,6 @@ void ICACHE_RAM_ATTR SX127xDriver::GetLastPacketStats()
hal.writeRegister(SX127X_REG_FIFO_ADDR_PTR, FIFOaddr, radio[secondRadioIdx]);
hal.readRegister(SX127X_REG_FIFO, RXdataBuffer_second, PayloadLength, radio[secondRadioIdx]);
- // leaving only the type in the first byte (crcHigh was cleared)
- RXdataBuffer[0] &= 0b11;
- RXdataBuffer_second[0] &= 0b11;
// if the second packet is same to the first, it's valid
if (memcmp(RXdataBuffer, RXdataBuffer_second, PayloadLength) == 0)
{
diff --git a/src/lib/SX127xDriver/SX127x.h b/src/lib/SX127xDriver/SX127x.h
index 64f380762c..945810f7ff 100644
--- a/src/lib/SX127xDriver/SX127x.h
+++ b/src/lib/SX127xDriver/SX127x.h
@@ -26,7 +26,7 @@ class SX127xDriver: public SX12xxDriverCommon
////////////////Configuration Functions/////////////
SX127xDriver();
- bool Begin();
+ bool Begin(uint32_t minimumFrequency, uint32_t maximumFrequency);
void End();
bool DetectChip(SX12XX_Radio_Number_t radioNumber);
void Config(uint8_t bw, uint8_t sf, uint8_t cr, uint32_t freq, uint8_t preambleLen, uint8_t syncWord, bool InvertIQ, uint8_t _PayloadLength, uint32_t interval);
@@ -78,7 +78,7 @@ class SX127xDriver: public SX12xxDriverCommon
private:
// constant used for no power change pending
// must not be a valid power register value
- static const uint8_t PWRPENDING_NONE = SX127X_MAX_OUTPUT_POWER_INVALID;
+ static const int16_t PWRPENDING_NONE = -1;
SX127x_Bandwidth currBW;
SX127x_SpreadingFactor currSF;
@@ -88,7 +88,7 @@ class SX127xDriver: public SX12xxDriverCommon
uint8_t currSyncWord;
uint8_t currPreambleLen;
uint8_t pwrCurrent;
- uint8_t pwrPending;
+ int16_t pwrPending;
uint8_t lowFrequencyMode;
static void IsrCallback_1();
diff --git a/src/lib/SX127xDriver/SX127xHal.cpp b/src/lib/SX127xDriver/SX127xHal.cpp
index 451bfafe3e..8d4aa58df7 100644
--- a/src/lib/SX127xDriver/SX127xHal.cpp
+++ b/src/lib/SX127xDriver/SX127xHal.cpp
@@ -3,7 +3,7 @@
#include "SX127xHal.h"
#include "SX127xRegs.h"
#include "logging.h"
-#include
+#include
SX127xHal *SX127xHal::instance = NULL;
@@ -19,7 +19,7 @@ void SX127xHal::end()
{
detachInterrupt(GPIO_PIN_DIO0_2);
}
- SPI.end();
+ SPIEx.end();
IsrCallback_1 = nullptr; // remove callbacks
IsrCallback_2 = nullptr; // remove callbacks
}
@@ -36,37 +36,35 @@ void SX127xHal::init()
pinMode(GPIO_PIN_NSS, OUTPUT);
digitalWrite(GPIO_PIN_NSS, HIGH);
- if (GPIO_PIN_NSS_2 != UNDEF_PIN)
- {
- pinMode(GPIO_PIN_NSS_2, OUTPUT);
- digitalWrite(GPIO_PIN_NSS_2, HIGH);
- }
#ifdef PLATFORM_ESP32
- SPI.begin(GPIO_PIN_SCK, GPIO_PIN_MISO, GPIO_PIN_MOSI, GPIO_PIN_NSS); // sck, miso, mosi, ss (ss can be any GPIO)
+ SPIEx.begin(GPIO_PIN_SCK, GPIO_PIN_MISO, GPIO_PIN_MOSI, GPIO_PIN_NSS); // sck, miso, mosi, ss (ss can be any GPIO)
gpio_pullup_en((gpio_num_t)GPIO_PIN_MISO);
- SPI.setFrequency(10000000);
- SPI.setHwCs(true);
+ SPIEx.setFrequency(10000000);
+ SPIEx.setHwCs(true);
if (GPIO_PIN_NSS_2 != UNDEF_PIN)
{
- spiAttachSS(SPI.bus(), 1, GPIO_PIN_NSS_2);
+ pinMode(GPIO_PIN_NSS_2, OUTPUT);
+ digitalWrite(GPIO_PIN_NSS_2, HIGH);
+ spiAttachSS(SPIEx.bus(), 1, GPIO_PIN_NSS_2);
}
- spiEnableSSPins(SPI.bus(), 0xFF);
+ spiEnableSSPins(SPIEx.bus(), SX12XX_Radio_All);
#elif defined(PLATFORM_ESP8266)
DBGLN("PLATFORM_ESP8266");
- SPI.begin();
- SPI.setBitOrder(MSBFIRST);
- SPI.setDataMode(SPI_MODE0);
- SPI.setFrequency(10000000);
+ SPIEx.begin();
+ SPIEx.setHwCs(true);
+ SPIEx.setBitOrder(MSBFIRST);
+ SPIEx.setDataMode(SPI_MODE0);
+ SPIEx.setFrequency(10000000);
#elif defined(PLATFORM_STM32)
DBGLN("Config SPI");
- SPI.setMOSI(GPIO_PIN_MOSI);
- SPI.setMISO(GPIO_PIN_MISO);
- SPI.setSCLK(GPIO_PIN_SCK);
- SPI.setBitOrder(MSBFIRST);
- SPI.setDataMode(SPI_MODE0);
- SPI.begin();
- SPI.setClockDivider(SPI_CLOCK_DIV4); // 72 / 8 = 9 MHz
+ SPIEx.setBitOrder(MSBFIRST);
+ SPIEx.setDataMode(SPI_MODE0);
+ SPIEx.setMOSI(GPIO_PIN_MOSI);
+ SPIEx.setMISO(GPIO_PIN_MISO);
+ SPIEx.setSCLK(GPIO_PIN_SCK);
+ SPIEx.begin();
+ SPIEx.setClockDivider(SPI_CLOCK_DIV4); // 72 / 8 = 9 MHz
#endif
attachInterrupt(digitalPinToInterrupt(GPIO_PIN_DIO0), this->dioISR_1, RISING);
@@ -76,19 +74,6 @@ void SX127xHal::init()
}
}
-void ICACHE_RAM_ATTR SX127xHal::setNss(uint8_t radioNumber, bool state)
-{
-#if defined(PLATFORM_ESP32)
- spiDisableSSPins(SPI.bus(), ~radioNumber);
- spiEnableSSPins(SPI.bus(), radioNumber);
-#else
- if (radioNumber & SX12XX_Radio_1)
- digitalWrite(GPIO_PIN_NSS, state);
- if (GPIO_PIN_NSS_2 != UNDEF_PIN && radioNumber & SX12XX_Radio_2)
- digitalWrite(GPIO_PIN_NSS_2, state);
-#endif
-}
-
void SX127xHal::reset(void)
{
DBGLN("SX127x Reset");
@@ -96,10 +81,18 @@ void SX127xHal::reset(void)
if (GPIO_PIN_RST != UNDEF_PIN)
{
pinMode(GPIO_PIN_RST, OUTPUT);
- delay(100);
digitalWrite(GPIO_PIN_RST, LOW);
- delay(100);
+ if (GPIO_PIN_RST_2 != UNDEF_PIN)
+ {
+ pinMode(GPIO_PIN_RST_2, OUTPUT);
+ digitalWrite(GPIO_PIN_RST_2, LOW);
+ }
+ delay(50); // Safety buffer. Busy takes longer to go low than the 1ms timeout in WaitOnBusy().
pinMode(GPIO_PIN_RST, INPUT); // leave floating
+ if (GPIO_PIN_RST_2 != UNDEF_PIN)
+ {
+ pinMode(GPIO_PIN_RST_2, INPUT);
+ }
}
DBGLN("SX127x Ready!");
@@ -121,12 +114,10 @@ uint8_t ICACHE_RAM_ATTR SX127xHal::readRegister(uint8_t reg, SX12XX_Radio_Number
void ICACHE_RAM_ATTR SX127xHal::readRegister(uint8_t reg, uint8_t *data, uint8_t numBytes, SX12XX_Radio_Number_t radioNumber)
{
- WORD_ALIGNED_ATTR uint8_t buf[numBytes + 1];
+ WORD_ALIGNED_ATTR uint8_t buf[WORD_PADDED(numBytes + 1)];
buf[0] = reg | SPI_READ;
- setNss(radioNumber, LOW);
- SPI.transfer(buf, numBytes + 1);
- setNss(radioNumber, HIGH);
+ SPIEx.read(radioNumber, buf, numBytes + 1);
memcpy(data, buf + 1, numBytes);
}
@@ -139,7 +130,7 @@ void ICACHE_RAM_ATTR SX127xHal::writeRegisterBits(uint8_t reg, uint8_t value, ui
uint8_t newValue = (currentValue & ~mask) | (value & mask);
writeRegister(reg, newValue, SX12XX_Radio_1);
}
-
+
if (GPIO_PIN_NSS_2 != UNDEF_PIN && radioNumber & SX12XX_Radio_2)
{
uint8_t currentValue = readRegister(reg, SX12XX_Radio_2);
@@ -155,13 +146,11 @@ void ICACHE_RAM_ATTR SX127xHal::writeRegister(uint8_t reg, uint8_t data, SX12XX_
void ICACHE_RAM_ATTR SX127xHal::writeRegister(uint8_t reg, uint8_t *data, uint8_t numBytes, SX12XX_Radio_Number_t radioNumber)
{
- WORD_ALIGNED_ATTR uint8_t buf[numBytes + 1];
+ WORD_ALIGNED_ATTR uint8_t buf[WORD_PADDED(numBytes + 1)];
buf[0] = reg | SPI_WRITE;
memcpy(buf + 1, data, numBytes);
- setNss(radioNumber, LOW);
- SPI.transfer(buf, numBytes + 1);
- setNss(radioNumber, HIGH);
+ SPIEx.write(radioNumber, buf, numBytes + 1);
}
void ICACHE_RAM_ATTR SX127xHal::dioISR_1()
diff --git a/src/lib/SX127xDriver/SX127xHal.h b/src/lib/SX127xDriver/SX127xHal.h
index 8b9f1c9c29..647f9f04fa 100644
--- a/src/lib/SX127xDriver/SX127xHal.h
+++ b/src/lib/SX127xDriver/SX127xHal.h
@@ -15,8 +15,6 @@ class SX127xHal
void end();
void reset();
- void ICACHE_RAM_ATTR setNss(uint8_t radioNumber, bool state);
-
uint8_t ICACHE_RAM_ATTR readRegisterBits(uint8_t reg, uint8_t mask, SX12XX_Radio_Number_t radioNumber);
uint8_t ICACHE_RAM_ATTR readRegister(uint8_t reg, SX12XX_Radio_Number_t radioNumber);
void ICACHE_RAM_ATTR readRegister(uint8_t reg, uint8_t *data, uint8_t numBytes, SX12XX_Radio_Number_t radioNumber);
diff --git a/src/lib/SX127xDriver/SX127xRegs.h b/src/lib/SX127xDriver/SX127xRegs.h
index 6c51c09e86..4457f5d145 100644
--- a/src/lib/SX127xDriver/SX127xRegs.h
+++ b/src/lib/SX127xDriver/SX127xRegs.h
@@ -125,10 +125,9 @@ typedef enum
// SX127X_REG_PA_CONFIG
#define SX127X_PA_SELECT_RFO 0b00000000 // 7 7 RFO pin output, power limited to +14 dBm
#define SX127X_PA_SELECT_BOOST 0b10000000 // 7 7 PA_BOOST pin output, power limited to +20 dBm
-#define SX127X_OUTPUT_POWER 0b00001111 // 3 0 output power: P_out = 17 - (15 - OUTPUT_POWER) [dBm] for PA_SELECT_BOOST
+#define SX127X_PA_POWER_MASK 0b01111111 // 6 0 PA MAX_POWER and OUTPUT_POWER combined bit mask
#define SX127X_MAX_OUTPUT_POWER_RFO_HF 0b00000000 // Max output power when using RFO_HF
#define SX127X_MAX_OUTPUT_POWER 0b01110000 // Enable max output power
-#define SX127X_MAX_OUTPUT_POWER_INVALID 0b00010000 // A value for the MaxPower field that is not used by ExpressLRS
// SX127X_REG_OCP
#define SX127X_OCP_OFF 0b00000000 // 5 5 PA overload current protection disabled
#define SX127X_OCP_ON 0b00100000 // 5 5 PA overload current protection enabled
diff --git a/src/lib/SX1280Driver/SX1280.cpp b/src/lib/SX1280Driver/SX1280.cpp
index 3ef997f109..5a040ae33a 100644
--- a/src/lib/SX1280Driver/SX1280.cpp
+++ b/src/lib/SX1280Driver/SX1280.cpp
@@ -48,12 +48,12 @@ static uint32_t endTX;
#define RX_TIMEOUT_PERIOD_BASE SX1280_RADIO_TICK_SIZE_0015_US
#define RX_TIMEOUT_PERIOD_BASE_NANOS 15625
-#ifdef USE_SX1280_DCDC
- #ifndef OPT_USE_SX1280_DCDC
- #define OPT_USE_SX1280_DCDC true
+#ifdef USE_HARDWARE_DCDC
+ #ifndef OPT_USE_HARDWARE_DCDC
+ #define OPT_USE_HARDWARE_DCDC true
#endif
#else
- #define OPT_USE_SX1280_DCDC false
+ #define OPT_USE_HARDWARE_DCDC false
#endif
SX1280Driver::SX1280Driver(): SX12xxDriverCommon()
@@ -62,6 +62,7 @@ SX1280Driver::SX1280Driver(): SX12xxDriverCommon()
timeout = 0xffff;
currOpmode = SX1280_MODE_SLEEP;
lastSuccessfulPacketRadio = SX12XX_Radio_1;
+ fallBackMode = SX1280_MODE_STDBY_RC;
}
void SX1280Driver::End()
@@ -77,7 +78,7 @@ void SX1280Driver::End()
PayloadLength = 8; // Dummy default value which is overwritten during setup.
}
-bool SX1280Driver::Begin()
+bool SX1280Driver::Begin(uint32_t minimumFrequency, uint32_t maximumFrequency)
{
hal.init();
hal.IsrCallback_1 = &SX1280Driver::IsrCallback_1;
@@ -114,6 +115,7 @@ bool SX1280Driver::Begin()
}
#if defined(TARGET_RX)
+ fallBackMode = SX1280_MODE_FS;
hal.WriteCommand(SX1280_RADIO_SET_AUTOFS, 0x01, SX12XX_Radio_All); //Enable auto FS
#else
/*
@@ -124,6 +126,7 @@ transitioning from FS mode and the other from Standby mode. This causes the tx d
*/
if (GPIO_PIN_NSS_2 == UNDEF_PIN)
{
+ fallBackMode = SX1280_MODE_FS;
hal.WriteCommand(SX1280_RADIO_SET_AUTOFS, 0x01, SX12XX_Radio_All); //Enable auto FS
}
#endif
@@ -132,8 +135,8 @@ transitioning from FS mode and the other from Standby mode. This causes the tx d
pwrCurrent = PWRPENDING_NONE;
SetOutputPower(SX1280_POWER_MIN);
CommitOutputPower();
-#if defined(USE_SX1280_DCDC)
- if (OPT_USE_SX1280_DCDC)
+#if defined(USE_HARDWARE_DCDC)
+ if (OPT_USE_HARDWARE_DCDC)
{
hal.WriteCommand(SX1280_RADIO_SET_REGULATORMODE, SX1280_USE_DCDC, SX12XX_Radio_All); // Enable DCDC converter instead of LDO
}
@@ -182,8 +185,8 @@ void SX1280Driver::Config(uint8_t bw, uint8_t sf, uint8_t cr, uint32_t regfreq,
SetFrequencyReg(regfreq);
SetRxTimeoutUs(interval);
- uint8_t dio1Mask = SX1280_IRQ_TX_DONE | SX1280_IRQ_RX_DONE;
- uint8_t irqMask = SX1280_IRQ_TX_DONE | SX1280_IRQ_RX_DONE | SX1280_IRQ_SYNCWORD_VALID | SX1280_IRQ_SYNCWORD_ERROR | SX1280_IRQ_CRC_ERROR;
+ uint16_t dio1Mask = SX1280_IRQ_TX_DONE | SX1280_IRQ_RX_DONE;
+ uint16_t irqMask = SX1280_IRQ_TX_DONE | SX1280_IRQ_RX_DONE | SX1280_IRQ_SYNCWORD_VALID | SX1280_IRQ_SYNCWORD_ERROR | SX1280_IRQ_CRC_ERROR;
SetDioIrqParams(irqMask, dio1Mask);
}
@@ -224,7 +227,7 @@ void ICACHE_RAM_ATTR SX1280Driver::CommitOutputPower()
hal.WriteCommand(SX1280_RADIO_SET_TXPARAMS, buf, sizeof(buf), SX12XX_Radio_All);
}
-void SX1280Driver::SetMode(SX1280_RadioOperatingModes_t OPmode, SX12XX_Radio_Number_t radioNumber)
+void SX1280Driver::SetMode(SX1280_RadioOperatingModes_t OPmode, SX12XX_Radio_Number_t radioNumber, uint32_t incomingTimeout)
{
/*
Comment out since it is difficult to keep track of dual radios.
@@ -236,6 +239,8 @@ void SX1280Driver::SetMode(SX1280_RadioOperatingModes_t OPmode, SX12XX_Radio_Num
// }
WORD_ALIGNED_ATTR uint8_t buf[3];
+ uint16_t tempTimeout;
+
switch (OPmode)
{
@@ -260,9 +265,10 @@ void SX1280Driver::SetMode(SX1280_RadioOperatingModes_t OPmode, SX12XX_Radio_Num
break;
case SX1280_MODE_RX:
+ tempTimeout = incomingTimeout ? (incomingTimeout * 1000 / RX_TIMEOUT_PERIOD_BASE_NANOS) : timeout;
buf[0] = RX_TIMEOUT_PERIOD_BASE;
- buf[1] = timeout >> 8;
- buf[2] = timeout & 0xFF;
+ buf[1] = tempTimeout >> 8;
+ buf[2] = tempTimeout & 0xFF;
hal.WriteCommand(SX1280_RADIO_SET_RX, buf, sizeof(buf), radioNumber, 100);
break;
@@ -480,7 +486,7 @@ void ICACHE_RAM_ATTR SX1280Driver::TXnb(uint8_t * data, uint8_t size, SX12XX_Rad
if (currOpmode == SX1280_MODE_TX)
{
DBGLN("Timeout!");
- SetMode(SX1280_MODE_FS, SX12XX_Radio_All);
+ SetMode(fallBackMode, SX12XX_Radio_All);
ClearIrqStatus(SX1280_IRQ_RADIO_ALL, SX12XX_Radio_All);
TXnbISR();
return;
@@ -488,7 +494,7 @@ void ICACHE_RAM_ATTR SX1280Driver::TXnb(uint8_t * data, uint8_t size, SX12XX_Rad
if (radioNumber == SX12XX_Radio_NONE)
{
- instance->SetMode(SX1280_MODE_FS, SX12XX_Radio_All);
+ instance->SetMode(fallBackMode, SX12XX_Radio_All);
return;
}
@@ -509,11 +515,11 @@ void ICACHE_RAM_ATTR SX1280Driver::TXnb(uint8_t * data, uint8_t size, SX12XX_Rad
// Make sure the unused radio is in FS mode and will not receive the tx packet.
if (radioNumber == SX12XX_Radio_1)
{
- instance->SetMode(SX1280_MODE_FS, SX12XX_Radio_2);
+ instance->SetMode(fallBackMode, SX12XX_Radio_2);
}
else
{
- instance->SetMode(SX1280_MODE_FS, SX12XX_Radio_1);
+ instance->SetMode(fallBackMode, SX12XX_Radio_1);
}
}
@@ -554,10 +560,10 @@ bool ICACHE_RAM_ATTR SX1280Driver::RXnbISR(uint16_t irqStatus, SX12XX_Radio_Numb
return RXdoneCallback(fail);
}
-void ICACHE_RAM_ATTR SX1280Driver::RXnb(SX1280_RadioOperatingModes_t rxMode)
+void ICACHE_RAM_ATTR SX1280Driver::RXnb(SX1280_RadioOperatingModes_t rxMode, uint32_t incomingTimeout)
{
RFAMP.RXenable();
- SetMode(rxMode, SX12XX_Radio_All);
+ SetMode(rxMode, SX12XX_Radio_All, incomingTimeout);
}
uint8_t ICACHE_RAM_ATTR SX1280Driver::GetRxBufferAddr(SX12XX_Radio_Number_t radioNumber)
@@ -625,9 +631,6 @@ void ICACHE_RAM_ATTR SX1280Driver::GetLastPacketStats()
WORD_ALIGNED_ATTR uint8_t RXdataBuffer_second[RXBuffSize];
hal.ReadBuffer (FIFOaddr, RXdataBuffer_second, PayloadLength, radio[secondRadioIdx]);
- // leaving only the type in the first byte (crcHigh was cleared)
- RXdataBuffer[0] &= 0b11;
- RXdataBuffer_second[0] &= 0b11;
// if the second packet is same to the first, it's valid
if(memcmp(RXdataBuffer, RXdataBuffer_second, PayloadLength) == 0)
{
diff --git a/src/lib/SX1280Driver/SX1280.h b/src/lib/SX1280Driver/SX1280.h
index fdc1df9981..88df5d46ef 100644
--- a/src/lib/SX1280Driver/SX1280.h
+++ b/src/lib/SX1280Driver/SX1280.h
@@ -24,7 +24,7 @@ class SX1280Driver: public SX12xxDriverCommon
////////////////Configuration Functions/////////////
SX1280Driver();
- bool Begin();
+ bool Begin(uint32_t minimumFrequency, uint32_t maximumFrequency);
void End();
void SetTxIdleMode() { SetMode(SX1280_MODE_FS, SX12XX_Radio_All); }; // set Idle mode used when switching from RX to TX
void Config(uint8_t bw, uint8_t sf, uint8_t cr, uint32_t freq,
@@ -41,7 +41,7 @@ class SX1280Driver: public SX12xxDriverCommon
bool FrequencyErrorAvailable() const { return modeSupportsFei && (LastPacketSNRRaw > 0); }
void TXnb(uint8_t * data, uint8_t size, SX12XX_Radio_Number_t radioNumber);
- void RXnb(SX1280_RadioOperatingModes_t rxMode = SX1280_MODE_RX);
+ void RXnb(SX1280_RadioOperatingModes_t rxMode = SX1280_MODE_RX, uint32_t incomingTimeout = 0);
uint16_t GetIrqStatus(SX12XX_Radio_Number_t radioNumber);
void ClearIrqStatus(uint16_t irqMask, SX12XX_Radio_Number_t radioNumber);
@@ -55,15 +55,16 @@ class SX1280Driver: public SX12xxDriverCommon
private:
// constant used for no power change pending
// must not be a valid power register value
- static const uint8_t PWRPENDING_NONE = 0xff;
+ static const uint8_t PWRPENDING_NONE = 0x7f;
SX1280_RadioOperatingModes_t currOpmode;
uint8_t packet_mode;
bool modeSupportsFei;
uint8_t pwrCurrent;
uint8_t pwrPending;
+ SX1280_RadioOperatingModes_t fallBackMode;
- void SetMode(SX1280_RadioOperatingModes_t OPmode, SX12XX_Radio_Number_t radioNumber);
+ void SetMode(SX1280_RadioOperatingModes_t OPmode, SX12XX_Radio_Number_t radioNumber, uint32_t incomingTimeout = 0);
void SetFIFOaddr(uint8_t txBaseAddr, uint8_t rxBaseAddr);
// LoRa functions
diff --git a/src/lib/SX1280Driver/SX1280_hal.cpp b/src/lib/SX1280Driver/SX1280_hal.cpp
index 301db9ab4a..a5949f2bc8 100644
--- a/src/lib/SX1280Driver/SX1280_hal.cpp
+++ b/src/lib/SX1280Driver/SX1280_hal.cpp
@@ -19,7 +19,7 @@ Modified and adapted by Alessandro Carcione for ELRS project
#ifndef UNIT_TEST
#include "SX1280_Regs.h"
#include "SX1280_hal.h"
-#include
+#include
#include "logging.h"
SX1280Hal *SX1280Hal::instance = NULL;
@@ -36,7 +36,7 @@ void SX1280Hal::end()
{
detachInterrupt(GPIO_PIN_DIO1_2);
}
- SPI.end();
+ SPIEx.end();
IsrCallback_1 = nullptr; // remove callbacks
IsrCallback_2 = nullptr; // remove callbacks
}
@@ -62,34 +62,35 @@ void SX1280Hal::init()
pinMode(GPIO_PIN_NSS, OUTPUT);
digitalWrite(GPIO_PIN_NSS, HIGH);
+
+#ifdef PLATFORM_ESP32
+ SPIEx.begin(GPIO_PIN_SCK, GPIO_PIN_MISO, GPIO_PIN_MOSI, GPIO_PIN_NSS); // sck, miso, mosi, ss (ss can be any GPIO)
+ gpio_pullup_en((gpio_num_t)GPIO_PIN_MISO);
+ SPIEx.setFrequency(17500000);
+ SPIEx.setHwCs(true);
if (GPIO_PIN_NSS_2 != UNDEF_PIN)
{
pinMode(GPIO_PIN_NSS_2, OUTPUT);
digitalWrite(GPIO_PIN_NSS_2, HIGH);
+ spiAttachSS(SPIEx.bus(), 1, GPIO_PIN_NSS_2);
}
-
-#ifdef PLATFORM_ESP32
- SPI.begin(GPIO_PIN_SCK, GPIO_PIN_MISO, GPIO_PIN_MOSI, GPIO_PIN_NSS); // sck, miso, mosi, ss (ss can be any GPIO)
- gpio_pullup_en((gpio_num_t)GPIO_PIN_MISO);
- SPI.setFrequency(10000000);
- SPI.setHwCs(true);
- if (GPIO_PIN_NSS_2 != UNDEF_PIN) spiAttachSS(SPI.bus(), 1, GPIO_PIN_NSS_2);
- spiEnableSSPins(SPI.bus(), SX12XX_Radio_All);
+ spiEnableSSPins(SPIEx.bus(), SX12XX_Radio_All);
#elif defined(PLATFORM_ESP8266)
DBGLN("PLATFORM_ESP8266");
- SPI.begin();
- SPI.setBitOrder(MSBFIRST);
- SPI.setDataMode(SPI_MODE0);
- SPI.setFrequency(10000000);
+ SPIEx.begin();
+ SPIEx.setHwCs(true);
+ SPIEx.setBitOrder(MSBFIRST);
+ SPIEx.setDataMode(SPI_MODE0);
+ SPIEx.setFrequency(17500000);
#elif defined(PLATFORM_STM32)
DBGLN("Config SPI");
- SPI.setMOSI(GPIO_PIN_MOSI);
- SPI.setMISO(GPIO_PIN_MISO);
- SPI.setSCLK(GPIO_PIN_SCK);
- SPI.setBitOrder(MSBFIRST);
- SPI.setDataMode(SPI_MODE0);
- SPI.begin();
- SPI.setClockDivider(SPI_CLOCK_DIV4); // 72 / 8 = 9 MHz
+ SPIEx.setBitOrder(MSBFIRST);
+ SPIEx.setDataMode(SPI_MODE0);
+ SPIEx.setMOSI(GPIO_PIN_MOSI);
+ SPIEx.setMISO(GPIO_PIN_MISO);
+ SPIEx.setSCLK(GPIO_PIN_SCK);
+ SPIEx.begin();
+ SPIEx.setClockDivider(SPI_CLOCK_DIV4); // 72 / 8 = 9 MHz
#endif
//attachInterrupt(digitalPinToInterrupt(GPIO_PIN_BUSY), this->busyISR, CHANGE); //not used atm
@@ -100,17 +101,6 @@ void SX1280Hal::init()
}
}
-void ICACHE_RAM_ATTR SX1280Hal::setNss(uint8_t radioNumber, bool state)
-{
- #if defined(PLATFORM_ESP32)
- spiDisableSSPins(SPI.bus(), ~radioNumber);
- spiEnableSSPins(SPI.bus(), radioNumber);
- #else
- if (radioNumber & SX12XX_Radio_1) digitalWrite(GPIO_PIN_NSS, state);
- if (GPIO_PIN_NSS_2 != UNDEF_PIN && radioNumber & SX12XX_Radio_2) digitalWrite(GPIO_PIN_NSS_2, state);
- #endif
-}
-
void SX1280Hal::reset(void)
{
DBGLN("SX1280 Reset");
@@ -119,8 +109,17 @@ void SX1280Hal::reset(void)
{
pinMode(GPIO_PIN_RST, OUTPUT);
digitalWrite(GPIO_PIN_RST, LOW);
+ if (GPIO_PIN_RST_2 != UNDEF_PIN)
+ {
+ pinMode(GPIO_PIN_RST_2, OUTPUT);
+ digitalWrite(GPIO_PIN_RST_2, LOW);
+ }
delay(50);
digitalWrite(GPIO_PIN_RST, HIGH);
+ if (GPIO_PIN_RST_2 != UNDEF_PIN)
+ {
+ digitalWrite(GPIO_PIN_RST_2, HIGH);
+ }
delay(50); // Safety buffer. Busy takes longer to go low than the 1ms timeout in WaitOnBusy().
}
@@ -138,60 +137,53 @@ void ICACHE_RAM_ATTR SX1280Hal::WriteCommand(SX1280_RadioCommands_t command, uin
void ICACHE_RAM_ATTR SX1280Hal::WriteCommand(SX1280_RadioCommands_t command, uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber, uint32_t busyDelay)
{
- WORD_ALIGNED_ATTR uint8_t OutBuffer[size + 1];
+ WORD_ALIGNED_ATTR uint8_t OutBuffer[WORD_PADDED(size + 1)] = {
+ command,
+ };
- OutBuffer[0] = (uint8_t)command;
memcpy(OutBuffer + 1, buffer, size);
WaitOnBusy(radioNumber);
- setNss(radioNumber, LOW);
- SPI.transfer(OutBuffer, (uint8_t)sizeof(OutBuffer));
- setNss(radioNumber, HIGH);
+ SPIEx.write(radioNumber, OutBuffer, size + 1);
BusyDelay(busyDelay);
}
void ICACHE_RAM_ATTR SX1280Hal::ReadCommand(SX1280_RadioCommands_t command, uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber)
{
- WORD_ALIGNED_ATTR uint8_t OutBuffer[size + 2];
- #define RADIO_GET_STATUS_BUF_SIZEOF 3 // special case for command == SX1280_RADIO_GET_STATUS, fixed 3 bytes packet size
+ WORD_ALIGNED_ATTR uint8_t OutBuffer[WORD_PADDED(size + 2)] = {
+ (uint8_t)command,
+ 0x00,
+ 0x00,
+ };
WaitOnBusy(radioNumber);
- setNss(radioNumber, LOW);
if (command == SX1280_RADIO_GET_STATUS)
{
- OutBuffer[0] = (uint8_t)command;
- OutBuffer[1] = 0x00;
- OutBuffer[2] = 0x00;
- SPI.transfer(OutBuffer, RADIO_GET_STATUS_BUF_SIZEOF);
+ const auto RADIO_GET_STATUS_BUF_SIZEOF = 3; // special case for command == SX1280_RADIO_GET_STATUS, fixed 3 bytes packet size
+ SPIEx.read(radioNumber, OutBuffer, RADIO_GET_STATUS_BUF_SIZEOF);
buffer[0] = OutBuffer[0];
}
else
{
- OutBuffer[0] = (uint8_t)command;
- OutBuffer[1] = 0x00;
- memcpy(OutBuffer + 2, buffer, size);
- SPI.transfer(OutBuffer, sizeof(OutBuffer));
+ SPIEx.read(radioNumber, OutBuffer, size + 2); // first 2 bytes returned are status!
memcpy(buffer, OutBuffer + 2, size);
}
- setNss(radioNumber, HIGH);
}
void ICACHE_RAM_ATTR SX1280Hal::WriteRegister(uint16_t address, uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber)
{
- WORD_ALIGNED_ATTR uint8_t OutBuffer[size + 3];
-
- OutBuffer[0] = (SX1280_RADIO_WRITE_REGISTER);
- OutBuffer[1] = ((address & 0xFF00) >> 8);
- OutBuffer[2] = (address & 0x00FF);
+ WORD_ALIGNED_ATTR uint8_t OutBuffer[WORD_PADDED(size + 3)] = {
+ SX1280_RADIO_WRITE_REGISTER,
+ (uint8_t)((address & 0xFF00) >> 8),
+ (uint8_t)(address & 0x00FF),
+ };
memcpy(OutBuffer + 3, buffer, size);
WaitOnBusy(radioNumber);
- setNss(radioNumber, LOW);
- SPI.transfer(OutBuffer, (uint8_t)sizeof(OutBuffer));
- setNss(radioNumber, HIGH);
+ SPIEx.write(radioNumber, OutBuffer, size + 3);
BusyDelay(15);
}
@@ -203,20 +195,17 @@ void ICACHE_RAM_ATTR SX1280Hal::WriteRegister(uint16_t address, uint8_t value, S
void ICACHE_RAM_ATTR SX1280Hal::ReadRegister(uint16_t address, uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber)
{
- WORD_ALIGNED_ATTR uint8_t OutBuffer[size + 4];
-
- OutBuffer[0] = (SX1280_RADIO_READ_REGISTER);
- OutBuffer[1] = ((address & 0xFF00) >> 8);
- OutBuffer[2] = (address & 0x00FF);
- OutBuffer[3] = 0x00;
+ WORD_ALIGNED_ATTR uint8_t OutBuffer[WORD_PADDED(size + 4)] = {
+ SX1280_RADIO_READ_REGISTER,
+ (uint8_t)((address & 0xFF00) >> 8),
+ (uint8_t)(address & 0x00FF),
+ 0x00,
+ };
WaitOnBusy(radioNumber);
- setNss(radioNumber, LOW);
- SPI.transfer(OutBuffer, uint8_t(sizeof(OutBuffer)));
+ SPIEx.read(radioNumber, OutBuffer, size + 4);
memcpy(buffer, OutBuffer + 4, size);
-
- setNss(radioNumber, HIGH);
}
uint8_t ICACHE_RAM_ATTR SX1280Hal::ReadRegister(uint16_t address, SX12XX_Radio_Number_t radioNumber)
@@ -228,50 +217,33 @@ uint8_t ICACHE_RAM_ATTR SX1280Hal::ReadRegister(uint16_t address, SX12XX_Radio_N
void ICACHE_RAM_ATTR SX1280Hal::WriteBuffer(uint8_t offset, uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber)
{
- uint8_t localbuf[size];
-
- for (int i = 0; i < size; i++) // todo check if this is the right want to handle volatiles
- {
- localbuf[i] = buffer[i];
- }
+ WORD_ALIGNED_ATTR uint8_t OutBuffer[WORD_PADDED(size + 2)] = {
+ SX1280_RADIO_WRITE_BUFFER,
+ offset
+ };
- WORD_ALIGNED_ATTR uint8_t OutBuffer[size + 2];
-
- OutBuffer[0] = SX1280_RADIO_WRITE_BUFFER;
- OutBuffer[1] = offset;
-
- memcpy(OutBuffer + 2, localbuf, size);
+ memcpy(OutBuffer + 2, buffer, size);
WaitOnBusy(radioNumber);
- setNss(radioNumber, LOW);
- SPI.transfer(OutBuffer, (uint8_t)sizeof(OutBuffer));
- setNss(radioNumber, HIGH);
+ SPIEx.write(radioNumber, OutBuffer, size + 2);
BusyDelay(15);
}
void ICACHE_RAM_ATTR SX1280Hal::ReadBuffer(uint8_t offset, uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber)
{
- WORD_ALIGNED_ATTR uint8_t OutBuffer[size + 3];
- uint8_t localbuf[size];
-
- OutBuffer[0] = SX1280_RADIO_READ_BUFFER;
- OutBuffer[1] = offset;
- OutBuffer[2] = 0x00;
+ WORD_ALIGNED_ATTR uint8_t OutBuffer[WORD_PADDED(size + 3)] = {
+ SX1280_RADIO_READ_BUFFER,
+ offset,
+ 0x00
+ };
WaitOnBusy(radioNumber);
- setNss(radioNumber, LOW);
- SPI.transfer(OutBuffer, uint8_t(sizeof(OutBuffer)));
- setNss(radioNumber, HIGH);
+ SPIEx.read(radioNumber, OutBuffer, size + 3);
- memcpy(localbuf, OutBuffer + 3, size);
-
- for (int i = 0; i < size; i++) // todo check if this is the right wany to handle volatiles
- {
- buffer[i] = localbuf[i];
- }
+ memcpy(buffer, OutBuffer + 3, size);
}
bool ICACHE_RAM_ATTR SX1280Hal::WaitOnBusy(SX12XX_Radio_Number_t radioNumber)
@@ -287,9 +259,9 @@ bool ICACHE_RAM_ATTR SX1280Hal::WaitOnBusy(SX12XX_Radio_Number_t radioNumber)
{
if (digitalRead(GPIO_PIN_BUSY) == LOW) return true;
}
- else if (GPIO_PIN_BUSY_2 != UNDEF_PIN && radioNumber == SX12XX_Radio_2)
+ else if (radioNumber == SX12XX_Radio_2)
{
- if (digitalRead(GPIO_PIN_BUSY_2) == LOW) return true;
+ if (GPIO_PIN_BUSY_2 == UNDEF_PIN || digitalRead(GPIO_PIN_BUSY_2) == LOW) return true;
}
else if (radioNumber == SX12XX_Radio_All)
{
diff --git a/src/lib/SX1280Driver/SX1280_hal.h b/src/lib/SX1280Driver/SX1280_hal.h
index 65d2db9e74..df4b99b4da 100644
--- a/src/lib/SX1280Driver/SX1280_hal.h
+++ b/src/lib/SX1280Driver/SX1280_hal.h
@@ -38,8 +38,6 @@ class SX1280Hal
void end();
void reset();
- void ICACHE_RAM_ATTR setNss(uint8_t radioNumber, bool state);
-
void ICACHE_RAM_ATTR WriteCommand(SX1280_RadioCommands_t command, uint8_t val, SX12XX_Radio_Number_t radioNumber, uint32_t busyDelay = 15);
void ICACHE_RAM_ATTR WriteCommand(SX1280_RadioCommands_t opcode, uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber, uint32_t busyDelay = 15);
void ICACHE_RAM_ATTR WriteRegister(uint16_t address, uint8_t *buffer, uint8_t size, SX12XX_Radio_Number_t radioNumber);
diff --git a/src/lib/SX12xxDriverCommon/SX12xxDriverCommon.h b/src/lib/SX12xxDriverCommon/SX12xxDriverCommon.h
index 1e4f4520c4..8073d2c01b 100644
--- a/src/lib/SX12xxDriverCommon/SX12xxDriverCommon.h
+++ b/src/lib/SX12xxDriverCommon/SX12xxDriverCommon.h
@@ -56,6 +56,7 @@ class SX12xxDriverCommon
int8_t FuzzySNRThreshold;
bool isFirstRxIrq = true;
+ bool ignoreSecondIRQ = false;
#if defined(DEBUG_RCVR_SIGNAL_STATS)
typedef struct rxSignalStats_s
@@ -80,7 +81,6 @@ class SX12xxDriverCommon
TXdoneCallback = nullCallbackTx;
}
-
/**
* @brief Calculates the reported SNR value using a fuzzy logic approach
*
@@ -88,26 +88,40 @@ class SX12xxDriverCommon
* The reported SNR is determined by a smooth transition between the lower value of the two input SNRs and their average.
* The transition is controlled by the difference between the input SNRs and the threshold value.
*
- * The sigmoid function is used to create a smooth S-shaped curve that maps the difference between the input SNRs
- * to a value between 0 and 1. This value is then used to interpolate between the lower value and the average value,
- * providing a smooth transition between the two conditions.
+ * A polynomial approximation is used to create a smooth S-shaped curve that maps the difference between the input SNRs
+ * to a value between 0 and 1. This approximation enables faster computation while maintaining a similar transition effect.
+ * This value is then used to interpolate between the lower value and the average value, providing a smooth transition between the two conditions.
*
* @param snr1 The first SNR value
* @param snr2 The second SNR value
* @param threshold The threshold value to control the transition between the lower value and the average value reporting strategy
* @return The reported SNR value, which is either the lower value of the two input SNRs, their average, or a value in between, depending on the difference between the input SNRs and the threshold value
*/
- int8_t fuzzy_snr(int8_t snr1, int8_t snr2, int8_t threshold)
+ int8_t fuzzy_snr(int16_t snr1, int16_t snr2, int16_t threshold)
{
- double diff = fabs(snr1 - snr2);
- double lower_value = fmin(snr1, snr2);
- double average_value = ((double)snr1 + snr2) / 2;
-
- // Map the difference to a value between 0 and 1, using sigmoid function
- // Scale and shift the sigmoid curve to cover the desired transition range
- double transition_value = 1.0 / (1.0 + exp(-1.0*((diff - threshold) * 10 / threshold)));
-
- // Interpolate between lower_value and average_value using the transition_value, then round to the nearest int
- return (int8_t)round(lower_value * (1.0 - transition_value) + average_value * transition_value);
+ if (threshold == 0)
+ {
+ return (int8_t)(snr1 < snr2 ? snr1 : snr2);
+ }
+
+ int16_t diff = (int16_t)abs(snr1 - snr2) << 4;
+ int16_t lower_value = (int16_t)(snr1 < snr2 ? snr1 : snr2) << 4;
+ int16_t average_value = ((int16_t)snr1 + snr2) << 3;
+
+ int16_t threshold_scaled = (int16_t)threshold << 4;
+
+ if (diff < threshold_scaled)
+ {
+ return (int8_t)(lower_value >> 4);
+ }
+ else if (diff > threshold_scaled * 2)
+ {
+ return (int8_t)(average_value >> 4);
+ }
+ else
+ {
+ int16_t transition_value = (diff - threshold_scaled) / threshold_scaled;
+ return (int8_t)((lower_value * (16 - transition_value) + average_value * transition_value) >> 8);
+ }
}
-};
+};
\ No newline at end of file
diff --git a/src/lib/ServoOutput/DShotRMT.cpp b/src/lib/ServoOutput/DShotRMT.cpp
new file mode 100644
index 0000000000..e64ea915f7
--- /dev/null
+++ b/src/lib/ServoOutput/DShotRMT.cpp
@@ -0,0 +1,198 @@
+#if (defined(GPIO_PIN_PWM_OUTPUTS) && defined(PLATFORM_ESP32))
+//
+// Name: DShotRMT.cpp
+// Created: 20.03.2021 00:49:15
+// Author: derdoktor667
+//
+
+#include "DShotRMT.h"
+
+DShotRMT::DShotRMT(gpio_num_t gpio, rmt_channel_t rmtChannel) : gpio_num(gpio), rmt_channel(rmtChannel) {
+ // ...create clean packet
+ encode_dshot_to_rmt(DSHOT_NULL_PACKET);
+}
+
+DShotRMT::~DShotRMT() {
+ rmt_tx_stop(rmt_channel);
+ rmt_driver_uninstall(rmt_channel);
+}
+
+bool DShotRMT::begin(dshot_mode_t dshot_mode, bool is_bidirectional) {
+ mode = dshot_mode;
+ bidirectional = is_bidirectional;
+
+ uint16_t ticks_per_bit;
+
+ switch (mode) {
+ case DSHOT150:
+ ticks_per_bit = 64; // ...Bit Period Time 6.67 us
+ ticks_zero_high = 24; // ...zero time 2.50 us
+ ticks_one_high = 48; // ...one time 5.00 us
+ break;
+
+ case DSHOT300:
+ ticks_per_bit = 32; // ...Bit Period Time 3.33 us
+ ticks_zero_high = 12; // ...zero time 1.25 us
+ ticks_one_high = 24; // ...one time 2.50 us
+ break;
+
+ case DSHOT600:
+ ticks_per_bit = 16; // ...Bit Period Time 1.67 us
+ ticks_zero_high = 6; // ...zero time 0.625 us
+ ticks_one_high = 12; // ...one time 1.25 us
+ break;
+
+ case DSHOT1200:
+ ticks_per_bit = 8; // ...Bit Period Time 0.83 us
+ ticks_zero_high = 3; // ...zero time 0.313 us
+ ticks_one_high = 6; // ...one time 0.625 us
+ break;
+
+ // ...because having a default is "good style"
+ default:
+ ticks_per_bit = 0; // ...Bit Period Time endless
+ ticks_zero_high = 0; // ...no bits, no time
+ ticks_one_high = 0; // ......no bits, no time
+ break;
+ }
+
+ // ...calc low signal timing
+ ticks_zero_low = (ticks_per_bit - ticks_zero_high);
+ ticks_one_low = (ticks_per_bit - ticks_one_high);
+
+ rmt_config_t dshot_tx_rmt_config = {
+ .rmt_mode = RMT_MODE_TX,
+ .channel = rmt_channel,
+ .gpio_num = gpio_num,
+ .clk_div = DSHOT_CLK_DIVIDER,
+ .mem_block_num = uint8_t(RMT_CHANNEL_MAX - uint8_t(rmt_channel)),
+ .tx_config = {
+ .idle_level = bidirectional ? RMT_IDLE_LEVEL_HIGH : RMT_IDLE_LEVEL_LOW,
+ .carrier_en = false,
+ .loop_en = true,
+ .idle_output_en = true,
+ },
+ };
+
+ // ...pause "bit" added to each frame
+ if (bidirectional) {
+ dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level0 = HIGH;
+ dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level1 = HIGH;
+ } else {
+ dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level0 = LOW;
+ dshot_tx_rmt_item[DSHOT_PAUSE_BIT].level1 = LOW;
+ }
+
+ dshot_tx_rmt_item[DSHOT_PAUSE_BIT].duration1 = 0;
+ dshot_tx_rmt_item[DSHOT_PAUSE_BIT].duration0 = 10000 - (16*ticks_per_bit) - 1;
+
+ // setup the RMT end marker
+ dshot_tx_rmt_item[DSHOT_PACKET_LENGTH-1].duration0 = 0;
+ dshot_tx_rmt_item[DSHOT_PACKET_LENGTH-1].level0 = HIGH;
+ dshot_tx_rmt_item[DSHOT_PACKET_LENGTH-1].duration1 = 0;
+ dshot_tx_rmt_item[DSHOT_PACKET_LENGTH-1].level1 = LOW;
+
+ // ...setup selected dshot mode
+ rmt_config(&dshot_tx_rmt_config);
+
+ // ...essential step, return the result
+ return rmt_driver_install(dshot_tx_rmt_config.channel, 0, 0);
+}
+
+// ...the config part is done, now the calculating and sending part
+void DShotRMT::send_dshot_value(uint16_t throttle_value, telemetric_request_t telemetric_request) {
+ dshot_packet_t dshot_rmt_packet = { };
+
+ if (throttle_value < DSHOT_THROTTLE_MIN) {
+ throttle_value = DSHOT_THROTTLE_MIN;
+ }
+
+ if (throttle_value > DSHOT_THROTTLE_MAX) {
+ throttle_value = DSHOT_THROTTLE_MAX;
+ }
+
+ // ...packets are the same for bidirectional mode
+ dshot_rmt_packet.throttle_value = throttle_value;
+ dshot_rmt_packet.telemetric_request = telemetric_request;
+ dshot_rmt_packet.checksum = this->calc_dshot_chksum(dshot_rmt_packet);
+
+ output_rmt_data(dshot_rmt_packet);
+}
+
+rmt_item32_t* DShotRMT::encode_dshot_to_rmt(uint16_t parsed_packet) {
+ // ...is bidirecional mode activated
+ if (bidirectional) {
+ // ..."invert" the signal duration
+ for (int i = 0; i < DSHOT_PAUSE_BIT; i++, parsed_packet <<= 1) {
+ if (parsed_packet & 0b1000000000000000) {
+ // set one
+ dshot_tx_rmt_item[i].duration0 = ticks_one_low;
+ dshot_tx_rmt_item[i].duration1 = ticks_one_high;
+ }
+ else {
+ // set zero
+ dshot_tx_rmt_item[i].duration0 = ticks_zero_low;
+ dshot_tx_rmt_item[i].duration1 = ticks_zero_high;
+ }
+
+ dshot_tx_rmt_item[i].level0 = LOW;
+ dshot_tx_rmt_item[i].level1 = HIGH;
+ }
+ }
+
+ // ..."normal" DShot mode / "bidirectional" mode OFF
+ else {
+ for (int i = 0; i < DSHOT_PAUSE_BIT; i++, parsed_packet <<= 1) {
+ if (parsed_packet & 0b1000000000000000) {
+ // set one
+ dshot_tx_rmt_item[i].duration0 = ticks_one_high;
+ dshot_tx_rmt_item[i].duration1 = ticks_one_low;
+ }
+ else {
+ // set zero
+ dshot_tx_rmt_item[i].duration0 = ticks_zero_high;
+ dshot_tx_rmt_item[i].duration1 = ticks_zero_low;
+ }
+
+ dshot_tx_rmt_item[i].level0 = HIGH;
+ dshot_tx_rmt_item[i].level1 = LOW;
+ }
+ }
+
+ return dshot_tx_rmt_item;
+}
+
+// ...just returns the checksum
+// DOES NOT APPEND CHECKSUM!!!
+uint16_t DShotRMT::calc_dshot_chksum(const dshot_packet_t& dshot_packet) {
+ // ...same initial 12bit data for bidirectional or "normal" mode
+ uint16_t packet = (dshot_packet.throttle_value << 1) | dshot_packet.telemetric_request;
+
+ if (bidirectional) {
+ // ...calc the checksum "inverted" / bidirectional mode
+ return (~(packet ^ (packet >> 4) ^ (packet >> 8))) & 0x0F;
+ } else {
+ // ...calc the checksum "normal" mode
+ return (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F;
+ }
+}
+
+uint16_t DShotRMT::prepare_rmt_data(const dshot_packet_t& dshot_packet) {
+ auto chksum = calc_dshot_chksum(dshot_packet);
+
+ // ..."construct" the packet
+ uint16_t prepared_to_encode = (dshot_packet.throttle_value << 1) | dshot_packet.telemetric_request;
+ prepared_to_encode = (prepared_to_encode << 4) | chksum;
+
+ return prepared_to_encode;
+}
+
+// ...finally output using ESP32 RMT
+void DShotRMT::output_rmt_data(const dshot_packet_t& dshot_packet) {
+ encode_dshot_to_rmt(prepare_rmt_data(dshot_packet));
+
+ rmt_tx_stop(rmt_channel);
+ rmt_fill_tx_items(rmt_channel, dshot_tx_rmt_item, DSHOT_PACKET_LENGTH, 0);
+ rmt_tx_start(rmt_channel, true);
+}
+#endif
\ No newline at end of file
diff --git a/src/lib/ServoOutput/DShotRMT.h b/src/lib/ServoOutput/DShotRMT.h
new file mode 100644
index 0000000000..5c48521178
--- /dev/null
+++ b/src/lib/ServoOutput/DShotRMT.h
@@ -0,0 +1,118 @@
+#if (defined(GPIO_PIN_PWM_OUTPUTS) && defined(PLATFORM_ESP32))
+//
+// Name: DShotRMT.h
+// Created: 20.03.2021 00:49:15
+// Author: derdoktor667
+//
+
+#pragma once
+
+#include
+// #include "BlheliCmdMap.h"
+// Added back to DShotRMT.h
+
+// ...utilizing the IR Module library for generating the DShot signal
+#include
+
+constexpr auto DSHOT_CLK_DIVIDER = 8; // ...slow down RMT clock to 0.1 microseconds / 100 nanoseconds per cycle
+constexpr auto DSHOT_PACKET_LENGTH = 18; // ...last packet is the pause followed by RMT end marker
+
+constexpr auto DSHOT_THROTTLE_MIN = 48;
+constexpr auto DSHOT_THROTTLE_MAX = 2047;
+constexpr auto DSHOT_NULL_PACKET = 0b0000000000000000;
+
+constexpr auto DSHOT_PAUSE = 21; // ...21bit is recommended, but to be sure
+constexpr auto DSHOT_PAUSE_BIT = 16;
+
+constexpr auto F_CPU_RMT = APB_CLK_FREQ;
+constexpr auto RMT_CYCLES_PER_SEC = (F_CPU_RMT / DSHOT_CLK_DIVIDER);
+constexpr auto RMT_CYCLES_PER_ESP_CYCLE = (F_CPU / RMT_CYCLES_PER_SEC);
+
+// Source: https://github.com/bitdump/BLHeli/blob/master/BLHeli_S%20SiLabs/Dshotprog%20spec%20BLHeli_S.txt
+// Date: 04.07.2021
+
+enum dshot_cmd_t {
+ DSHOT_CMD_MOTOR_STOP, // Currently not implemented - STOP Motors
+ DSHOT_CMD_BEEP1, // Wait at least length of beep (380ms) before next command
+ DSHOT_CMD_BEEP2, // Wait at least length of beep (380ms) before next command
+ DSHOT_CMD_BEEP3, // Wait at least length of beep (400ms) before next command
+ DSHOT_CMD_BEEP4, // Wait at least length of beep (400ms) before next command
+ DSHOT_CMD_BEEP5, // Wait at least length of beep (400ms) before next command
+ DSHOT_CMD_ESC_INFO, // Currently not implemented
+ DSHOT_CMD_SPIN_DIRECTION_1, // Need 6x, no wait required
+ DSHOT_CMD_SPIN_DIRECTION_2, // Need 6x, no wait required
+ DSHOT_CMD_3D_MODE_OFF, // Need 6x, no wait required
+ DSHOT_CMD_3D_MODE_ON, // Need 6x, no wait required
+ DSHOT_CMD_SETTINGS_REQUEST, // Currently not implemented
+ DSHOT_CMD_SAVE_SETTINGS, // Need 6x, wait at least 12ms before next command
+ DSHOT_CMD_SPIN_DIRECTION_NORMAL, // Need 6x, no wait required
+ DSHOT_CMD_SPIN_DIRECTION_REVERSED, // Need 6x, no wait required
+ DSHOT_CMD_LED0_ON, // Currently not implemented
+ DSHOT_CMD_LED1_ON, // Currently not implemented
+ DSHOT_CMD_LED2_ON, // Currently not implemented
+ DSHOT_CMD_LED3_ON, // Currently not implemented
+ DSHOT_CMD_LED0_OFF, // Currently not implemented
+ DSHOT_CMD_LED1_OFF, // Currently not implemented
+ DSHOT_CMD_LED2_OFF, // Currently not implemented
+ DSHOT_CMD_LED3_OFF, // Currently not implemented
+ DSHOT_CMD_MAX = 47
+};
+
+typedef enum dshot_mode_e {
+ DSHOT_OFF,
+ DSHOT150,
+ DSHOT300,
+ DSHOT600,
+ DSHOT1200
+} dshot_mode_t;
+
+typedef enum telemetric_request_e {
+ NO_TELEMETRIC,
+ ENABLE_TELEMETRIC,
+} telemetric_request_t;
+
+// ...set bitcount for DShot packet
+typedef struct dshot_packet_s {
+ uint16_t throttle_value : 11;
+ telemetric_request_t telemetric_request : 1;
+ uint16_t checksum : 4;
+} dshot_packet_t;
+
+// ...set bitcount for eRPM packet
+typedef struct eRPM_packet_s {
+ uint16_t eRPM_data : 12;
+ uint8_t checksum : 4;
+} eRPM_packet_t;
+
+// ...all settings for the dshot mode
+typedef struct dshot_config_s {
+} dshot_config_t;
+
+class DShotRMT {
+public:
+ DShotRMT(gpio_num_t gpio, rmt_channel_t rmtChannel);
+ ~DShotRMT();
+
+ // ...safety first ...no parameters, no DShot
+ bool begin(dshot_mode_t dshot_mode = DSHOT_OFF, bool is_bidirectional = false);
+ void send_dshot_value(uint16_t throttle_value, telemetric_request_t telemetric_request = NO_TELEMETRIC);
+
+private:
+ gpio_num_t gpio_num;
+ rmt_channel_t rmt_channel;
+ rmt_item32_t dshot_tx_rmt_item[DSHOT_PACKET_LENGTH + 1];
+
+ dshot_mode_t mode = DSHOT_OFF;
+ bool bidirectional = false;
+ uint16_t ticks_zero_high = 0;
+ uint16_t ticks_zero_low = 0;
+ uint16_t ticks_one_high = 0;
+ uint16_t ticks_one_low = 0;
+
+ rmt_item32_t* encode_dshot_to_rmt(uint16_t parsed_packet);
+ uint16_t calc_dshot_chksum(const dshot_packet_t& dshot_packet);
+ uint16_t prepare_rmt_data(const dshot_packet_t& dshot_packet);
+
+ void output_rmt_data(const dshot_packet_t& dshot_packet);
+};
+#endif
\ No newline at end of file
diff --git a/src/lib/ServoOutput/ServoMgr.cpp b/src/lib/ServoOutput/ServoMgr.cpp
deleted file mode 100644
index 31e61c5aa1..0000000000
--- a/src/lib/ServoOutput/ServoMgr.cpp
+++ /dev/null
@@ -1,197 +0,0 @@
-#if defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32)
-
-#include "ServoMgr.h"
-#include "logging.h"
-#include "waveform_8266.h"
-#include
-
-ServoMgr::ServoMgr(const uint8_t *const pins, const uint8_t outputCnt, uint32_t defaultInterval)
- : _pins(pins), _outputCnt(outputCnt), _refreshInterval(new uint16_t[outputCnt]), _activePwmChannels(0), _resolution_bits(new uint8_t[outputCnt])
-{
- for (uint8_t ch = 0; ch < _outputCnt; ++ch)
- {
- _refreshInterval[ch] = defaultInterval;
- }
-}
-
-#if defined(PLATFORM_ESP32)
-#include "driver/ledc.h"
-
-#ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK
-#define LEDC_DEFAULT_CLK LEDC_USE_XTAL_CLK
-#else
-#define LEDC_DEFAULT_CLK LEDC_AUTO_CLK
-#endif
-
-extern uint8_t channels_resolution[];
-
-/*
- * Modified versions of the ledcSetup/ledcAttachPin from Arduino ESP32 Hal which allows
- * control of the timer to use rather than being directly associated with the channel. This
- * allows multiple channels to use the same timer within a LEDC group (high/low speed) when
- * they are set to the same frequency.
- */
-static void ledcSetupEx(uint8_t chan, ledc_timer_t timer, uint32_t freq, uint8_t bit_num)
-{
- ledc_mode_t group = (ledc_mode_t)(chan / 8);
-
- ledc_timer_config_t ledc_timer = {
- .speed_mode = group,
- .duty_resolution = (ledc_timer_bit_t)bit_num,
- .timer_num = timer,
- .freq_hz = freq,
- .clk_cfg = LEDC_DEFAULT_CLK};
- if (ledc_timer_config(&ledc_timer) != ESP_OK)
- {
- log_e("ledc setup failed!");
- return;
- }
- channels_resolution[chan] = bit_num;
-}
-
-static void ledcAttachPinEx(uint8_t pin, uint8_t chan, ledc_timer_t timer)
-{
- uint8_t group = (chan / 8), channel = (chan % 8);
-
- ledc_channel_config_t ledc_channel = {
- .gpio_num = pin,
- .speed_mode = (ledc_mode_t)group,
- .channel = (ledc_channel_t)channel,
- .intr_type = LEDC_INTR_DISABLE,
- .timer_sel = (ledc_timer_t)timer,
- .duty = 0,
- .hpoint = 0};
- ledc_channel_config(&ledc_channel);
-}
-
-void ServoMgr::allocateLedcChn(uint8_t ch, uint16_t intervalUs, uint8_t pin)
-{
- uint32_t target_freq = 1000000U / intervalUs;
- _resolution_bits[ch] = (uint16_t)(log2f(80000000.0f / target_freq)); // no matter high speed timer or low speed timer, the clk src can be 80Mhz
- if (_resolution_bits[ch] > 16)
- {
- _resolution_bits[ch] = 16;
- }
-
- uint8_t group = ch / 8;
- for (int i = 0; i <= 3; i++)
- {
- uint8_t timer_idx = group * 4 + i;
- if (_timerConfigs[timer_idx] == 0)
- {
- _timerConfigs[timer_idx] = target_freq;
- ledcSetupEx(ch, (ledc_timer_t)i, target_freq, _resolution_bits[ch]);
- }
- if (_timerConfigs[timer_idx] == target_freq)
- {
- ledcAttachPinEx(pin, ch, (ledc_timer_t)i);
- DBGLN("allocate ledc_ch %d on pin %d using group: %d, ledc_tim: %d, bits: %d", ch, pin, group, i, _resolution_bits[ch]);
- return;
- }
- }
- DBGLN("Could not allocate timer for channel %d", ch);
-}
-#endif
-
-void ServoMgr::initialize()
-{
- for (uint8_t ch = 0; ch < _outputCnt; ++ch)
- {
- const uint8_t pin = _pins[ch];
- if (pin != PIN_DISCONNECTED)
- {
- pinMode(pin, OUTPUT);
- digitalWrite(pin, LOW);
- }
- }
-}
-
-void ServoMgr::writeMicroseconds(uint8_t ch, uint16_t valueUs)
-{
- const uint8_t pin = _pins[ch];
- if (pin == PIN_DISCONNECTED)
- {
- return;
- }
- _activePwmChannels |= (1 << ch);
-#if defined(PLATFORM_ESP32)
- ledcWrite(ch, map(valueUs, 0, _refreshInterval[ch], 0, (1 << _resolution_bits[ch]) - 1));
-#else
- startWaveform8266(pin, valueUs, _refreshInterval[ch] - valueUs);
-#endif
-}
-
-void ServoMgr::writeDuty(uint8_t ch, uint16_t duty)
-{
- const uint8_t pin = _pins[ch];
- if (pin == PIN_DISCONNECTED)
- {
- return;
- }
- _activePwmChannels |= (1 << ch);
-#if defined(PLATFORM_ESP32)
- ledcWrite(ch, map(duty, 0, 1000, 0, (1 << _resolution_bits[ch]) - 1));
-#else
- uint16_t high = map(duty, 0, 1000, 0, _refreshInterval[ch]);
- startWaveform8266(pin, high, _refreshInterval[ch] - high);
-#endif
-}
-
-void ServoMgr::setRefreshInterval(uint8_t ch, uint16_t intervalUs)
-{
- if (intervalUs != 0)
- {
- _refreshInterval[ch] = intervalUs;
-#if defined(PLATFORM_ESP32)
- const uint8_t pin = _pins[ch];
- if (pin == PIN_DISCONNECTED)
- {
- return;
- }
- allocateLedcChn(ch, intervalUs, pin);
-#endif
- }
-}
-
-void ServoMgr::stopPwm(uint8_t ch)
-{
- const uint8_t pin = _pins[ch];
- if (pin == PIN_DISCONNECTED)
- {
- return;
- }
- _activePwmChannels &= ~(1 << ch);
-#if defined(PLATFORM_ESP32)
- ledcDetachPin(pin);
-#else
- stopWaveform8266(pin);
-#endif
- digitalWrite(pin, LOW);
-}
-
-void ServoMgr::stopAllPwm()
-{
- for (uint8_t ch = 0; ch < _outputCnt; ++ch)
- {
- stopPwm(ch);
- }
- _activePwmChannels = 0;
-}
-
-void ServoMgr::writeDigital(uint8_t ch, bool value)
-{
- const uint8_t pin = _pins[ch];
- if (pin == PIN_DISCONNECTED)
- {
- return;
- }
- if (isPwmActive(ch))
- {
- stopPwm(ch);
- // Wait for the last edge, which is at most 1 cycle from now
- delay((_refreshInterval[ch] / 1000U) + 1);
- }
- digitalWrite(pin, value);
-}
-
-#endif
\ No newline at end of file
diff --git a/src/lib/ServoOutput/ServoMgr.h b/src/lib/ServoOutput/ServoMgr.h
deleted file mode 100644
index f1c8d1656a..0000000000
--- a/src/lib/ServoOutput/ServoMgr.h
+++ /dev/null
@@ -1,44 +0,0 @@
-#if defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)
-#pragma once
-
-#include
-class ServoMgr
-{
-public:
- ServoMgr(const uint8_t *const pins, const uint8_t outputCnt, uint32_t defaultInterval = 20000U);
- ~ServoMgr() { delete[] _refreshInterval; }
-
- // Initialize the pins for output
- void initialize();
- // Start/Update PWM by pulse width
- void writeMicroseconds(uint8_t ch, uint16_t valueUs);
- // Start/Update PWM by duty
- void writeDuty(uint8_t ch, uint16_t duty);
- // Stop PWM
- void stopPwm(uint8_t ch);
- // Stop any active PWM channels (and set LOW)
- void stopAllPwm();
- // Set a pin high/low (will stopPwm first if active)
- void writeDigital(uint8_t ch, bool value);
-
- inline uint16_t getRefreshInterval(uint8_t ch) const { return _refreshInterval[ch]; }
- void setRefreshInterval(uint8_t ch, uint16_t intervalUs);
- inline bool isPwmActive(uint8_t ch) const { return _activePwmChannels & (1 << ch); }
- inline bool isAnyPwmActive() const { return _activePwmChannels; }
- inline uint8_t getOutputCnt() const { return _outputCnt; }
-
- static const uint8_t PIN_DISCONNECTED = 0xff;
-
-private:
-#if defined(PLATFORM_ESP32)
- uint32_t _timerConfigs[8] = {0};
- void allocateLedcChn(uint8_t ch, uint16_t intervalUs, uint8_t pin);
-#endif
- const uint8_t *const _pins;
- const uint8_t _outputCnt;
- uint16_t *_refreshInterval;
- uint32_t _activePwmChannels;
- uint8_t *_resolution_bits;
-};
-
-#endif
\ No newline at end of file
diff --git a/src/lib/ServoOutput/devServoOutput.cpp b/src/lib/ServoOutput/devServoOutput.cpp
index ee50d4878b..91f880b919 100644
--- a/src/lib/ServoOutput/devServoOutput.cpp
+++ b/src/lib/ServoOutput/devServoOutput.cpp
@@ -1,41 +1,49 @@
#if defined(GPIO_PIN_PWM_OUTPUTS)
#include "devServoOutput.h"
+#include "PWM.h"
#include "CRSF.h"
#include "config.h"
-#include "helpers.h"
+#include "logging.h"
#include "rxtx_intf.h"
-static uint8_t SERVO_PINS[PWM_MAX_CHANNELS];
-static ServoMgr *servoMgr;
+static int8_t servoPins[PWM_MAX_CHANNELS];
+static pwm_channel_t pwmChannels[PWM_MAX_CHANNELS];
+static uint16_t pwmChannelValues[PWM_MAX_CHANNELS];
+
+#if (defined(PLATFORM_ESP32))
+static DShotRMT *dshotInstances[PWM_MAX_CHANNELS] = {nullptr};
+const uint8_t RMT_MAX_CHANNELS = 8;
+#endif
+
// true when the RX has a new channels packet
static bool newChannelsAvailable;
// Absolute max failsafe time if no update is received, regardless of LQ
static constexpr uint32_t FAILSAFE_ABS_TIMEOUT_MS = 1000U;
-void ICACHE_RAM_ATTR servoNewChannelsAvaliable()
+void ICACHE_RAM_ATTR servoNewChannelsAvailable()
{
newChannelsAvailable = true;
}
-uint16_t servoOutputModeToUs(eServoOutputMode mode)
+uint16_t servoOutputModeToFrequency(eServoOutputMode mode)
{
switch (mode)
{
case som50Hz:
- return (1000000U / 50U);
+ return 50U;
case som60Hz:
- return (1000000U / 60U);
+ return 60U;
case som100Hz:
- return (1000000U / 100U);
+ return 100U;
case som160Hz:
- return (1000000U / 160U);
+ return 160U;
case som333Hz:
- return (1000000U / 333U);
+ return 333U;
case som400Hz:
- return (1000000U / 400U);
+ return 400U;
case som10KHzDuty:
- return (1000000U / 10000U);
+ return 10000U;
default:
return 0;
}
@@ -44,19 +52,31 @@ uint16_t servoOutputModeToUs(eServoOutputMode mode)
static void servoWrite(uint8_t ch, uint16_t us)
{
const rx_config_pwm_t *chConfig = config.GetPwmChannel(ch);
- if ((eServoOutputMode)chConfig->val.mode == somOnOff)
+#if defined(PLATFORM_ESP32)
+ if ((eServoOutputMode)chConfig->val.mode == somDShot)
{
- servoMgr->writeDigital(ch, us > 1500U);
+ // DBGLN("Writing DShot output: us: %u, ch: %d", us, ch);
+ if (dshotInstances[ch])
+ {
+ dshotInstances[ch]->send_dshot_value(((us - 1000) * 2) + 47); // Convert PWM signal in us to DShot value
+ }
}
else
+#endif
+ if (servoPins[ch] != UNDEF_PIN && pwmChannelValues[ch] != us)
{
- if ((eServoOutputMode)chConfig->val.mode == som10KHzDuty)
+ pwmChannelValues[ch] = us;
+ if ((eServoOutputMode)chConfig->val.mode == somOnOff)
{
- servoMgr->writeDuty(ch, constrain(us, 1000, 2000) - 1000);
+ digitalWrite(servoPins[ch], us > 1500);
+ }
+ else if ((eServoOutputMode)chConfig->val.mode == som10KHzDuty)
+ {
+ PWM.setDuty(pwmChannels[ch], constrain(us, 1000, 2000) - 1000);
}
else
{
- servoMgr->writeMicroseconds(ch, us / (chConfig->val.narrow + 1));
+ PWM.setMicroseconds(pwmChannels[ch], us / (chConfig->val.narrow + 1));
}
}
}
@@ -64,29 +84,37 @@ static void servoWrite(uint8_t ch, uint16_t us)
static void servosFailsafe()
{
constexpr unsigned SERVO_FAILSAFE_MIN = 988U;
- for (unsigned ch = 0; ch < servoMgr->getOutputCnt(); ++ch)
+ for (int ch = 0 ; ch < GPIO_PIN_PWM_OUTPUTS_COUNT ; ++ch)
{
const rx_config_pwm_t *chConfig = config.GetPwmChannel(ch);
- // Note: Failsafe values do not respect the inverted flag, failsafes are absolute
- uint16_t us = chConfig->val.failsafe + SERVO_FAILSAFE_MIN;
- // Always write the failsafe position even if the servo never has been started,
- // so all the servos go to their expected position
- servoWrite(ch, us);
+ if (chConfig->val.failsafeMode == PWMFAILSAFE_SET_POSITION) {
+ // Note: Failsafe values do not respect the inverted flag, failsafe values are absolute
+ uint16_t us = chConfig->val.failsafe + SERVO_FAILSAFE_MIN;
+ // Always write the failsafe position even if the servo has never been started,
+ // so all the servos go to their expected position
+ servoWrite(ch, us);
+ }
+ else if (chConfig->val.failsafeMode == PWMFAILSAFE_NO_PULSES) {
+ servoWrite(ch, 0);
+ }
+ else if (chConfig->val.failsafeMode == PWMFAILSAFE_LAST_POSITION) {
+ // do nothing
+ }
}
}
-static int servosUpdate(unsigned long now)
+static void servosUpdate(unsigned long now)
{
static uint32_t lastUpdate;
if (newChannelsAvailable)
{
newChannelsAvailable = false;
lastUpdate = now;
- for (unsigned ch = 0; ch < servoMgr->getOutputCnt(); ++ch)
+ for (int ch = 0 ; ch < GPIO_PIN_PWM_OUTPUTS_COUNT ; ++ch)
{
const rx_config_pwm_t *chConfig = config.GetPwmChannel(ch);
const unsigned crsfVal = ChannelData[chConfig->val.inputChannel];
- // crsfVal might 0 if this is a switch channel and it has not been
+ // crsfVal might 0 if this is a switch channel, and it has not been
// received yet. Delay initializing the servo until the channel is valid
if (crsfVal == 0)
{
@@ -94,7 +122,7 @@ static int servosUpdate(unsigned long now)
}
uint16_t us = CRSF_to_US(crsfVal);
- // Flip the output around the mid value if inverted
+ // Flip the output around the mid-value if inverted
// (1500 - usOutput) + 1500
if (chConfig->val.inverted)
{
@@ -107,14 +135,11 @@ static int servosUpdate(unsigned long now)
// LQ goes to 0 (100 packets missed in a row)
// OR last update older than FAILSAFE_ABS_TIMEOUT_MS
// go to failsafe
- else if (lastUpdate &&
- ((getLq() == 0) || (now - lastUpdate > FAILSAFE_ABS_TIMEOUT_MS)))
+ else if (lastUpdate && ((getLq() == 0) || (now - lastUpdate > FAILSAFE_ABS_TIMEOUT_MS)))
{
servosFailsafe();
lastUpdate = 0;
}
-
- return DURATION_IMMEDIATELY;
}
static void initialize()
@@ -124,52 +149,112 @@ static void initialize()
return;
}
+#if defined(PLATFORM_ESP32)
+ uint8_t rmtCH = 0;
+#endif
for (int ch = 0; ch < GPIO_PIN_PWM_OUTPUTS_COUNT; ++ch)
{
- uint8_t pin = GPIO_PIN_PWM_OUTPUTS[ch];
+ pwmChannelValues[ch] = UINT16_MAX;
+ pwmChannels[ch] = -1;
+ int8_t pin = GPIO_PIN_PWM_OUTPUTS[ch];
#if (defined(DEBUG_LOG) || defined(DEBUG_RCVR_LINKSTATS)) && (defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32))
// Disconnect the debug UART pins if DEBUG_LOG
+#if defined(PLATFORM_ESP32_S3)
+ if (pin == 43 || pin == 44)
+#else
if (pin == 1 || pin == 3)
+#endif
{
- pin = ServoMgr::PIN_DISCONNECTED;
+ pin = UNDEF_PIN;
}
#endif
- // Mark servo pins that are being used for serial as disconnected
- eServoOutputMode mode = (eServoOutputMode)config.GetPwmChannel(ch)->val.mode;
- if (mode == somSerial)
+ // Mark servo pins that are being used for serial (or other purposes) as disconnected
+ auto mode = (eServoOutputMode)config.GetPwmChannel(ch)->val.mode;
+ if (mode >= somSerial)
{
- pin = ServoMgr::PIN_DISCONNECTED;
+ pin = UNDEF_PIN;
}
- SERVO_PINS[ch] = pin;
- }
+#if defined(PLATFORM_ESP32)
+ else if (mode == somDShot)
+ {
+ if (rmtCH < RMT_MAX_CHANNELS)
+ {
+ auto gpio = (gpio_num_t)pin;
+ auto rmtChannel = (rmt_channel_t)rmtCH;
+ DBGLN("Initializing DShot: gpio: %u, ch: %d, rmtChannel: %u", gpio, ch, rmtChannel);
+ pinMode(pin, OUTPUT);
+ dshotInstances[ch] = new DShotRMT(gpio, rmtChannel); // Initialize the DShotRMT instance
+ rmtCH++;
+ }
+ pin = UNDEF_PIN;
+ }
+#endif
+ servoPins[ch] = pin;
+ // Initialize all servos to low ASAP
+ if (pin != UNDEF_PIN)
+ {
+ if (mode == somOnOff)
+ {
+ DBGLN("Initializing digital output: ch: %d, pin: %d", ch, pin);
+ }
+ else
+ {
+ DBGLN("Initializing PWM output: ch: %d, pin: %d", ch, pin);
+ }
- // Initialize all servos to low ASAP
- servoMgr = new ServoMgr(SERVO_PINS, GPIO_PIN_PWM_OUTPUTS_COUNT, 20000U);
- servoMgr->initialize();
+ pinMode(pin, OUTPUT);
+ digitalWrite(pin, LOW);
+ }
+ }
}
static int start()
{
- for (unsigned ch = 0; servoMgr && ch < servoMgr->getOutputCnt(); ++ch)
+ for (int ch = 0; ch < GPIO_PIN_PWM_OUTPUTS_COUNT; ++ch)
{
const rx_config_pwm_t *chConfig = config.GetPwmChannel(ch);
- servoMgr->setRefreshInterval(ch, servoOutputModeToUs((eServoOutputMode)chConfig->val.mode));
+ auto frequency = servoOutputModeToFrequency((eServoOutputMode)chConfig->val.mode);
+ if (frequency && servoPins[ch] != UNDEF_PIN)
+ {
+ pwmChannels[ch] = PWM.allocate(servoPins[ch], frequency);
+ }
+#if defined(PLATFORM_ESP32)
+ else if (((eServoOutputMode)chConfig->val.mode) == somDShot)
+ {
+ dshotInstances[ch]->begin(DSHOT300, false); // Set DShot protocol and bidirectional dshot bool
+ dshotInstances[ch]->send_dshot_value(0); // Set throttle low so the ESC can continue initialsation
+ }
+#endif
}
-
return DURATION_NEVER;
}
static int event()
{
- if (servoMgr == nullptr || connectionState == disconnected)
+ if (!OPT_HAS_SERVO_OUTPUT || connectionState == disconnected)
{
- // Disconnected should come after failsafe on the RX
+ // Disconnected should come after failsafe on the RX,
// so it is safe to shut down when disconnected
return DURATION_NEVER;
}
else if (connectionState == wifiUpdate)
{
- servoMgr->stopAllPwm();
+ for (int ch = 0; ch < GPIO_PIN_PWM_OUTPUTS_COUNT; ++ch)
+ {
+ if (pwmChannels[ch] != -1)
+ {
+ PWM.release(pwmChannels[ch]);
+ pwmChannels[ch] = -1;
+ }
+#if defined(PLATFORM_ESP32)
+ if (dshotInstances[ch] != nullptr)
+ {
+ delete dshotInstances[ch];
+ dshotInstances[ch] = nullptr;
+ }
+#endif
+ servoPins[ch] = UNDEF_PIN;
+ }
return DURATION_NEVER;
}
return DURATION_IMMEDIATELY;
@@ -177,7 +262,8 @@ static int event()
static int timeout()
{
- return servosUpdate(millis());
+ servosUpdate(millis());
+ return DURATION_IMMEDIATELY;
}
device_t ServoOut_device = {
diff --git a/src/lib/ServoOutput/devServoOutput.h b/src/lib/ServoOutput/devServoOutput.h
index ff2aaf0648..ec5ede213c 100644
--- a/src/lib/ServoOutput/devServoOutput.h
+++ b/src/lib/ServoOutput/devServoOutput.h
@@ -1,19 +1,19 @@
#pragma once
#if defined(GPIO_PIN_PWM_OUTPUTS)
-#include "ServoMgr.h"
-
#include "device.h"
#include "common.h"
+#if (defined(PLATFORM_ESP32))
+#include "DShotRMT.h"
+#endif
+
extern device_t ServoOut_device;
#define HAS_SERVO_OUTPUT
#define OPT_HAS_SERVO_OUTPUT (GPIO_PIN_PWM_OUTPUTS_COUNT > 0)
// Notify this unit that new channel data has arrived
-void servoNewChannelsAvaliable();
-// Convert eServoOutputMode to microseconds, or 0 for non-servo modes
-uint16_t servoOutputModeToUs(eServoOutputMode mode);
+void servoNewChannelsAvailable();
#else
-inline void servoNewChannelsAvaliable(){};
+inline void servoNewChannelsAvailable(){};
#endif
diff --git a/src/lib/ServoOutput/waveform_8266.h b/src/lib/ServoOutput/waveform_8266.h
deleted file mode 100644
index e7914bc0b6..0000000000
--- a/src/lib/ServoOutput/waveform_8266.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma once
-
-void startWaveform8266(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS);
-void stopWaveform8266(uint8_t pin);
-
-// This section below is a hack to prevent the use of the core's core_esp8266_waveform functions by
-// overriding their implementations. It seems to work and save about 800 bytes of RAM and 500 bytes of code
-extern "C"
-{
- int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS,
- int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) { return false; };
- int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles,
- int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) { return false; };
- void stopWaveform(uint8_t pin) {};
- void setTimer1Callback(uint32_t (*fn)()) {};
- void _setPWMFreq(uint32_t freq) {};
- void _stopPWM(uint8_t pin) {};
- bool _setPWM(int pin, uint32_t val, uint32_t range) { return false; };
-}
\ No newline at end of file
diff --git a/src/lib/TCPSOCKET/tcpsocket.cpp b/src/lib/TCPSOCKET/tcpsocket.cpp
index 9f7b19944c..4abe0064f2 100644
--- a/src/lib/TCPSOCKET/tcpsocket.cpp
+++ b/src/lib/TCPSOCKET/tcpsocket.cpp
@@ -61,7 +61,7 @@ bool TCPSOCKET::write(uint8_t *data, uint16_t len) // doesn't send, just ques it
return false; // nothing to do
}
- if (FIFOout.free() > (uint32_t)(len + 2))
+ if (FIFOout.available(len + 2))
{
FIFOout.pushSize(len);
FIFOout.pushBytes(data, len);
@@ -93,7 +93,7 @@ void TCPSOCKET::handleDataIn(void *arg, AsyncClient *client, void *data, size_t
instance->TCPclient = client;
instance->clientTimeoutLastData = millis();
- if (instance->FIFOin.free() > (uint32_t)(len + 2)) // +2 because it takes 2 bytes to store the size of the FIFO chunk
+ if (instance->FIFOin.available(len + 2)) // +2 because it takes 2 bytes to store the size of the FIFO chunk
{
instance->FIFOin.pushSize(len);
instance->FIFOin.pushBytes((uint8_t *)data, len);
diff --git a/src/lib/TCPSOCKET/tcpsocket.h b/src/lib/TCPSOCKET/tcpsocket.h
index 0733e7e591..1306378417 100644
--- a/src/lib/TCPSOCKET/tcpsocket.h
+++ b/src/lib/TCPSOCKET/tcpsocket.h
@@ -4,7 +4,7 @@
#include
#include
#include "ESPAsyncWebServer.h"
-#include "FIFO_GENERIC.h"
+#include "FIFO.h"
#define BUFFER_OUTPUT_SIZE 1024
#define BUFFER_INPUT_SIZE 1024
@@ -29,8 +29,8 @@ class TCPSOCKET
static void handleTimeOut(void *arg, AsyncClient *client, uint32_t time);
static void handleError(void *arg, AsyncClient *client, int8_t error);
- FIFO_GENERIC FIFOout;
- FIFO_GENERIC FIFOin;
+ FIFO FIFOout;
+ FIFO FIFOin;
public:
TCPSOCKET(const uint32_t port);
diff --git a/src/lib/THERMAL/devThermal.cpp b/src/lib/THERMAL/devThermal.cpp
index af3e9f5b37..fab6a86132 100644
--- a/src/lib/THERMAL/devThermal.cpp
+++ b/src/lib/THERMAL/devThermal.cpp
@@ -60,10 +60,14 @@ uint32_t get_rpm();
static void initialize()
{
#if defined(HAS_THERMAL)
+#if defined(PLATFORM_ESP32_S3)
+ thermal.init();
+#else
if (OPT_HAS_THERMAL_LM75A && GPIO_PIN_SCL != UNDEF_PIN && GPIO_PIN_SDA != UNDEF_PIN)
{
thermal.init();
}
+#endif
#endif
if (GPIO_PIN_FAN_EN != UNDEF_PIN)
{
@@ -71,10 +75,12 @@ static void initialize()
}
}
-#if defined(HAS_THERMAL)
static void timeoutThermal()
{
+#if defined(HAS_THERMAL)
+#if !defined(PLATFORM_ESP32_S3)
if(OPT_HAS_THERMAL_LM75A)
+#endif
{
thermal.handle();
#ifdef HAS_SMART_FAN
@@ -90,8 +96,8 @@ static void timeoutThermal()
}
#endif
}
-}
#endif
+}
#if defined(PLATFORM_ESP32)
static void setFanSpeed()
@@ -258,12 +264,7 @@ static int event()
static int timeout()
{
-#if defined(HAS_THERMAL)
- if (OPT_HAS_THERMAL_LM75A && GPIO_PIN_SCL != UNDEF_PIN && GPIO_PIN_SDA != UNDEF_PIN)
- {
- timeoutThermal();
- }
-#endif
+ timeoutThermal();
timeoutFan();
timeoutTacho();
return THERMAL_DURATION;
diff --git a/src/lib/THERMAL/thermal.cpp b/src/lib/THERMAL/thermal.cpp
index dc1cafa748..484f2173b5 100644
--- a/src/lib/THERMAL/thermal.cpp
+++ b/src/lib/THERMAL/thermal.cpp
@@ -6,8 +6,11 @@
#include "lm75a.h"
LM75A lm75a;
#endif
+#if defined(PLATFORM_ESP32_S3)
+#include "driver/temp_sensor.h"
+#endif
-uint8_t thermal_threshold_data[] = {
+static const uint8_t thermal_threshold_data[] = {
THERMAL_FAN_DEFAULT_HIGH_THRESHOLD,
THERMAL_FAN_DEFAULT_LOW_THRESHOLD,
THERMAL_FAN_ALWAYS_ON_HIGH_THRESHOLD,
@@ -16,25 +19,34 @@ uint8_t thermal_threshold_data[] = {
THERMAL_FAN_OFF_LOW_THRESHOLD
};
-int thermal_status = THERMAL_STATUS_FAIL;
+static Thermal_Status_t thermal_status = THERMAL_STATUS_FAIL;
void Thermal::init()
{
int status = -1;
-#ifdef HAS_THERMAL_LM75A
- status = lm75a.init();
-#endif
- if(status == -1)
+ if (OPT_HAS_THERMAL_LM75A)
{
- ERRLN("Thermal failed!");
+ status = lm75a.init();
}
- else
+#if defined(PLATFORM_ESP32_S3)
+ if (status == -1)
{
- DBGLN("Thermal OK!");
- temp_value = 0;
- thermal_status = THERMAL_STATUS_NORMAL;
- update_threshold(0);
+ temp_sensor_config_t temp_sensor = TSENS_CONFIG_DEFAULT();
+ temp_sensor.dac_offset = TSENS_DAC_L2; //TSENS_DAC_L2 is default L4(-40℃ ~ 20℃), L2(-10℃ ~ 80℃) L1(20℃ ~ 100℃) L0(50℃ ~ 125℃)
+ temp_sensor_set_config(temp_sensor);
+ temp_sensor_start();
+ }
+#else
+ if (status == -1)
+ {
+ ERRLN("Thermal failed!");
+ return;
}
+#endif
+ DBGLN("Thermal OK!");
+ temp_value = 0;
+ thermal_status = THERMAL_STATUS_NORMAL;
+ update_threshold(0);
}
void Thermal::handle()
@@ -49,8 +61,15 @@ uint8_t Thermal::read_temp()
ERRLN("thermal not ready!");
return 0;
}
-#ifdef HAS_THERMAL_LM75A
- return lm75a.read_lm75a();
+ if (OPT_HAS_THERMAL_LM75A)
+ {
+ return lm75a.read_lm75a();
+ }
+
+#if defined(PLATFORM_ESP32_S3)
+ float result = 0;
+ temp_sensor_read_celsius(&result);
+ return static_cast(result);
#else
return 0;
#endif
@@ -69,17 +88,18 @@ void Thermal::update_threshold(int index)
ERRLN("thermal not ready!");
return;
}
- int size = sizeof(thermal_threshold_data)/sizeof(thermal_threshold_data[0]);
+ constexpr int size = sizeof(thermal_threshold_data)/sizeof(thermal_threshold_data[0]);
if(index > size/2)
{
ERRLN("thermal index out of range!");
return;
}
- uint8_t high = thermal_threshold_data[2*index];
- uint8_t low = thermal_threshold_data[2*index+1];
-#ifdef HAS_THERMAL_LM75A
- lm75a.update_lm75a_threshold(high, low);
-#endif
+ if (OPT_HAS_THERMAL_LM75A)
+ {
+ const uint8_t high = thermal_threshold_data[2*index];
+ const uint8_t low = thermal_threshold_data[2*index+1];
+ lm75a.update_lm75a_threshold(high, low);
+ }
}
#endif
\ No newline at end of file
diff --git a/src/lib/Telemetry/telemetry.cpp b/src/lib/Telemetry/telemetry.cpp
index 59e5ddabea..b055fc68b8 100644
--- a/src/lib/Telemetry/telemetry.cpp
+++ b/src/lib/Telemetry/telemetry.cpp
@@ -54,11 +54,30 @@ bool Telemetry::ShouldSendDeviceFrame()
return deviceFrame;
}
+void Telemetry::SetCrsfBatterySensorDetected()
+{
+ crsfBatterySensorDetected = true;
+}
+
void Telemetry::CheckCrsfBatterySensorDetected()
{
if (CRSFinBuffer[CRSF_TELEMETRY_TYPE_INDEX] == CRSF_FRAMETYPE_BATTERY_SENSOR)
{
- crsfBatterySensorDetected = true;
+ SetCrsfBatterySensorDetected();
+ }
+}
+
+void Telemetry::SetCrsfBaroSensorDetected()
+{
+ crsfBaroSensorDetected = true;
+}
+
+void Telemetry::CheckCrsfBaroSensorDetected()
+{
+ if (CRSFinBuffer[CRSF_TELEMETRY_TYPE_INDEX] == CRSF_FRAMETYPE_BARO_ALTITUDE ||
+ CRSFinBuffer[CRSF_TELEMETRY_TYPE_INDEX] == CRSF_FRAMETYPE_VARIO)
+ {
+ SetCrsfBaroSensorDetected();
}
}
@@ -188,9 +207,10 @@ bool Telemetry::RXhandleUARTin(uint8_t data)
{
AppendTelemetryPackage(CRSFinBuffer);
- // Special case to check here and not in AppendTelemetryPackage(). devAnalogVbat sends
+ // Special case to check here and not in AppendTelemetryPackage(). devAnalogVbat and vario sends
// direct to AppendTelemetryPackage() and we want to detect packets only received through serial.
CheckCrsfBatterySensorDetected();
+ CheckCrsfBaroSensorDetected();
receivedPackages++;
return true;
@@ -211,36 +231,61 @@ bool Telemetry::RXhandleUARTin(uint8_t data)
return true;
}
-bool Telemetry::AppendTelemetryPackage(uint8_t *package)
+/**
+ * @brief: Check the CRSF frame for commands that should not be passed on
+ * @return: true if packet was internal and should not be processed further
+*/
+bool Telemetry::processInternalTelemetryPackage(uint8_t *package)
{
- const crsf_header_t *header = (crsf_header_t *) package;
+ const crsf_ext_header_t *header = (crsf_ext_header_t *)package;
- if (header->type == CRSF_FRAMETYPE_COMMAND && package[3] == 'b' && package[4] == 'l')
- {
- callBootloader = true;
- return true;
- }
- if (header->type == CRSF_FRAMETYPE_COMMAND && package[3] == 'b' && package[4] == 'd')
+ if (header->type == CRSF_FRAMETYPE_COMMAND)
{
- callEnterBind = true;
- return true;
- }
- if (header->type == CRSF_FRAMETYPE_COMMAND && package[3] == 'm' && package[4] == 'm')
- {
- callUpdateModelMatch = true;
- modelMatchId = package[5];
- return true;
+ // Non CRSF, dest=b src=l -> reboot to bootloader
+ if (package[3] == 'b' && package[4] == 'l')
+ {
+ callBootloader = true;
+ return true;
+ }
+ // 1. Non CRSF, dest=b src=b -> bind mode
+ // 2. CRSF bind command
+ if ((package[3] == 'b' && package[4] == 'd') ||
+ (header->frame_size >= 6 // official CRSF is 7 bytes with two CRCs
+ && header->dest_addr == CRSF_ADDRESS_CRSF_RECEIVER
+ && header->orig_addr == CRSF_ADDRESS_FLIGHT_CONTROLLER
+ && header->payload[0] == CRSF_COMMAND_SUBCMD_RX
+ && header->payload[1] == CRSF_COMMAND_SUBCMD_RX_BIND))
+ {
+ callEnterBind = true;
+ return true;
+ }
+ // Non CRSF, dest=b src=m -> set modelmatch
+ if (package[3] == 'm' && package[4] == 'm')
+ {
+ callUpdateModelMatch = true;
+ modelMatchId = package[5];
+ return true;
+ }
}
- if (header->type == CRSF_FRAMETYPE_DEVICE_PING && package[CRSF_TELEMETRY_TYPE_INDEX + 1] == CRSF_ADDRESS_CRSF_RECEIVER)
+
+ if (header->type == CRSF_FRAMETYPE_DEVICE_PING && header->dest_addr == CRSF_ADDRESS_CRSF_RECEIVER)
{
sendDeviceFrame = true;
return true;
}
+ return false;
+}
+
+bool Telemetry::AppendTelemetryPackage(uint8_t *package)
+{
+ if (processInternalTelemetryPackage(package))
+ return true;
+
+ const crsf_header_t *header = (crsf_header_t *) package;
uint8_t targetIndex = 0;
bool targetFound = false;
-
if (header->type >= CRSF_FRAMETYPE_DEVICE_PING)
{
const crsf_ext_header_t *extHeader = (crsf_ext_header_t *) package;
diff --git a/src/lib/Telemetry/telemetry.h b/src/lib/Telemetry/telemetry.h
index 9aa826f7c0..d5776daf2f 100644
--- a/src/lib/Telemetry/telemetry.h
+++ b/src/lib/Telemetry/telemetry.h
@@ -2,7 +2,7 @@
#include
#include "crsf_protocol.h"
-#include "devCRSF.h"
+#include "CRSF.h"
enum CustomTelemSubTypeID : uint8_t {
CRSF_AP_CUSTOM_TELEM_SINGLE_PACKET_PASSTHROUGH = 0xF0,
@@ -58,13 +58,18 @@ class Telemetry
bool ShouldCallUpdateModelMatch();
bool ShouldSendDeviceFrame();
void CheckCrsfBatterySensorDetected();
+ void SetCrsfBatterySensorDetected();
bool GetCrsfBatterySensorDetected() { return crsfBatterySensorDetected; };
+ void CheckCrsfBaroSensorDetected();
+ void SetCrsfBaroSensorDetected();
+ bool GetCrsfBaroSensorDetected() { return crsfBaroSensorDetected; };
uint8_t GetUpdatedModelMatch() { return modelMatchId; }
bool GetNextPayload(uint8_t* nextPayloadSize, uint8_t **payloadData);
uint8_t UpdatedPayloadCount();
uint8_t ReceivedPackagesCount();
bool AppendTelemetryPackage(uint8_t *package);
private:
+ bool processInternalTelemetryPackage(uint8_t *package);
void AppendToPackage(volatile crsf_telemetry_package_t *current);
uint8_t CRSFinBuffer[CRSF_MAX_PACKET_LEN];
telemetry_state_s telemetry_state;
@@ -77,5 +82,6 @@ class Telemetry
bool callUpdateModelMatch;
bool sendDeviceFrame;
bool crsfBatterySensorDetected;
+ bool crsfBaroSensorDetected;
uint8_t modelMatchId;
};
diff --git a/src/lib/VTX/devVTX.cpp b/src/lib/VTX/devVTX.cpp
index 3d486b43e0..b9e417d0bd 100644
--- a/src/lib/VTX/devVTX.cpp
+++ b/src/lib/VTX/devVTX.cpp
@@ -8,6 +8,7 @@
#include "logging.h"
#include "devButton.h"
+#include "handset.h"
#define PITMODE_OFF 0
#define PITMODE_ON 1
@@ -17,7 +18,6 @@
// reset between the user switching equipment
#define VTX_DISCONNECT_DEBOUNCE_MS (10 * 1000)
-extern CRSF crsf;
extern Stream *TxBackpack;
static uint8_t pitmodeAuxState = 0;
static bool sendEepromWrite = true;
@@ -61,7 +61,7 @@ static void eepromWriteToMSPOut()
packet.reset();
packet.function = MSP_EEPROM_WRITE;
- crsf.AddMspMessage(&packet);
+ CRSF::AddMspMessage(&packet, CRSF_ADDRESS_FLIGHT_CONTROLLER);
}
static void VtxConfigToMSPOut()
@@ -88,9 +88,9 @@ static void VtxConfigToMSPOut()
}
}
- crsf.AddMspMessage(&packet);
+ CRSF::AddMspMessage(&packet, CRSF_ADDRESS_FLIGHT_CONTROLLER);
- if (!crsf.IsArmed()) // Do not send while armed. There is no need to change the video frequency while armed. It can also cause VRx modules to flash up their OSD menu e.g. Rapidfire.
+ if (!handset->IsArmed()) // Do not send while armed. There is no need to change the video frequency while armed. It can also cause VRx modules to flash up their OSD menu e.g. Rapidfire.
{
MSP::sendPacket(&packet, TxBackpack); // send to tx-backpack as MSP
}
@@ -164,7 +164,7 @@ static int timeout()
VtxSendState = VTXSS_UNKNOWN;
// Never received a connection, clear the queue which now
// has multiple VTX config packets in it
- crsf.ResetMspQueue();
+ CRSF::ResetMspQueue();
}
return DURATION_NEVER;
diff --git a/src/lib/VTXSPI/devVTXSPI.cpp b/src/lib/VTXSPI/devVTXSPI.cpp
index b39b79986c..6b12a6f9f4 100644
--- a/src/lib/VTXSPI/devVTXSPI.cpp
+++ b/src/lib/VTXSPI/devVTXSPI.cpp
@@ -7,6 +7,7 @@
#include "hwTimer.h"
#include "logging.h"
#include
+#include "PWM.h"
#define SYNTHESIZER_REGISTER_A 0x00
#define SYNTHESIZER_REGISTER_B 0x01
@@ -41,7 +42,7 @@
#define BUF_PACKET_SIZE 4 // 25b packet in 4 bytes
#if defined(PLATFORM_ESP32)
-constexpr uint8_t rfAmpPwmChannel = 0;
+pwm_channel_t rfAmpPwmChannel = -1;
#endif
uint16_t vtxSPIFrequency = 6000;
@@ -64,9 +65,13 @@ static bool stopVtxMonitoring = false;
#if defined(TARGET_UNIFIED_RX)
const uint16_t *VpdSetPointArray25mW = nullptr;
const uint16_t *VpdSetPointArray100mW = nullptr;
+const uint16_t *PwmArray25mW = nullptr;
+const uint16_t *PwmArray100mW = nullptr;
#else
uint16_t VpdSetPointArray25mW[] = VPD_VALUES_25MW;
uint16_t VpdSetPointArray100mW[] = VPD_VALUES_100MW;
+uint16_t PwmArray25mW[] = PWM_VALUES_25MW;
+uint16_t PwmArray100mW[] = PWM_VALUES_100MW;
#endif
uint16_t VpdFreqArray[] = {5650, 5750, 5850, 5950};
@@ -83,13 +88,21 @@ static void rtc6705WriteRegister(uint32_t regData)
digitalWrite(GPIO_PIN_SPI_VTX_NSS, LOW);
}
- #if defined(PLATFORM_ESP32)
- vtxSPI->transferBits(regData, nullptr, 25);
- #else
- uint8_t buf[BUF_PACKET_SIZE];
- memcpy(buf, (byte *)®Data, BUF_PACKET_SIZE);
- vtxSPI->transfer(buf, BUF_PACKET_SIZE);
- #endif
+ // On some ESP32 MCUs there's a silicon bug which affects all 8n+1 bit and 1 bit transfers where the
+ // last bit sent is corrupt.
+ // See: https://github.com/ExpressLRS/ExpressLRS/pull/2406#issuecomment-1722573356
+ //
+ // To reproduce, use an ESP32S3 and 25 bit transfers, change from channel A4 to A1, then A1 to A4 (ok),
+ // then A4 to A3, then A3 to A4 (fail)
+ //
+ // 12816, 9286833 appears on the scope when changing from A:1->A:4
+ // 12816, 26064049 appears on the scope when changing from A:3->A:4
+ //
+ // 9286833 = 0_1000_1101_1011_0100_1011_0001
+ // 26064049 = 1_1000_1101_1011_0100_1011_0001
+ //
+ // Since the RTC6705 just ignores the extra bits, we send 32 bits and the RTC6705 ignores the last 7 bits.
+ vtxSPI->transfer32(regData);
if (GPIO_PIN_SPI_VTX_SCK == GPIO_PIN_SCK)
{
@@ -140,14 +153,16 @@ static void RfAmpVrefOff()
static void setPWM()
{
-#if defined(PLATFORM_ESP32)
+#if defined(PLATFORM_ESP32_S3)
+ PWM.setDuty(rfAmpPwmChannel, vtxSPIPWM * 1000 / 4096);
+#elif defined(PLATFORM_ESP32)
if (GPIO_PIN_RF_AMP_PWM == 25 || GPIO_PIN_RF_AMP_PWM == 26)
{
dacWrite(GPIO_PIN_RF_AMP_PWM, vtxSPIPWM >> 4);
}
else
{
- ledcWrite(rfAmpPwmChannel, vtxSPIPWM);
+ PWM.setDuty(rfAmpPwmChannel, vtxSPIPWM * 1000 / 4096);
}
#else
analogWrite(GPIO_PIN_RF_AMP_PWM, vtxSPIPWM);
@@ -200,29 +215,60 @@ static uint16_t LinearInterpVpdSetPointArray(const uint16_t VpdSetPointArray[])
return newVpd;
}
+static uint16_t LinearInterpSetPwm(const uint16_t PwmArray[])
+{
+ uint16_t newPwm = 0;
+
+ if (vtxSPIFrequencyCurrent <= VpdFreqArray[0])
+ {
+ newPwm = PwmArray[0];
+ }
+ else if (vtxSPIFrequencyCurrent >= VpdFreqArray[VpdSetPointCount - 1])
+ {
+ newPwm = PwmArray[VpdSetPointCount - 1];
+ }
+ else
+ {
+ for (uint8_t i = 0; i < (VpdSetPointCount - 1); i++)
+ {
+ if (vtxSPIFrequencyCurrent < VpdFreqArray[i + 1])
+ {
+ newPwm = PwmArray[i] + ((PwmArray[i + 1]-PwmArray[i])/(VpdFreqArray[i + 1]-VpdFreqArray[i])) * (vtxSPIFrequencyCurrent - VpdFreqArray[i]);
+ }
+ }
+ }
+
+ return newPwm;
+}
+
static void SetVpdSetPoint()
{
switch (vtxSPIPowerIdx)
{
case 1: // 0 mW
VpdSetPoint = VPD_SETPOINT_0_MW;
+ vtxSPIPWM = vtxMaxPWM;
break;
case 2: // RCE
case 3: // 25 mW
VpdSetPoint = LinearInterpVpdSetPointArray(VpdSetPointArray25mW);
+ vtxSPIPWM = LinearInterpSetPwm(PwmArray25mW);
break;
case 4: // 100 mW
VpdSetPoint = LinearInterpVpdSetPointArray(VpdSetPointArray100mW);
+ vtxSPIPWM = LinearInterpSetPwm(PwmArray100mW);
break;
default: // YOLO mW
VpdSetPoint = VPD_SETPOINT_YOLO_MW;
+ vtxSPIPWM = vtxMinPWM;
break;
}
- DBGLN("Setting new VPD setpoint: %d", VpdSetPoint);
+ setPWM();
+ DBGLN("Setting new VPD setpoint: %d, initial PWM: %d", VpdSetPoint, vtxSPIPWM);
}
static void checkOutputPower()
@@ -252,6 +298,37 @@ static void checkOutputPower()
}
}
+#if defined(VTX_OUTPUT_CALIBRATION)
+int sampleCount = 0;
+int calibFreqIndex = 0;
+#define CALIB_SAMPLES 10
+
+static int gatherOutputCalibrationData()
+{
+ if (VpdSetPoint <= VPD_SETPOINT_YOLO_MW && calibFreqIndex < VpdSetPointCount)
+ {
+ sampleCount++;
+ checkOutputPower();
+ DBGLN("VTX Freq=%d, VPD setpoint=%d, VPD=%d, PWM=%d, sample=%d", VpdFreqArray[calibFreqIndex], VpdSetPoint, Vpd, vtxSPIPWM, sampleCount);
+ if (sampleCount >= CALIB_SAMPLES)
+ {
+ VpdSetPoint += VPD_BUFFER;
+ sampleCount = 0;
+ }
+
+ if (VpdSetPoint > VPD_SETPOINT_YOLO_MW)
+ {
+ calibFreqIndex++;
+ rtc6705SetFrequency(VpdFreqArray[calibFreqIndex]);
+ VpdSetPoint = VPD_BUFFER;
+ return RTC6705_PLL_SETTLE_TIME_MS;
+ }
+ return VTX_POWER_INTERVAL_MS;
+ }
+ return DURATION_NEVER;
+}
+#endif
+
void disableVTxSpi()
{
stopVtxMonitoring = true;
@@ -264,19 +341,16 @@ static void initialize()
#if defined(TARGET_UNIFIED_RX)
VpdSetPointArray25mW = VPD_VALUES_25MW;
VpdSetPointArray100mW = VPD_VALUES_100MW;
+ PwmArray25mW = PWM_VALUES_25MW;
+ PwmArray100mW = PWM_VALUES_100MW;
#endif
if (GPIO_PIN_SPI_VTX_NSS != UNDEF_PIN)
{
if (GPIO_PIN_SPI_VTX_SCK != UNDEF_PIN && GPIO_PIN_SPI_VTX_SCK != GPIO_PIN_SCK)
{
- vtxSPI = new SPIClass();
- #if defined(PLATFORM_ESP32)
+ vtxSPI = new SPIClass(HSPI);
vtxSPI->begin(GPIO_PIN_SPI_VTX_SCK, GPIO_PIN_SPI_VTX_MISO, GPIO_PIN_SPI_VTX_MOSI, GPIO_PIN_SPI_VTX_NSS);
- #else
- vtxSPI->pins(GPIO_PIN_SPI_VTX_SCK, GPIO_PIN_SPI_VTX_MISO, GPIO_PIN_SPI_VTX_MOSI, GPIO_PIN_SPI_VTX_NSS);
- vtxSPI->begin();
- #endif
vtxSPI->setHwCs(true);
vtxSPI->setBitOrder(LSBFIRST);
}
@@ -290,11 +364,9 @@ static void initialize()
pinMode(GPIO_PIN_RF_AMP_VREF, OUTPUT);
digitalWrite(GPIO_PIN_RF_AMP_VREF, LOW);
- #if defined(PLATFORM_ESP8266)
- pinMode(GPIO_PIN_RF_AMP_PWM, OUTPUT);
- analogWriteFreq(10000); // 10kHz
- analogWriteResolution(12); // 0 - 4095
- #else
+ #if defined(PLATFORM_ESP32_S3)
+ rfAmpPwmChannel = PWM.allocate(GPIO_PIN_RF_AMP_PWM, 10000);
+ #elif defined(PLATFORM_ESP32)
// If using a DAC pin then adjust min/max and initial value
if (GPIO_PIN_RF_AMP_PWM == 25 || GPIO_PIN_RF_AMP_PWM == 26)
{
@@ -304,9 +376,12 @@ static void initialize()
}
else
{
- ledcSetup(rfAmpPwmChannel, 10000, 12); // 12 bit 10khz
- ledcAttachPin(GPIO_PIN_RF_AMP_PWM, rfAmpPwmChannel);
+ rfAmpPwmChannel = PWM.allocate(GPIO_PIN_RF_AMP_PWM, 10000);
}
+ #else
+ pinMode(GPIO_PIN_RF_AMP_PWM, OUTPUT);
+ analogWriteFreq(10000); // 10kHz
+ analogWriteResolution(12); // 0 - 4095
#endif
setPWM();
@@ -321,6 +396,14 @@ static int start()
return DURATION_NEVER;
}
+#if defined(VTX_OUTPUT_CALIBRATION)
+ rtc6705SetFrequency(VpdFreqArray[calibFreqIndex]); // Set to the first calib frequency
+ vtxSPIPitmodeCurrent = 0;
+ VpdSetPoint = VPD_SETPOINT_0_MW;
+ rtc6705PowerAmpOn();
+ return RTC6705_PLL_SETTLE_TIME_MS;
+#endif
+
rtc6705SetFrequency(5999); // Boot with VTx set away from standard frequencies.
rtc6705PowerAmpOn();
@@ -345,17 +428,21 @@ static int event()
static int timeout()
{
- if (GPIO_PIN_SPI_VTX_NSS == UNDEF_PIN)
+ if ((GPIO_PIN_SPI_VTX_NSS == UNDEF_PIN) || stopVtxMonitoring)
{
return DURATION_NEVER;
}
- if ((hwTimer::running && !hwTimer::isTick) || stopVtxMonitoring)
+ if (hwTimer::running && !hwTimer::isTick)
{
// Dont run spi and analog reads during rx hopping, wifi or updating
return DURATION_IMMEDIATELY;
}
+#if defined(VTX_OUTPUT_CALIBRATION)
+ return gatherOutputCalibrationData();
+#endif
+
if (vtxSPIFrequencyCurrent != vtxSPIFrequency)
{
rtc6705SetFrequency(vtxSPIFrequency);
diff --git a/src/lib/WIFI/devWIFI.cpp b/src/lib/WIFI/devWIFI.cpp
index e28c23b3d8..5527bf4453 100644
--- a/src/lib/WIFI/devWIFI.cpp
+++ b/src/lib/WIFI/devWIFI.cpp
@@ -16,10 +16,13 @@
#include
#include
#include
+#include
#else
#include
#include
#define wifi_mode_t WiFiMode_t
+#define U0TXD_GPIO_NUM (1)
+#define U0RXD_GPIO_NUM (3)
#endif
#include
@@ -43,14 +46,17 @@
#include "WebContent.h"
#include "config.h"
+
#if defined(TARGET_TX)
+
+#include "wifiJoystick.h"
+
extern TxConfig config;
extern void setButtonColors(uint8_t b1, uint8_t b2);
#else
extern RxConfig config;
#endif
-extern void deferExecution(uint32_t ms, std::function f);
extern unsigned long rebootTime;
static char station_ssid[33];
@@ -58,7 +64,6 @@ static char station_password[65];
static bool wifiStarted = false;
bool webserverPreventAutoStart = false;
-extern bool InBindingMode;
static wl_status_t laststatus = WL_IDLE_STATUS;
volatile WiFiMode_t wifiMode = WIFI_OFF;
@@ -69,6 +74,7 @@ static const byte DNS_PORT = 53;
static IPAddress netMsk(255, 255, 255, 0);
static DNSServer dnsServer;
static IPAddress ipAddress;
+static IPAddress gatewayIpAddress(0, 0, 0, 0);
#if defined(USE_MSP_WIFI) && defined(TARGET_RX) //MSP2WIFI in enabled only for RX only at the moment
#include "crsf2msp.h"
@@ -78,9 +84,12 @@ static IPAddress ipAddress;
TCPSOCKET wifi2tcp(5761); //port 5761 as used by BF configurator
#endif
+#if defined(PLATFORM_ESP8266)
+static bool scanComplete = false;
+#endif
+
static AsyncWebServer server(80);
static bool servicesStarted = false;
-static bool scanComplete = false;
static constexpr uint32_t STALE_WIFI_SCAN = 20000;
static uint32_t lastScanTimeMS = 0;
@@ -244,15 +253,36 @@ static void HandleReset(AsyncWebServerRequest *request)
static void UpdateSettings(AsyncWebServerRequest *request, JsonVariant &json)
{
- if (flash_discriminator != json["flash-discriminator"].as()) {
+ if (firmwareOptions.flash_discriminator != json["flash-discriminator"].as()) {
request->send(409, "text/plain", "Mismatched device identifier, refresh the page and try again.");
return;
}
+
File file = SPIFFS.open("/options.json", "w");
serializeJson(json, file);
request->send(200);
}
+static const char *GetConfigUidType(JsonDocument &json)
+{
+#if defined(TARGET_RX)
+ if (config.GetVolatileBind())
+ return "Volatile";
+ if (config.GetIsBound())
+ return "Bound";
+ return "Not Bound";
+#else
+ if (firmwareOptions.hasUID)
+ {
+ if (json["options"]["customised"] | false)
+ return "Overridden";
+ else
+ return "Flashed";
+ }
+ return "Not set (using MAC address)";
+#endif
+}
+
static void GetConfiguration(AsyncWebServerRequest *request)
{
#if defined(PLATFORM_ESP32)
@@ -270,6 +300,9 @@ static void GetConfiguration(AsyncWebServerRequest *request)
json["options"] = options;
}
+ JsonArray uid = json["config"].createNestedArray("uid");
+ copyArray(UID, UID_LEN, uid);
+
#if defined(TARGET_TX)
int button_count = 0;
if (GPIO_PIN_BUTTON != UNDEF_PIN)
@@ -279,8 +312,10 @@ static void GetConfiguration(AsyncWebServerRequest *request)
for (int button=0 ; buttonval.color;
- for (int pos=0 ; posval.color;
+ }
+ for (int pos=0 ; posval.actions[pos].pressType ? true : false;
json["config"]["button-actions"][button]["action"][pos]["count"] = buttonColor->val.actions[pos].count;
@@ -302,7 +337,7 @@ static void GetConfiguration(AsyncWebServerRequest *request)
json["config"]["backpack"]["dvr-stop-delay"] = config.GetDvrStopDelay();
json["config"]["backpack"]["dvr-aux-channel"] = config.GetDvrAux();
- for (int model = 0 ; model < 64 ; model++)
+ for (int model = 0 ; model < CONFIG_TX_MODEL_CNT ; model++)
{
const model_config_t &modelConfig = config.GetModelConfig(model);
String strModel(model);
@@ -317,9 +352,8 @@ static void GetConfiguration(AsyncWebServerRequest *request)
modelJson["tx-antenna"] = modelConfig.txAntenna;
}
}
-#endif
- JsonArray uid = json["config"].createNestedArray("uid");
- copyArray(UID, sizeof(UID), uid);
+#endif /* TARGET_TX */
+
if (!exportMode)
{
json["config"]["ssid"] = station_ssid;
@@ -329,30 +363,31 @@ static void GetConfiguration(AsyncWebServerRequest *request)
json["config"]["sbus-failsafe"] = config.GetFailsafeMode();
json["config"]["modelid"] = config.GetModelId();
json["config"]["force-tlm"] = config.GetForceTlmOff();
+ json["config"]["vbind"] = config.GetVolatileBind();
#if defined(GPIO_PIN_PWM_OUTPUTS)
- for (uint8_t ch=0; chraw;
- json["config"]["pwm"][ch]["pin"] = GPIO_PIN_PWM_OUTPUTS[ch];
+ json["config"]["pwm"][ch]["config"] = config.GetPwmChannel(ch)->raw;
+ json["config"]["pwm"][ch]["pin"] = GPIO_PIN_PWM_OUTPUTS[ch];
+ uint8_t features = 0;
+ auto pin = GPIO_PIN_PWM_OUTPUTS[ch];
+ if (pin == U0TXD_GPIO_NUM) features |= 1; // SerialTX supported
+ else if (pin == U0RXD_GPIO_NUM) features |= 2; // SerialRX supported
+ else if (pin == GPIO_PIN_SCL) features |= 4; // I2C SCL supported (only on this pin)
+ else if (pin == GPIO_PIN_SDA) features |= 8; // I2C SCL supported (only on this pin)
+ else if (GPIO_PIN_SCL == UNDEF_PIN || GPIO_PIN_SDA == UNDEF_PIN) features |= 12; // Both I2C SCL/SDA supported (on any pin)
+ #if defined(PLATFORM_ESP32)
+ if (pin != 0) features |= 16; // DShot supported
+ #endif
+ json["config"]["pwm"][ch]["features"] = features;
}
#endif
#endif
json["config"]["product_name"] = product_name;
json["config"]["lua_name"] = device_name;
- json["config"]["reg_domain"] = getRegulatoryDomain();
-
- #if defined(TARGET_RX)
- if (config.GetOnLoan()) json["config"]["uidtype"] = "On loan";
- else
- #endif
- if (firmwareOptions.hasUID) json["config"]["uidtype"] = (json["options"]["customised"] | false) ? "Overridden" : "Flashed";
- #if defined(TARGET_RX)
- else if (config.GetIsBound()) json["config"]["uidtype"] = "Traditional";
- else json["config"]["uidtype"] = "Not set";
- #else
- else json["config"]["uidtype"] = "Not set (using MAC address)";
- #endif
+ json["config"]["reg_domain"] = FHSSgetRegulatoryDomain();
json["config"]["has-highpower"] = (MaxPower != HighPower);
+ json["config"]["uidtype"] = GetConfigUidType(json);
}
AsyncResponseStream *response = request->beginResponseStream("application/json");
@@ -368,7 +403,7 @@ static void UpdateConfiguration(AsyncWebServerRequest *request, JsonVariant &jso
for (size_t button=0 ; buttonsend(200);
}
#else
+/**
+ * @brief: Copy uid to config if changed
+*/
+static void JsonUidToConfig(JsonVariant &json)
+{
+ JsonArray juid = json["uid"].as();
+ size_t juidLen = constrain(juid.size(), 0, UID_LEN);
+ uint8_t newUid[UID_LEN] = { 0 };
+
+ // Copy only as many bytes as were included, right-justified
+ // This supports 6-digit UID as well as 4-digit (OTA bound) UID
+ copyArray(juid, &newUid[UID_LEN-juidLen], juidLen);
+
+ if (memcmp(newUid, config.GetUID(), UID_LEN) != 0)
+ {
+ config.SetUID(newUid);
+ config.Commit();
+ // Also copy it to the global UID in case the page is reloaded
+ memcpy(UID, newUid, UID_LEN);
+ }
+}
static void UpdateConfiguration(AsyncWebServerRequest *request, JsonVariant &json)
{
uint8_t protocol = json["serial-protocol"] | 0;
- DBGLN("Setting serial protocol %u", protocol);
config.SetSerialProtocol((eSerialProtocol)protocol);
uint8_t failsafe = json["sbus-failsafe"] | 0;
- DBGLN("Setting SBUS failsafe mode %u", failsafe);
config.SetFailsafeMode((eFailsafeMode)failsafe);
long modelid = json["modelid"] | 255;
if (modelid < 0 || modelid > 63) modelid = 255;
- DBGLN("Setting model match id %u", (uint8_t)modelid);
config.SetModelId((uint8_t)modelid);
long forceTlm = json["force-tlm"] | 0;
- DBGLN("Setting force telemetry %u", (uint8_t)forceTlm);
config.SetForceTlmOff(forceTlm != 0);
+ config.SetVolatileBind((json["vbind"] | 0) != 0);
+ JsonUidToConfig(json);
+
#if defined(GPIO_PIN_PWM_OUTPUTS)
JsonArray pwm = json["pwm"].as();
for(uint32_t channel = 0 ; channel < pwm.size() ; channel++)
{
uint32_t val = pwm[channel];
- DBGLN("PWMch(%u)=%u", channel, val);
+ //DBGLN("PWMch(%u)=%u", channel, val);
config.SetPwmChannelRaw(channel, val);
}
#endif
@@ -485,7 +540,27 @@ static void WebUpdateGetTarget(AsyncWebServerRequest *request)
json["version"] = VERSION;
json["product_name"] = product_name;
json["lua_name"] = device_name;
- json["reg_domain"] = getRegulatoryDomain();
+ json["reg_domain"] = FHSSgetRegulatoryDomain();
+ json["git-commit"] = commit;
+#if defined(TARGET_TX)
+ json["module-type"] = "TX";
+#endif
+#if defined(TARGET_RX)
+ json["module-type"] = "RX";
+#endif
+#if defined(RADIO_SX128X)
+ json["radio-type"] = "SX128X";
+ json["has-sub-ghz"] = false;
+#endif
+#if defined(RADIO_SX127X)
+ json["radio-type"] = "SX127X";
+ json["has-sub-ghz"] = true;
+#endif
+#if defined(RADIO_LR1121)
+ json["radio-type"] = "LR1121";
+ json["has-sub-ghz"] = true;
+#endif
+
AsyncResponseStream *response = request->beginResponseStream("application/json");
serializeJson(json, *response);
request->send(response);
@@ -650,6 +725,10 @@ static void WebUploadResponseHandler(AsyncWebServerRequest *request) {
static void WebUploadDataHandler(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) {
force_update = force_update || request->hasArg("force");
if (index == 0) {
+ #ifdef HAS_WIFI_JOYSTICK
+ WifiJoystick::StopJoystickService();
+ #endif
+
size_t filesize = request->header("X-FileSize").toInt();
DBGLN("Update: '%s' size %u", filename.c_str(), filesize);
#if defined(PLATFORM_ESP8266)
@@ -715,6 +794,24 @@ static void WebUploadForceUpdateHandler(AsyncWebServerRequest *request) {
}
}
+#ifdef HAS_WIFI_JOYSTICK
+static void WebUdpControl(AsyncWebServerRequest *request)
+{
+ const String &action = request->arg("action");
+ if (action.equals("joystick_begin"))
+ {
+ WifiJoystick::StartSending(request->client()->remoteIP(),
+ request->arg("interval").toInt(), request->arg("channels").toInt());
+ request->send(200, "text/plain", "ok");
+ }
+ else if (action.equals("joystick_end"))
+ {
+ WifiJoystick::StopSending();
+ request->send(200, "text/plain", "ok");
+ }
+}
+#endif
+
static size_t firmwareOffset = 0;
static size_t getFirmwareChunk(uint8_t *data, size_t len, size_t pos)
{
@@ -770,7 +867,7 @@ static void HandleContinuousWave(AsyncWebServerRequest *request) {
request->client()->close();
Radio.TXdoneCallback = [](){};
- Radio.Begin();
+ Radio.Begin(FHSSgetMinimumFreq(), FHSSgetMaximumFreq());
POWERMGNT::init();
POWERMGNT::setPower(POWERMGNT::getMinPower());
@@ -824,12 +921,6 @@ static void startWiFi(unsigned long now)
WiFi.persistent(false);
WiFi.disconnect();
WiFi.mode(WIFI_OFF);
- #if defined(PLATFORM_ESP8266)
- WiFi.setOutputPower(13);
- WiFi.setPhyMode(WIFI_PHY_MODE_11N);
- #elif defined(PLATFORM_ESP32)
- WiFi.setTxPower(WIFI_POWER_13dBm);
- #endif
strcpy(station_ssid, firmwareOptions.home_wifi_ssid);
strcpy(station_password, firmwareOptions.home_wifi_password);
if (station_ssid[0] == 0) {
@@ -859,10 +950,6 @@ static void startMDNS()
{
options += " -DUNLOCK_HIGHER_POWER";
}
- if (firmwareOptions.uart_inverted)
- {
- options += " -DUART_INVERTED";
- }
options += " -DTLM_REPORT_INTERVAL_MS=" + String(firmwareOptions.tlm_report_interval);
options += " -DFAN_MIN_RUNTIME=" + String(firmwareOptions.fan_min_runtime);
#endif
@@ -912,6 +999,12 @@ static void startMDNS()
MDNS.addServiceTxt("http", "tcp", "type", "rx");
#endif
#endif
+
+ #ifdef HAS_WIFI_JOYSTICK
+ MDNS.addService("elrs", "udp", JOYSTICK_PORT);
+ MDNS.addServiceTxt("elrs", "udp", "device", (const char *)device_name);
+ MDNS.addServiceTxt("elrs", "udp", "version", String(JOYSTICK_VERSION).c_str());
+ #endif
}
static void startServices()
@@ -967,6 +1060,9 @@ static void startServices()
server.on("/options.json", HTTP_GET, getFile);
server.on("/reboot", HandleReboot);
server.on("/reset", HandleReset);
+ #ifdef HAS_WIFI_JOYSTICK
+ server.on("/udpcontrol", HTTP_POST, WebUdpControl);
+ #endif
server.addHandler(new AsyncCallbackJsonWebHandler("/config", UpdateConfiguration));
server.addHandler(new AsyncCallbackJsonWebHandler("/options.json", UpdateSettings));
@@ -984,6 +1080,10 @@ static void startServices()
startMDNS();
+ #ifdef HAS_WIFI_JOYSTICK
+ WifiJoystick::StartJoystickService();
+ #endif
+
servicesStarted = true;
DBGLN("HTTPUpdateServer ready! Open http://%s.local in your browser", wifi_hostname);
#if defined(USE_MSP_WIFI) && defined(TARGET_RX)
@@ -1032,7 +1132,13 @@ static void HandleWebUpdate()
WiFi.setHostname(wifi_hostname); // hostname must be set before the mode is set to STA
#endif
changeTime = now;
- WiFi.softAPConfig(ipAddress, ipAddress, netMsk);
+ #if defined(PLATFORM_ESP8266)
+ WiFi.setOutputPower(20.5);
+ WiFi.setPhyMode(WIFI_PHY_MODE_11N);
+ #elif defined(PLATFORM_ESP32)
+ WiFi.setTxPower(WIFI_POWER_19_5dBm);
+ #endif
+ WiFi.softAPConfig(ipAddress, gatewayIpAddress, netMsk);
WiFi.softAP(wifi_ap_ssid, wifi_ap_password);
startServices();
break;
@@ -1047,6 +1153,14 @@ static void HandleWebUpdate()
WiFi.setHostname(wifi_hostname); // hostname must be set after the mode is set to STA
#endif
changeTime = now;
+ #if defined(PLATFORM_ESP8266)
+ WiFi.setOutputPower(20.5);
+ WiFi.setPhyMode(WIFI_PHY_MODE_11N);
+ #elif defined(PLATFORM_ESP32)
+ WiFi.setTxPower(WIFI_POWER_19_5dBm);
+ WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
+ WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
+ #endif
WiFi.begin(station_ssid, station_password);
startServices();
default:
@@ -1072,6 +1186,10 @@ static void HandleWebUpdate()
#if defined(PLATFORM_ESP8266)
MDNS.update();
#endif
+
+ #ifdef HAS_WIFI_JOYSTICK
+ WifiJoystick::Loop(now);
+ #endif
}
}
diff --git a/src/lib/WIFI/devWIFI.h b/src/lib/WIFI/devWIFI.h
index e4fa66d4b2..228eadc4c8 100644
--- a/src/lib/WIFI/devWIFI.h
+++ b/src/lib/WIFI/devWIFI.h
@@ -3,6 +3,8 @@
#include "device.h"
#if defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)
+#include "devButton.h"
+
extern device_t WIFI_device;
#define HAS_WIFI
#endif
\ No newline at end of file
diff --git a/src/lib/WIFI/wifiJoystick.cpp b/src/lib/WIFI/wifiJoystick.cpp
new file mode 100644
index 0000000000..73dd958935
--- /dev/null
+++ b/src/lib/WIFI/wifiJoystick.cpp
@@ -0,0 +1,158 @@
+#include "device.h"
+
+#include "wifiJoystick.h"
+
+#if defined(TARGET_TX) && defined(PLATFORM_ESP32)
+
+#include
+#include
+#include "common.h"
+#include "CRSF.h"
+#include "handset.h"
+#include "POWERMGNT.h"
+#include "hwTimer.h"
+#include "logging.h"
+#include "options.h"
+
+
+#if defined(RADIO_SX127X)
+extern SX127xDriver Radio;
+#elif defined(RADIO_SX128X)
+extern SX1280Driver Radio;
+#endif
+
+WiFiUDP *WifiJoystick::udp = NULL;
+IPAddress WifiJoystick::remoteIP;
+uint8_t WifiJoystick::channelCount = JOYSTICK_DEFAULT_CHANNEL_COUNT;
+bool WifiJoystick::active = false;
+uint8_t WifiJoystick::failedCount = 0;
+
+static inline uint16_t htole16(uint16_t val)
+{
+#if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+ return __builtin_bswap16(val);
+#else
+ return val;
+#endif
+}
+
+void WifiJoystick::StartJoystickService()
+{
+ if (!udp)
+ {
+ udp = new WiFiUDP();
+ udp->begin(JOYSTICK_PORT);
+ }
+}
+
+void WifiJoystick::StopJoystickService()
+{
+ active = false;
+ if (udp)
+ {
+ udp->stop();
+ delete udp;
+ udp = NULL;
+ }
+}
+
+void WifiJoystick::StartSending(const IPAddress& ip, int32_t updateInterval, uint8_t newChannelCount)
+{
+ remoteIP = ip;
+ if (!udp || active)
+ {
+ return;
+ }
+
+ // RF should already be shut down if in wifi mode
+ // Adjust the timer to run at the requested interval
+ // with a hard lower limit of 1000Hz
+ if (updateInterval == 0 || updateInterval < 1000)
+ {
+ updateInterval = JOYSTICK_DEFAULT_UPDATE_INTERVAL;
+ }
+ hwTimer::updateInterval(updateInterval);
+ handset->setPacketInterval(updateInterval);
+
+ handset->setRCDataCallback(UpdateValues);
+
+ // Channel Count
+ if (newChannelCount == 0)
+ {
+ newChannelCount = JOYSTICK_DEFAULT_CHANNEL_COUNT;
+ }
+ else if (newChannelCount > 16)
+ {
+ newChannelCount = 16;
+ }
+ channelCount = newChannelCount;
+
+ active = true;
+ failedCount = 0;
+}
+
+
+void WifiJoystick::Loop(unsigned long now)
+{
+ static unsigned long last = 0;
+ if (!udp || active)
+ {
+ return;
+ }
+
+ // Advertise the existance of the server via broadcast
+ constexpr unsigned BROADCAST_INTERVAL = 5000U;
+ if (now - last > BROADCAST_INTERVAL)
+ {
+ last = now;
+
+ struct ElrsUdpAdvertisement_s {
+ uint32_t magic; // char[4] = ['E', 'L', 'R', 'S']
+ uint8_t version; // JOYSTICK_VERSION
+ uint16_t port; // JOYSTICK_PORT, little endian
+ uint8_t name_len; // length of the device name that follows
+ char name[0]; // device name
+ } PACKED eua = {
+ .magic = htobe32(0x454C5253), // always big-endian so ELRS is in order
+ .version = JOYSTICK_VERSION,
+ .port = htole16(JOYSTICK_PORT),
+ .name_len = (uint8_t)strlen(device_name)
+ };
+
+ udp->beginPacket(IPAddress(255, 255, 255, 255), JOYSTICK_PORT);
+ udp->write((uint8_t*)&eua, sizeof(eua));
+ udp->write((uint8_t*)device_name, eua.name_len);
+ udp->endPacket();
+ }
+}
+
+void WifiJoystick::UpdateValues()
+{
+ if (!udp || !active)
+ {
+ return;
+ }
+
+ udp->beginPacket(remoteIP, JOYSTICK_PORT);
+ udp->write(WifiJoystick::FRAME_CHANNELS);
+ udp->write(channelCount);
+ for (uint8_t i = 0; i < channelCount; i++)
+ {
+ uint16_t channel = htole16(map(
+ constrain(ChannelData[i], CRSF_CHANNEL_VALUE_MIN, CRSF_CHANNEL_VALUE_MAX),
+ CRSF_CHANNEL_VALUE_MIN, CRSF_CHANNEL_VALUE_MAX, 0, 0x7fff));
+ udp->write((uint8_t*)&channel, 2);
+ }
+
+ // check if sending failed, don't stop sending after the first error since transient errors can happen
+ if (udp->endPacket() == 0)
+ {
+ failedCount++;
+ active = failedCount < JOYSTICK_MAX_SEND_ERROR_COUNT;
+ }
+ else {
+ failedCount = 0;
+ }
+}
+
+#endif
diff --git a/src/lib/WIFI/wifiJoystick.h b/src/lib/WIFI/wifiJoystick.h
new file mode 100644
index 0000000000..bcfa1b6954
--- /dev/null
+++ b/src/lib/WIFI/wifiJoystick.h
@@ -0,0 +1,71 @@
+#pragma once
+
+#if defined(TARGET_TX) && defined(PLATFORM_ESP32)
+
+#include
+
+#define HAS_WIFI_JOYSTICK 1
+#define JOYSTICK_PORT 11000
+#define JOYSTICK_DEFAULT_UPDATE_INTERVAL 10000
+#define JOYSTICK_DEFAULT_CHANNEL_COUNT 8
+#define JOYSTICK_VERSION 1
+#define JOYSTICK_MAX_SEND_ERROR_COUNT 100
+
+/**
+ * Class to send stick data via udp
+ * Version 1
+ *
+ * Usage for simulator or driver on PC:
+ *
+ * Step 1: Find device. There are two options:
+ * Recommended: Use MDNS to query for a UDP service called elrs to find out IP+Port, Protocol version can be found in service txt record
+ * Alternative: Listen for UDP broadcasts that contains a frame in the structure of
+ * 4 bytes: ['E', 'L', 'R', 'S']
+ * 1 byte: Protocol Version
+ * 2 byte unsigned: PORT, little-endian
+ * 1 byte: length of Device Name
+ * ASCII Text: Device Name
+ *
+ * Step 2:
+ * Start udp socket recvfrom(IP, PORT) discovered in Step 1
+ *
+ * Step 3:
+ * Send HTTP POST request to device URL http:///udpcontrol
+ * Param: "action" must be "joystick_begin"
+ * Param (optional): "interval" in us to send updates, or 0 for default (10ms)
+ * Param (optional): "channels" number of channels to send in each frame, or 0 for default (8)
+ * e.g. http:///udpcontrol?action=joystick_begin&interval=10000&channels=8
+ *
+ * Step 4:
+ * receive frames in the format of:
+ * 1 byte: Frame type (WifiJoystickFrameType_e)CHANNELS
+ * 1 byte: Number of channels that follow
+ * 2 bytes unsigned * channel count: Channel data in range 0 to 0x7fff, little-endian
+ *
+ * Step 5:
+ * To end joystick data being sent, POST to the control URL
+ * Param: "action" must be "joystick_end"
+ * e.g http:///udpcontrol?action=joystick_end
+ */
+
+class WifiJoystick
+{
+public:
+ enum WifiJoystickFrameType_e {
+ FRAME_CHANNELS = 1
+ };
+ static void StartJoystickService();
+ static void StopJoystickService();
+ static void UpdateValues();
+ static void StartSending(const IPAddress& ip, int32_t updateInterval, uint8_t newChannelCount);
+ static void StopSending() { active = false; }
+ static void Loop(unsigned long now);
+private:
+ static WiFiUDP *udp;
+ static IPAddress remoteIP;
+ static uint8_t channelCount;
+ static bool active;
+ static uint8_t failedCount;
+};
+
+#endif
diff --git a/src/lib/helpers/dummy.c b/src/lib/helpers/dummy.c
deleted file mode 100644
index 1f65bcc117..0000000000
--- a/src/lib/helpers/dummy.c
+++ /dev/null
@@ -1 +0,0 @@
-int __helpers_dummy;
diff --git a/src/lib/logging/logging.h b/src/lib/logging/logging.h
index 8c12f0f178..5b85e5f9bf 100644
--- a/src/lib/logging/logging.h
+++ b/src/lib/logging/logging.h
@@ -23,7 +23,11 @@
#if defined(TARGET_TX)
extern Stream *TxBackpack;
+#if defined(PLATFORM_ESP32_S3)
+#define LOGGING_UART (Serial)
+#else
#define LOGGING_UART (*TxBackpack)
+#endif
#else
extern Stream *SerialLogger;
#define LOGGING_UART (*SerialLogger)
@@ -47,7 +51,7 @@ void debugFreeInitLogger();
LOGGING_UART.print("ERROR: "); \
debugPrintf(msg, ##__VA_ARGS__); \
LOGGING_UART.println(); \
- })(LOGGING_UART.println("ERROR: " msg))
+ },LOGGING_UART.println("ERROR: " msg))
#endif
#if defined(DEBUG_LOG) && !defined(CRITICAL_FLASH)
@@ -55,10 +59,10 @@ void debugFreeInitLogger();
#define DBGW(c) LOGGING_UART.write(c)
#ifndef LOG_USE_PROGMEM
#define DBG(msg, ...) debugPrintf(msg, ##__VA_ARGS__)
- #define DBGLN(msg, ...) { \
+ #define DBGLN(msg, ...) do { \
debugPrintf(msg, ##__VA_ARGS__); \
LOGGING_UART.println(); \
- }
+ } while(0)
#else
#define DBG(msg, ...) debugPrintf(PSTR(msg), ##__VA_ARGS__)
#define DBGLN(msg, ...) { \
diff --git a/src/lua/elrsV3.lua b/src/lua/elrsV3.lua
index 3391340afe..2852518094 100644
--- a/src/lua/elrsV3.lua
+++ b/src/lua/elrsV3.lua
@@ -1,19 +1,10 @@
-- TNS|ExpressLRS|TNE
---- #########################################################################
---- # #
----- # Copyright (C) OpenTX #
+---- # Copyright (C) OpenTX, adapted for ExpressLRS #
-----# #
---- # License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html #
---- # #
----- # 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. #
----- # #
----- # This program 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 General Public License for more details. #
----- # #
---- #########################################################################
local deviceId = 0xEE
local handsetId = 0xEF
@@ -21,22 +12,19 @@ local deviceName = ""
local lineIndex = 1
local pageOffset = 0
local edit = nil
-local charIndex = 1
local fieldPopup
local fieldTimeout = 0
local loadQ = {}
local fieldChunk = 0
-local fieldData = {}
+local fieldData = nil
local fields = {}
local devices = {}
local goodBadPkt = "?/??? ?"
local elrsFlags = 0
local elrsFlagsInfo = ""
local fields_count = 0
-local backButtonId = 2
-local exitButtonId = 3
local devicesRefreshTimeout = 50
-local folderAccess = nil
+local currentFolderId = nil
local commandRunningIndicator = 1
local expectChunksRemain = -1
local deviceIsELRS_TX = nil
@@ -45,30 +33,23 @@ local titleShowWarn = nil
local titleShowWarnTimeout = 100
local exitscript = 0
+local COL1
local COL2
local maxLineIndex
-local textXoffset
local textYoffset
local textSize
-local byteToStr
local function allocateFields()
fields = {}
for i=1, fields_count + 2 + #devices do
fields[i] = { }
end
- backButtonId = fields_count + 2 + #devices
- fields[backButtonId] = {name="----BACK----", parent = 255, type=14}
- if folderAccess ~= nil then
- fields[backButtonId].parent = folderAccess
- end
- exitButtonId = backButtonId + 1
- fields[exitButtonId] = {id = exitButtonId, name="----EXIT----", type=17}
+ fields[#fields] = {name="----EXIT----", type=14}
end
local function reloadAllField()
fieldChunk = 0
- fieldData = {}
+ fieldData = nil
-- loadQ is actually a stack
loadQ = {}
for fieldId = fields_count, 1, -1 do
@@ -80,7 +61,7 @@ local function getField(line)
local counter = 1
for i = 1, #fields do
local field = fields[i]
- if folderAccess == field.parent and not field.hidden then
+ if currentFolderId == field.parent and not field.hidden then
if counter < line then
counter = counter + 1
else
@@ -90,28 +71,33 @@ local function getField(line)
end
end
-local function constrain(x, low, high)
- if x < low then
- return low
- elseif x > high then
- return high
- end
- return x
-end
-
--- Change display attribute to current field
local function incrField(step)
local field = getField(lineIndex)
local min, max = 0, 0
- if ((field.type <= 5) or (field.type == 8)) then
+ if field.type <= 8 then
min = field.min or 0
max = field.max or 0
- step = field.step * step
+ step = (field.step or 1) * step
elseif field.type == 9 then
min = 0
max = #field.values - 1
end
- field.value = constrain(field.value + step, min, max)
+
+ local newval = field.value
+ repeat
+ newval = newval + step
+ if newval < min then
+ newval = min
+ elseif newval > max then
+ newval = max
+ end
+
+ -- keep looping until a non-blank selection value is found
+ if field.values == nil or #field.values[newval+1] ~= 0 then
+ field.value = newval
+ return
+ end
+ until (newval == min or newval == max)
end
-- Select the next or previous editable field
@@ -136,7 +122,7 @@ local function selectField(step)
end
end
-local function fieldGetSelectOpts(data, offset, last)
+local function fieldGetStrOrOpts(data, offset, last, isOpts)
if last then
while data[offset] ~= 0 do
offset = offset + 1
@@ -144,38 +130,33 @@ local function fieldGetSelectOpts(data, offset, last)
return last, offset + 1
end
- -- Split a table of byte values (string) with ; separator into a table
- local r = {}
+ -- For isOpts: Split a table of byte values (string) with ; separator into a table
+ -- Else just read a string until the first null byte
+ local r = isOpts and {}
local opt = ''
local b = data[offset]
while b ~= 0 do
- if b == 59 then -- ';'
+ if r and b == 59 then -- ';'
r[#r+1] = opt
opt = ''
else
- opt = opt .. byteToStr(b)
+ -- On firmwares that have constants defined for the arrow chars, use them in place of
+ -- the \xc0 \xc1 chars (which are OpenTX-en)
+ -- Use the table to convert the char, else use string.char if not in the table
+ opt = opt .. (({
+ [192] = CHAR_UP or (__opentx and __opentx.CHAR_UP),
+ [193] = CHAR_DOWN or (__opentx and __opentx.CHAR_DOWN)
+ })[b] or string.char(b))
end
offset = offset + 1
b = data[offset]
end
- r[#r+1] = opt
- opt = nil
- return r, offset + 1, collectgarbage("collect")
-end
-
-local function fieldGetString(data, offset, last)
- if last then
- return last, offset + #last + 1
+ if r then
+ r[#r+1] = opt
+ opt = r
end
-
- local result = ""
- while data[offset] ~= 0 do
- result = result .. byteToStr(data[offset])
- offset = offset + 1
- end
-
- return result, offset + 1, collectgarbage("collect")
+ return opt, offset + 1, collectgarbage("collect")
end
local function getDevice(name)
@@ -194,13 +175,17 @@ local function fieldGetValue(data, offset, size)
return result
end
-local function fieldUnsignedLoad(field, data, offset, size)
+-- UINT8/INT8/UINT16/INT16 + FLOAT + TEXTSELECT
+local function fieldUnsignedLoad(field, data, offset, size, unitoffset)
field.value = fieldGetValue(data, offset, size)
field.min = fieldGetValue(data, offset+size, size)
field.max = fieldGetValue(data, offset+2*size, size)
--field.default = fieldGetValue(data, offset+3*size, size)
- field.unit = fieldGetString(data, offset+4*size, field.unit)
- field.step = 1
+ field.unit = fieldGetStrOrOpts(data, offset+(unitoffset or (4*size)), field.unit)
+ -- Only store the size if it isn't 1 (covers most fields / selection)
+ if size ~= 1 then
+ field.size = size
+ end
end
local function fieldUnsignedToSigned(field, size)
@@ -211,101 +196,85 @@ local function fieldUnsignedToSigned(field, size)
--field.default = field.default - bit32.band(field.default, bandval) * 2
end
-local function fieldSignedLoad(field, data, offset, size)
- fieldUnsignedLoad(field, data, offset, size)
+local function fieldSignedLoad(field, data, offset, size, unitoffset)
+ fieldUnsignedLoad(field, data, offset, size, unitoffset)
fieldUnsignedToSigned(field, size)
+ -- signed ints are INTdicated by a negative size
+ field.size = -size
end
-local function fieldIntSave(index, value, size)
- local frame = { deviceId, handsetId, index }
- for i=size-1, 0, -1 do
- frame[#frame + 1] = (bit32.rshift(value, 8*i) % 256)
- end
- crossfireTelemetryPush(0x2D, frame)
+local function fieldIntLoad(field, data, offset)
+ -- Type is U8/I8/U16/I16, use that to determine the size and signedness
+ local loadFn = (field.type % 2 == 0) and fieldUnsignedLoad or fieldSignedLoad
+ loadFn(field, data, offset, math.floor(field.type / 2) + 1)
end
-local function fieldUnsignedSave(field, size)
+local function fieldIntSave(field)
local value = field.value
- fieldIntSave(field.id, value, size)
-end
+ local size = field.size or 1
+ -- Convert signed to 2s complement
+ if size < 0 then
+ size = -size
+ if value < 0 then
+ value = bit32.lshift(0x100, (size-1)*8) + value
+ end
+ end
-local function fieldSignedSave(field, size)
- local value = field.value
- if value < 0 then
- value = bit32.lshift(0x100, (size-1)*8) + value
+ local frame = { deviceId, handsetId, field.id }
+ for i = size-1, 0, -1 do
+ frame[#frame + 1] = bit32.rshift(value, 8*i) % 256
end
- fieldIntSave(field.id, value, size)
+ crossfireTelemetryPush(0x2D, frame)
end
local function fieldIntDisplay(field, y, attr)
lcd.drawText(COL2, y, field.value .. field.unit, attr)
end
--- UINT8
-local function fieldUint8Load(field, data, offset)
- fieldUnsignedLoad(field, data, offset, 1)
-end
-
-local function fieldUint8Save(field)
- fieldUnsignedSave(field, 1)
-end
-
--- INT8
-local function fieldInt8Load(field, data, offset)
- fieldSignedLoad(field, data, offset, 1)
-end
-
-local function fieldInt8Save(field)
- fieldSignedSave(field, 1)
-end
-
--- UINT16
-local function fieldUint16Load(field, data, offset)
- fieldUnsignedLoad(field, data, offset, 2)
-end
-
-local function fieldUint16Save(field)
- fieldUnsignedSave(field, 2)
-end
+-- -- FLOAT
+local function fieldFloatLoad(field, data, offset)
+ fieldSignedLoad(field, data, offset, 4, 21)
+ field.prec = data[offset+16]
+ if field.prec > 3 then
+ field.prec = 3
+ end
+ field.step = fieldGetValue(data, offset+17, 4)
--- INT16
-local function fieldInt16Load(field, data, offset)
- fieldSignedLoad(field, data, offset, 2)
+ -- precompute the format string to preserve the precision
+ field.fmt = "%." .. tostring(field.prec) .. "f" .. field.unit
+ -- Convert precision to a divider
+ field.prec = 10 ^ field.prec
end
-local function fieldInt16Save(field)
- fieldSignedSave(field, 2)
+local function fieldFloatDisplay(field, y, attr)
+ lcd.drawText(COL2, y, string.format(field.fmt, field.value / field.prec), attr)
end
-- TEXT SELECTION
-local function fieldTextSelectionLoad(field, data, offset)
- field.values, offset = fieldGetSelectOpts(data, offset, field.nc == nil and field.values)
+local function fieldTextSelLoad(field, data, offset)
+ field.values, offset = fieldGetStrOrOpts(data, offset, field.nc == nil and field.values, true)
field.value = data[offset]
-- min max and default (offset+1 to 3) are not used on selections
-- units never uses cache
- field.unit = fieldGetString(data, offset+4)
+ field.unit = fieldGetStrOrOpts(data, offset+4)
field.nc = nil -- use cache next time
end
-local function fieldTextSelectionSave(field)
- crossfireTelemetryPush(0x2D, { deviceId, handsetId, field.id, field.value })
-end
-
-local function fieldTextSelectionDisplay_color(field, y, attr)
+local function fieldTextSelDisplay_color(field, y, attr)
local val = field.values[field.value+1] or "ERR"
lcd.drawText(COL2, y, val, attr)
local strPix = lcd.sizeText and lcd.sizeText(val) or (10 * #val)
lcd.drawText(COL2 + strPix, y, field.unit, 0)
end
-local function fieldTextSelectionDisplay_bw(field, y, attr)
+local function fieldTextSelDisplay_bw(field, y, attr)
lcd.drawText(COL2, y, field.values[field.value+1] or "ERR", attr)
lcd.drawText(lcd.getLastPos(), y, field.unit, 0)
end
-- STRING
local function fieldStringLoad(field, data, offset)
- field.value, offset = fieldGetString(data, offset)
+ field.value, offset = fieldGetStrOrOpts(data, offset)
if #data >= offset then
field.maxlen = data[offset]
end
@@ -316,12 +285,13 @@ local function fieldStringDisplay(field, y, attr)
end
local function fieldFolderOpen(field)
- folderAccess = field.id
- local backFld = fields[backButtonId]
+ currentFolderId = field.id
+ local backFld = fields[#fields]
+ backFld.name = "----BACK----"
-- Store the lineIndex and pageOffset to return to in the backFld
backFld.li = lineIndex
backFld.po = pageOffset
- backFld.parent = folderAccess
+ backFld.parent = currentFolderId
lineIndex = 1
pageOffset = 0
@@ -333,13 +303,13 @@ local function fieldFolderDeviceOpen(field)
end
local function fieldFolderDisplay(field,y ,attr)
- lcd.drawText(textXoffset, y, "> " .. field.name, bit32.bor(attr, BOLD))
+ lcd.drawText(COL1, y, "> " .. field.name, bit32.bor(attr, BOLD))
end
local function fieldCommandLoad(field, data, offset)
field.status = data[offset]
field.timeout = data[offset+1]
- field.info = fieldGetString(data, offset+2)
+ field.info = fieldGetStrOrOpts(data, offset+2)
if field.status == 0 then
fieldPopup = nil
end
@@ -352,7 +322,6 @@ local function fieldCommandSave(field)
crossfireTelemetryPush(0x2D, { deviceId, handsetId, field.id, field.status })
fieldPopup = field
fieldPopup.lastStatus = 0
- commandRunningIndicator = 1
fieldTimeout = getTime() + field.timeout
end
end
@@ -362,23 +331,23 @@ local function fieldCommandDisplay(field, y, attr)
lcd.drawText(10, y, "[" .. field.name .. "]", bit32.bor(attr, BOLD))
end
-local function UIbackExec()
- local backFld = fields[backButtonId]
- lineIndex = backFld.li or 1
- pageOffset = backFld.po or 0
-
- backFld.parent = 255
- backFld.li = nil
- backFld.po = nil
- folderAccess = nil
-end
-
-local function UIexitExec()
- exitscript = 1
+local function fieldBackExec(field)
+ if field.parent then
+ lineIndex = field.li or 1
+ pageOffset = field.po or 0
+
+ field.name = "----EXIT----"
+ field.parent = nil
+ field.li = nil
+ field.po = nil
+ currentFolderId = nil
+ else
+ exitscript = 1
+ end
end
local function changeDeviceId(devId) --change to selected device ID
- folderAccess = nil
+ currentFolderId = nil
deviceIsELRS_TX = nil
elrsFlags = 0
--if the selected device ID (target) is a TX Module, we use our Lua ID, so TX Flag that user is using our LUA
@@ -398,14 +367,11 @@ local function fieldDeviceIdSelect(field)
end
local function createDeviceFields() -- put other devices in the field list
- fields[fields_count + 2 + #devices] = fields[backButtonId]
- backButtonId = fields_count + 2 + #devices -- move back button to the end of the list, so it will always show up at the bottom.
+ -- move back button to the end of the list, so it will always show up at the bottom.
+ fields[fields_count + 2 + #devices] = fields[#fields]
for i=1, #devices do
- if devices[i].id == deviceId then
- fields[fields_count+1+i] = {name=devices[i].name, parent = 255, type=15}
- else
- fields[fields_count+1+i] = {name=devices[i].name, parent = fields_count+1, type=15}
- end
+ local parent = (devices[i].id == deviceId) and 255 or (fields_count+1)
+ fields[fields_count+1+i] = {name=devices[i].name, parent=parent, type=15}
end
end
@@ -413,7 +379,7 @@ local function parseDeviceInfoMessage(data)
local offset
local id = data[2]
local newName
- newName, offset = fieldGetString(data, 3)
+ newName, offset = fieldGetStrOrOpts(data, 3)
local device = getDevice(newName)
if device == nil then
device = { id = id, name = newName }
@@ -437,55 +403,67 @@ local function parseDeviceInfoMessage(data)
end
local functions = {
- { load=fieldUint8Load, save=fieldUint8Save, display=fieldIntDisplay }, --1 UINT8(0)
- { load=fieldInt8Load, save=fieldInt8Save, display=fieldIntDisplay }, --2 INT8(1)
- { load=fieldUint16Load, save=fieldUint16Save, display=fieldIntDisplay }, --3 UINT16(2)
- { load=fieldInt16Load, save=fieldInt16Save, display=fieldIntDisplay }, --4 INT16(3)
+ { load=fieldIntLoad, save=fieldIntSave, display=fieldIntDisplay }, --1 UINT8(0)
+ { load=fieldIntLoad, save=fieldIntSave, display=fieldIntDisplay }, --2 INT8(1)
+ { load=fieldIntLoad, save=fieldIntSave, display=fieldIntDisplay }, --3 UINT16(2)
+ { load=fieldIntLoad, save=fieldIntSave, display=fieldIntDisplay }, --4 INT16(3)
nil,
nil,
nil,
nil,
- nil, --9 FLOAT(8)
- { load=fieldTextSelectionLoad, save=fieldTextSelectionSave, display = nil }, --10 SELECT(9)
+ { load=fieldFloatLoad, save=fieldIntSave, display=fieldFloatDisplay }, --9 FLOAT(8)
+ { load=fieldTextSelLoad, save=fieldIntSave, display=nil }, --10 SELECT(9)
{ load=fieldStringLoad, save=nil, display=fieldStringDisplay }, --11 STRING(10) editing NOTIMPL
{ load=nil, save=fieldFolderOpen, display=fieldFolderDisplay }, --12 FOLDER(11)
{ load=fieldStringLoad, save=nil, display=fieldStringDisplay }, --13 INFO(12)
{ load=fieldCommandLoad, save=fieldCommandSave, display=fieldCommandDisplay }, --14 COMMAND(13)
- { load=nil, save=UIbackExec, display=fieldCommandDisplay }, --15 back(14)
+ { load=nil, save=fieldBackExec, display=fieldCommandDisplay }, --15 back/exit(14)
{ load=nil, save=fieldDeviceIdSelect, display=fieldCommandDisplay }, --16 device(15)
{ load=nil, save=fieldFolderDeviceOpen, display=fieldFolderDisplay }, --17 deviceFOLDER(16)
- { load=nil, save=UIexitExec, display=fieldCommandDisplay }, --18 exit(17)
}
local function parseParameterInfoMessage(data)
local fieldId = (fieldPopup and fieldPopup.id) or loadQ[#loadQ]
if data[2] ~= deviceId or data[3] ~= fieldId then
- fieldData = {}
+ fieldData = nil
fieldChunk = 0
return
end
local field = fields[fieldId]
local chunksRemain = data[4]
-- If no field or the chunksremain changed when we have data, don't continue
- if not field or (chunksRemain ~= expectChunksRemain and #fieldData ~= 0) then
+ if not field or (fieldData and chunksRemain ~= expectChunksRemain) then
return
end
- expectChunksRemain = chunksRemain - 1
- for i=5, #data do
- fieldData[#fieldData + 1] = data[i]
+
+ local offset
+ -- If data is chunked, copy it to persistent buffer
+ if chunksRemain > 0 or fieldChunk > 0 then
+ fieldData = fieldData or {}
+ for i=5, #data do
+ fieldData[#fieldData + 1] = data[i]
+ data[i] = nil
+ end
+ offset = 1
+ else
+ -- All data arrived in one chunk, operate directly on data
+ fieldData = data
+ offset = 5
end
+
if chunksRemain > 0 then
fieldChunk = fieldChunk + 1
+ expectChunksRemain = chunksRemain - 1
else
+ -- Field data stream is now complete, process into a field
loadQ[#loadQ] = nil
- -- Populate field from fieldData
- if #fieldData > 3 then
- local offset
+
+ if #fieldData > (offset + 2) then
field.id = fieldId
- field.parent = (fieldData[1] ~= 0) and fieldData[1] or nil
- field.type = bit32.band(fieldData[2], 0x7f)
- field.hidden = bit32.btest(fieldData[2], 0x80) or nil
- field.name, offset = fieldGetString(fieldData, 3, field.name)
+ field.parent = (fieldData[offset] ~= 0) and fieldData[offset] or nil
+ field.type = bit32.band(fieldData[offset+1], 0x7f)
+ field.hidden = bit32.btest(fieldData[offset+1], 0x80) or nil
+ field.name, offset = fieldGetStrOrOpts(fieldData, offset+2, field.name)
if functions[field.type+1].load then
functions[field.type+1].load(field, fieldData, offset)
end
@@ -494,7 +472,7 @@ local function parseParameterInfoMessage(data)
end
fieldChunk = 0
- fieldData = {}
+ fieldData = nil
-- Last field loaded, add the list of devices to the end
if #loadQ == 0 then
@@ -510,7 +488,7 @@ end
local function parseElrsInfoMessage(data)
if data[2] ~= deviceId then
- fieldData = {}
+ fieldData = nil
fieldChunk = 0
return
end
@@ -523,7 +501,7 @@ local function parseElrsInfoMessage(data)
elrsFlags = newFlags
titleShowWarnTimeout = 0
end
- elrsFlagsInfo = fieldGetString(data, 7)
+ elrsFlagsInfo = fieldGetStrOrOpts(data, 7)
local state = (bit32.btest(elrsFlags, 1) and "C") or "-"
goodBadPkt = string.format("%u/%u %s", badPkt, goodPkt, state)
@@ -621,10 +599,10 @@ local function lcd_title_color()
lcd.drawRectangle(LCD_W - textSize, 1 , textSize - 1, barHeight - 2, CUSTOM_COLOR) -- left and bottom line only 1px, make it look bevelled
lcd.setColor(CUSTOM_COLOR, BLACK)
if titleShowWarn then
- lcd.drawText(textXoffset + 1, 4, elrsFlagsInfo, CUSTOM_COLOR)
+ lcd.drawText(COL1 + 1, 4, elrsFlagsInfo, CUSTOM_COLOR)
else
local title = fields_count > 0 and deviceName or "Loading..."
- lcd.drawText(textXoffset + 1, 4, title, CUSTOM_COLOR)
+ lcd.drawText(COL1 + 1, 4, title, CUSTOM_COLOR)
lcd.drawText(LCD_W - 5, 4, goodBadPkt, RIGHT + BOLD + CUSTOM_COLOR)
end
-- progress bar
@@ -652,17 +630,17 @@ local function lcd_title_bw()
else
lcd.drawFilledRectangle(0, 0, LCD_W, barHeight, GREY_DEFAULT)
if titleShowWarn then
- lcd.drawText(textXoffset, 1, elrsFlagsInfo, INVERS)
+ lcd.drawText(COL1, 1, elrsFlagsInfo, INVERS)
else
local title = fields_count > 0 and deviceName or "Loading..."
- lcd.drawText(textXoffset, 1, title, INVERS)
+ lcd.drawText(COL1, 1, title, INVERS)
end
end
end
local function lcd_warn()
- lcd.drawText(textXoffset, textSize*2, "Error:")
- lcd.drawText(textXoffset, textSize*3, elrsFlagsInfo)
+ lcd.drawText(COL1, textSize*2, "Error:")
+ lcd.drawText(COL1, textSize*3, elrsFlagsInfo)
lcd.drawText(LCD_W/2, textSize*5, "[OK]", BLINK + INVERS + CENTER)
end
@@ -670,7 +648,7 @@ local function reloadCurField()
local field = getField(lineIndex)
fieldTimeout = 0
fieldChunk = 0
- fieldData = {}
+ fieldData = nil
loadQ[#loadQ+1] = field.id
end
@@ -685,9 +663,10 @@ local function reloadRelatedFields(field)
for fieldId = fields_count, 1, -1 do
-- Skip this field, will be added to end
local fldTest = fields[fieldId]
+ local fldType = fldTest.type or 99 -- type could be nil if still loading
if fieldId ~= field.id
and fldTest.parent == field.parent
- and (fldTest.type or 99) < 11 then -- type could be nil if still loading
+ and (fldType < 11 or fldType == 12) then -- ignores FOLDER/COMMAND/devices/EXIT
fldTest.nc = true -- "no cache" the options
loadQ[#loadQ+1] = fieldId
end
@@ -703,7 +682,7 @@ local function handleDevicePageEvent(event)
if #fields == 0 then --if there is no field yet
return
else
- if fields[backButtonId].name == nil then --if back button is not assigned yet, means there is no field yet.
+ if fields[#fields].name == nil then --if back button is not assigned yet, means there is no field yet.
return
end
end
@@ -713,15 +692,16 @@ local function handleDevicePageEvent(event)
edit = nil
reloadCurField()
else
- if folderAccess == nil and #loadQ == 0 then -- only do reload if we're in the root folder and finished loading
+ if currentFolderId == nil and #loadQ == 0 then -- only do reload if we're in the root folder and finished loading
if deviceId ~= 0xEE then
changeDeviceId(0xEE) --change device id clear the fields_count, therefore the next ping will do reloadAllField()
else
reloadAllField()
end
crossfireTelemetryPush(0x28, { 0x00, 0xEA })
+ else
+ fieldBackExec(fields[#fields])
end
- UIbackExec()
end
elseif event == EVT_VIRTUAL_ENTER then -- toggle editing/selecting current field
if elrsFlags > 0x1F then
@@ -783,7 +763,7 @@ local function runDevicePage(event)
and ((edit and BLINK or 0) + INVERS)
or 0
if field.type < 11 or field.type == 12 then -- if not folder, command, or back
- lcd.drawText(textXoffset, y*textSize+textYoffset, field.name, 0)
+ lcd.drawText(COL1, y*textSize+textYoffset, field.name, 0)
end
if functions[field.type+1].display then
functions[field.type+1].display(field, y*textSize+textYoffset, attr)
@@ -804,13 +784,12 @@ local function runPopupPage(event)
fieldTimeout = getTime() + 200 -- 2s
end
- local result
if fieldPopup.status == 0 and fieldPopup.lastStatus ~= 0 then -- stopped
popupCompat(fieldPopup.info, "Stopped!", event)
reloadAllField()
fieldPopup = nil
elseif fieldPopup.status == 3 then -- confirmation required
- result = popupCompat(fieldPopup.info, "PRESS [OK] to confirm", event)
+ local result = popupCompat(fieldPopup.info, "PRESS [OK] to confirm", event)
fieldPopup.lastStatus = fieldPopup.status
if result == "OK" then
crossfireTelemetryPush(0x2D, { deviceId, handsetId, fieldPopup.id, 4 }) -- lcsConfirmed
@@ -823,7 +802,7 @@ local function runPopupPage(event)
if fieldChunk == 0 then
commandRunningIndicator = (commandRunningIndicator % 4) + 1
end
- result = popupCompat(fieldPopup.info .. " [" .. string.sub("|/-\\", commandRunningIndicator, commandRunningIndicator) .. "]", "Press [RTN] to exit", event)
+ local result = popupCompat(fieldPopup.info .. " [" .. string.sub("|/-\\", commandRunningIndicator, commandRunningIndicator) .. "]", "Press [RTN] to exit", event)
fieldPopup.lastStatus = fieldPopup.status
if result == "CANCEL" then
crossfireTelemetryPush(0x2D, { deviceId, handsetId, fieldPopup.id, 5 }) -- lcsCancel
@@ -833,22 +812,6 @@ local function runPopupPage(event)
end
end
-local function loadSymbolChars()
- -- On firmwares that have constants defined for the arrow chars, use them in place of
- -- the \xc0 \xc1 chars (which are OpenTX-en)
- if __opentx then
- byteToStr = function (b)
- -- Use the table to convert the char, else use string.char if not in the table
- return ({
- [192] = __opentx.CHAR_UP,
- [193] = __opentx.CHAR_DOWN
- })[b] or string.char(b)
- end
- else
- byteToStr = string.char
- end
-end
-
local function touch2evt(event, touchState)
-- Convert swipe events to normal events Left/Right/Up/Down -> EXIT/ENTER/PREV/NEXT
-- PREV/NEXT are swapped if editing
@@ -866,33 +829,33 @@ local function setLCDvar()
-- set textselection unit function, use GetLastPost or sizeText
if (lcd.RGB ~= nil) then
lcd_title = lcd_title_color
- functions[10].display=fieldTextSelectionDisplay_color
+ functions[10].display = fieldTextSelDisplay_color
else
lcd_title = lcd_title_bw
- functions[10].display=fieldTextSelectionDisplay_bw
+ functions[10].display = fieldTextSelDisplay_bw
touch2evt = nil
end
lcd_title_color = nil
lcd_title_bw = nil
- fieldTextSelectionDisplay_bw = nil
- fieldTextSelectionDisplay_color = nil
- -- Determine if popupConfirmation takes 3 arguments or 2
+ fieldTextSelDisplay_bw = nil
+ fieldTextSelDisplay_color = nil
+ -- Determine if popupConfirmation takes 3 arguments or 2
-- if pcall(popupConfirmation, "", "", EVT_VIRTUAL_EXIT) then
-- major 1 is assumed to be FreedomTX
- local ver, radio, major = getVersion()
+ local _, _, major = getVersion()
if major ~= 1 then
popupCompat = popupConfirmation
end
if LCD_W == 480 then
+ COL1 = 3
COL2 = 240
maxLineIndex = 10
- textXoffset = 3
textYoffset = 10
textSize = 22 --textSize is text Height
elseif LCD_W == 320 then
+ COL1 = 3
COL2 = 160
maxLineIndex = 14
- textXoffset = 3
textYoffset = 10
textSize = 22
else
@@ -901,13 +864,15 @@ local function setLCDvar()
else
COL2 = 70
end
- maxLineIndex = 6
- textXoffset = 0
+ if LCD_H == 96 then
+ maxLineIndex = 9
+ else
+ maxLineIndex = 6
+ end
+ COL1 = 0
textYoffset = 3
textSize = 8
end
- loadSymbolChars()
- loadSymbolChars = nil
end
local function setMock()
@@ -916,15 +881,10 @@ local function setMock()
if string.sub(rv, -5) ~= "-simu" then return end
local mock = loadScript("mockup/elrsmock.lua")
if mock == nil then return end
- fields, goodBadPkt, deviceName = mock(), "0/500 C", "ExpressLRS TX"
+ fields, goodBadPkt, deviceName = mock()
fields_count = #fields - 1
loadQ = { fields_count }
deviceIsELRS_TX = true
- backButtonId = #fields
-
- fields_count = fields_count + 1
- exitButtonId = fields_count + 1
- fields[exitButtonId] = {id = exitButtonId, name="----EXIT----", type=17}
end
-- Init
diff --git a/src/lua/mockup/elrsmock.lua b/src/lua/mockup/elrsmock.lua
index 3feac2d5e9..00b95a46c1 100644
--- a/src/lua/mockup/elrsmock.lua
+++ b/src/lua/mockup/elrsmock.lua
@@ -1,22 +1,24 @@
return {
- {name='Packet Rate', id=0, type=9, values={'250(-108dBm)','500(-105dBm)'}, value=1, unit='Hz'},
- {name='Telem Ratio', id=1, type=9, values={'Off','1:128'}, value=1, unit=''},
- {name='Switch Mode', id=2, type=9, values={'Hybrid','Wide'}, value=1, unit=''},
- {name='Model Match', id=3, type=9, values={'Off','On'}, value=0, unit=''},
- {name='TX Power', id=4, type=11},
- {name='Max Power', id=5, type=9, parent=4, values={'10','25','50'}, value=2, unit='mW'},
- {name='Dynamic', id=6, type=9, parent=4, values={'Off','On','AUX9'}, value=1, unit=''},
- {name='Fan Thresh', id=6, type=9, parent=4, values={'10mW','25mW','50mW', '250mW'}, value=3, unit=''},
- {name='VTX Administrator', id=7, type=11},
- {name='Band', id=8, type=9, parent=7, values={'Off', 'A', 'B', 'F', 'R', 'L'}, value=0, unit=''},
- {name='Channel', id=9, type=9, parent=7, values={'1', '2', '3', '4' }, value=0, unit=''},
- {name='Pwr Lvl', id=10, type=9, parent=7, values={'-', '1', '2', '3' }, value=0, unit=''},
- {name='Pitmode', id=11, type=9, parent=7, values={'Off', 'On'}, value=0, unit=''},
- {name='Send VTx', id=12, type=13, parent=7},
- {name='Bind', id=8, type=13},
- {name='Wifi Update', id=9, type=13},
- {name='BLE Joystick', id=10, type=13},
- {name='master', id=11, type=12, value='f00fcb'},
+ {name='Packet Rate', id=0, type=9, values={'250(-108dBm)','500(-105dBm)'}, value=1, unit='Hz'},
+ {name='Telem Ratio', id=1, type=9, values={'Off','1:128'}, value=1, unit=''},
+ {name='Switch Mode', id=2, type=9, values={'Hybrid','Wide'}, value=1, unit=''},
+ {name='Model Match', id=3, type=9, values={'Off','On'}, value=0, unit=''},
+ {name='TX Power', id=4, type=11},
+ {name='Max Power', id=5, type=9, parent=4, values={'10','25','50'}, value=2, unit='mW'},
+ {name='Dynamic', id=6, type=9, parent=4, values={'Off','On','AUX9'}, value=1, unit=''},
+ {name='Fan Thresh', id=7, type=9, parent=4, values={'10mW','25mW','50mW', '250mW'}, value=3, unit=''},
+ {name='VTX Administrator', id=8, type=11},
+ {name='Band', id=9, type=9, parent=8, values={'Off', 'A', 'B', 'F', 'R', 'L'}, value=0, unit=''},
+ {name='Channel', id=10, type=0, parent=8, value=1, step=1, min=1, max=8, unit=''},
+ {name='Pwr Lvl', id=11, type=9, parent=8, values={'-', '1', '2', '3' }, value=0, unit=''},
+ {name='Pitmode', id=12, type=9, parent=8, values={'Off', 'On'}, value=0, unit=''},
+ {name='Send VTx', id=13, type=13, parent=8},
+ {name='Bind', id=14, type=13},
+ {name='Wifi Update', id=15, type=13},
+ {name='BLE Joystick', id=16, type=13},
+ {name='master', id=17, type=16, value='f00fcb'},
+ {name='Float Tst', id=18, type=8, value=-15, step=5, prec=1000, min=-50, max=50, unit='flt', fmt='%.3fflt'},
- {name="----BACK----", id=12, type=14, parent=255}
- }
\ No newline at end of file
+ {name="----BACK----", type=14, parent=255},
+ {name="----EXIT----", type=14, exit = true}
+}, "0/500 C", "ExpressLRS TX"
\ No newline at end of file
diff --git a/src/platformio.ini b/src/platformio.ini
index da6bcb948d..9c9e4f1106 100644
--- a/src/platformio.ini
+++ b/src/platformio.ini
@@ -5,34 +5,16 @@ extra_configs =
# keep common first
targets/common.ini
# defined one by one to maintain the order
- targets/ELRS_2400.ini
- targets/axis_2400.ini
+ targets/unified.ini
+ targets/diy_900.ini
+ targets/diy_2400.ini
targets/frsky.ini
- targets/emax_900.ini
- targets/emax_2400.ini
targets/happymodel_900.ini
targets/happymodel_2400.ini
- targets/betafpv_900.ini
- targets/betafpv_2400.ini
targets/imrc.ini
targets/namimnorc_900.ini
targets/namimnorc_2400.ini
- targets/neutronrc_900.ini
targets/siyi.ini
- targets/HGLRC_900.ini
- targets/HGLRC_2400.ini
- targets/quadkopters_2400.ini
- targets/diy_900.ini
- targets/diy_2400.ini
- targets/diy_ble_joystick.ini
- targets/MATEK_2400.ini
- targets/iFlight_900.ini
- targets/iFlight_2400.ini
- targets/vantac_2400.ini
- targets/Jumper_2400.ini
- targets/radiomaster_2400.ini
- targets/foxeer_2400.ini
- targets/unified.ini
# ------------------------- TARGET ENV DEFINITIONS -----------------
@@ -40,7 +22,7 @@ extra_configs =
platform = native
framework =
test_ignore = test_embedded
-lib_ignore = BUTTON, DAC, LQCALC, LBT
+lib_ignore = BUTTON, DAC, LQCALC, LBT, SPIEx, PWM
build_src_filter = ${common_env_data.build_src_filter} - - - - - - -
build_flags =
-std=c++11
diff --git a/src/python/UnifiedConfiguration.py b/src/python/UnifiedConfiguration.py
index dabaf072d9..3718644bd6 100644
--- a/src/python/UnifiedConfiguration.py
+++ b/src/python/UnifiedConfiguration.py
@@ -47,11 +47,17 @@ def appendToFirmware(firmware_file, product_name, lua_name, defines, config, lay
hardware = json.load(h)
if 'overlay' in config:
hardware.update(config['overlay'])
- firmware_file.write(json.JSONEncoder().encode(hardware).encode())
+ layout = (json.JSONEncoder().encode(hardware).encode() + (b'\0' * 2048))[0:2048]
+ firmware_file.write(layout)
except EnvironmentError:
sys.stderr.write(f'Error opening file "{layout_file}"\n')
exit(1)
- firmware_file.write(b'\0')
+ else:
+ firmware_file.write(b'\0' * 2048)
+ if config is not None and 'logo_file' in config:
+ logo_file = f"hardware/logo/{config['logo_file']}"
+ with open(logo_file, 'rb') as f:
+ firmware_file.write(f.read())
if config is not None and 'prior_target_name' in config:
firmware_file.write(b'\xBE\xEF\xCA\xFE')
firmware_file.write(config['prior_target_name'].upper().encode())
@@ -106,12 +112,17 @@ def appendConfiguration(source, target, env):
frequency = ''
if config is not None:
moduletype = 'tx' if '.tx_' in config else 'rx'
- frequency = '2400' if '_2400.' in config else '900'
+ frequency = '2400' if '_2400.' in config else '900' if '_900.' in config else 'dual'
else:
moduletype = 'tx' if '_TX_' in target_name else 'rx'
- frequency = '2400' if '_2400_' in target_name else '900'
+ frequency = '2400' if '_2400_' in target_name else '900' if '_900_' in target_name else 'dual'
- platform = 'esp32' if env.get('PIOPLATFORM', '') in ['espressif32'] else 'esp8285'
+ if env.get('PIOPLATFORM', '') == 'espressif32':
+ platform = 'esp32'
+ if 'esp32-s3' in env.get('BOARD', ''):
+ platform = 'esp32-s3'
+ else:
+ platform = 'esp8285'
defines = json.JSONEncoder().encode(env['OPTIONS_JSON'])
diff --git a/src/python/binary_configurator.py b/src/python/binary_configurator.py
index 9d43d9cb74..abcca00c0b 100644
--- a/src/python/binary_configurator.py
+++ b/src/python/binary_configurator.py
@@ -29,6 +29,8 @@ def __str__(self):
return self.value
class RegulatoryDomain(Enum):
+ us_433 = 'us_433'
+ us_433_wide = 'us_433_wide'
eu_433 = 'eu_433'
au_433 = 'au_433'
in_866 = 'in_866'
@@ -72,7 +74,9 @@ def generateUID(phrase):
int(item) if item.isdigit() else -1
for item in phrase.split(',')
]
- if len(uid) == 6 and all(ele >= 0 and ele < 256 for ele in uid):
+ if (4 <= len(uid) <= 6) and all(ele >= 0 and ele < 256 for ele in uid):
+ # Extend the UID to 6 bytes, as only 4 are needed to bind
+ uid = [0] * (6 - len(uid)) + uid
uid = bytes(uid)
else:
uid = hashlib.md5(("-DMY_BINDING_PHRASE=\""+phrase+"\"").encode()).digest()[0:6]
@@ -85,6 +89,9 @@ def patch_uid(mm, pos, args):
pos += 7
return pos
+def patch_flash_discriminator(mm, pos, args):
+ return write32(mm, pos, args.flash_discriminator)
+
def patch_wifi(mm, pos, args):
interval = None
if args.no_auto_wifi:
@@ -193,6 +200,10 @@ def domain_number(domain):
return 4
elif domain == RegulatoryDomain.eu_433:
return 5
+ elif domain == RegulatoryDomain.us_433:
+ return 6
+ elif domain == RegulatoryDomain.us_433_wide:
+ return 7
def patch_firmware(options, mm, pos, args):
if options.mcuType is MCUType.STM32:
@@ -200,6 +211,7 @@ def patch_firmware(options, mm, pos, args):
mm[pos] = domain_number(args.domain)
pos += 1
pos = patch_uid(mm, pos, args)
+ pos = patch_flash_discriminator(mm, pos, args)
if options.deviceType is DeviceType.TX:
pos = patch_tx_params(mm, pos, args, options)
elif options.deviceType is DeviceType.RX:
@@ -247,7 +259,7 @@ def patch_unified(args, options):
JSONEncoder().encode(json_flags),
args.target,
'tx' if options.deviceType is DeviceType.TX else 'rx',
- '2400' if options.radioChip is RadioType.SX1280 else '900',
+ '2400' if options.radioChip is RadioType.SX1280 else '900' if options.radioChip is RadioType.SX127X else 'dual',
'32' if options.mcuType is MCUType.ESP32 and options.deviceType is DeviceType.RX else '',
options.luaName
)
@@ -270,7 +282,7 @@ def ask_for_firmware(args):
config = jmespath.search('.'.join(map(lambda s: f'"{s}"', args.target.split('.'))), targets)
else:
i = 0
- for k in jmespath.search(f'*.["{moduletype}_2400","{moduletype}_900"][].*[]', targets):
+ for k in jmespath.search(f'*.["{moduletype}_2400","{moduletype}_900","{moduletype}_dual"][].*[]', targets):
i += 1
products.append(k)
print(f"{i}) {k['product_name']}")
@@ -317,6 +329,7 @@ def main():
parser.add_argument('--fdir', action=readable_dir, default=None, help='If specified, then the firmware files are loaded from this directory')
# Bind phrase
parser.add_argument('--phrase', type=str, help='Your personal binding phrase')
+ parser.add_argument('--flash-discriminator', type=int, default=randint(1,2**32-1), dest='flash_discriminator', help='Force a fixed flash-descriminator instead of random')
# WiFi Params
parser.add_argument('--ssid', type=length_check(32, "ssid"), required=False, help='Home network SSID')
parser.add_argument('--password', type=length_check(64, "password"), required=False, help='Home network password')
@@ -351,7 +364,7 @@ def main():
parser.add_argument("--force", action='store_true', default=False, help="Force upload even if target does not match")
parser.add_argument("--confirm", action='store_true', default=False, help="Confirm upload if a mismatched target was previously uploaded")
parser.add_argument("--tx", action='store_true', default=False, help="Flash a TX module, RX if not specified")
- parser.add_argument("--lbt", action='store_true', default=False, help="Use LBT firmware, default is FCC (onl for 2.4GHz firmware)")
+ parser.add_argument("--lbt", action='store_true', default=False, help="Use LBT firmware, default is FCC (only for 2.4GHz firmware)")
# Deprecated options, left for backward compatibility
parser.add_argument('--uart-inverted', action=deprecate_action, nargs=0, help='Deprecated')
parser.add_argument('--no-uart-inverted', action=deprecate_action, nargs=0, help='Deprecated')
@@ -392,7 +405,7 @@ def main():
True if 'features' in config and 'buzzer' in config['features'] else False,
MCUType.STM32 if config['platform'] == 'stm32' else MCUType.ESP32 if config['platform'].startswith('esp32') else MCUType.ESP8266,
DeviceType.RX if '.rx_' in args.target else DeviceType.TX,
- RadioType.SX127X if '_900.' in args.target else RadioType.SX1280,
+ RadioType.SX127X if '_900.' in args.target else RadioType.SX1280 if '_2400.' in args.target else RadioType.LR1121,
config['lua_name'] if 'lua_name' in config else '',
config['stlink']['bootloader'] if 'stlink' in config else '',
config['stlink']['offset'] if 'stlink' in config else 0,
@@ -410,6 +423,7 @@ def main():
if args.flash:
args.target = config.get('firmware')
args.accept = config.get('prior_target_name')
+ args.platform = config.get('platform')
return binary_flash.upload(options, args)
elif 'upload_methods' in config and 'stock' in config['upload_methods']:
shutil.copy(args.file.name, 'firmware.elrs')
diff --git a/src/python/binary_flash.py b/src/python/binary_flash.py
index 34858470c1..a884a0ecee 100644
--- a/src/python/binary_flash.py
+++ b/src/python/binary_flash.py
@@ -62,7 +62,8 @@ def upload_stm32_stlink(args, options: FirmwareOptions):
app_start = flash_start + offset
if options.bootloader is not None:
- stlink.program_flash('bootloader/' + options.bootloader, flash_start, erase=True, verify=True, initialize_comms=True)
+ bootloader_dir = '' if args.fdir is None else args.fdir + '/'
+ stlink.program_flash(bootloader_dir + 'bootloader/' + options.bootloader, flash_start, erase=True, verify=True, initialize_comms=True)
stlink.program_flash(args.file.name, app_start, erase=True, verify=True, initialize_comms=True)
@@ -103,9 +104,10 @@ def upload_esp32_uart(args):
args.port = serials_find.get_serial_port()
try:
dir = os.path.dirname(args.file.name)
- cmd = ['--chip', 'esp32', '--port', args.port, '--baud', str(args.baud), '--after', 'hard_reset', 'write_flash']
+ cmd = ['--chip', args.platform.replace('-', ''), '--port', args.port, '--baud', str(args.baud), '--after', 'hard_reset', 'write_flash']
if args.erase: cmd.append('--erase-all')
- cmd.extend(['-z', '--flash_mode', 'dio', '--flash_freq', '40m', '--flash_size', 'detect', '0x1000', os.path.join(dir, 'bootloader.bin'), '0x8000', os.path.join(dir, 'partitions.bin'), '0xe000', os.path.join(dir, 'boot_app0.bin'), '0x10000', args.file.name])
+ start_addr = '0x0000' if args.platform.startswith('esp32-') else '0x1000'
+ cmd.extend(['-z', '--flash_mode', 'dio', '--flash_freq', '40m', '--flash_size', 'detect', start_addr, os.path.join(dir, 'bootloader.bin'), '0x8000', os.path.join(dir, 'partitions.bin'), '0xe000', os.path.join(dir, 'boot_app0.bin'), '0x10000', args.file.name])
esptool.main(cmd)
except:
return ElrsUploadResult.ErrorGeneral
@@ -117,9 +119,10 @@ def upload_esp32_etx(args):
ETXinitPassthrough.etx_passthrough_init(args.port, args.baud)
try:
dir = os.path.dirname(args.file.name)
- cmd = ['--chip', 'esp32', '--port', args.port, '--baud', str(args.baud), '--before', 'no_reset', '--after', 'hard_reset', 'write_flash']
+ cmd = ['--chip', args.platform.replace('-', ''), '--port', args.port, '--baud', str(args.baud), '--before', 'no_reset', '--after', 'hard_reset', 'write_flash']
if args.erase: cmd.append('--erase-all')
- cmd.extend(['-z', '--flash_mode', 'dio', '--flash_freq', '40m', '--flash_size', 'detect', '0x1000', os.path.join(dir, 'bootloader.bin'), '0x8000', os.path.join(dir, 'partitions.bin'), '0xe000', os.path.join(dir, 'boot_app0.bin'), '0x10000', args.file.name])
+ start_addr = '0x0000' if args.platform.startswith('esp32-') else '0x1000'
+ cmd.extend(['-z', '--flash_mode', 'dio', '--flash_freq', '40m', '--flash_size', 'detect', start_addr, os.path.join(dir, 'bootloader.bin'), '0x8000', os.path.join(dir, 'partitions.bin'), '0xe000', os.path.join(dir, 'boot_app0.bin'), '0x10000', args.file.name])
esptool.main(cmd)
except:
return ElrsUploadResult.ErrorGeneral
@@ -135,7 +138,7 @@ def upload_esp32_bf(args, options):
if retval != ElrsUploadResult.Success:
return retval
try:
- esptool.main(['--passthrough', '--chip', 'esp32', '--port', args.port, '--baud', str(args.baud), '--before', 'no_reset', '--after', 'hard_reset', 'write_flash', '-z', '--flash_mode', 'dio', '--flash_freq', '40m', '--flash_size', 'detect', '0x10000', args.file.name])
+ esptool.main(['--passthrough', '--chip', args.platform.replace('-', ''), '--port', args.port, '--baud', str(args.baud), '--before', 'no_reset', '--after', 'hard_reset', 'write_flash', '-z', '--flash_mode', 'dio', '--flash_freq', '40m', '--flash_size', 'detect', '0x10000', args.file.name])
except:
return ElrsUploadResult.ErrorGeneral
return ElrsUploadResult.Success
@@ -180,7 +183,12 @@ def upload(options: FirmwareOptions, args):
elif args.flash == UploadMethod.stlink: # untested
return upload_stm32_stlink(args, options)
else:
- if options.mcuType == MCUType.ESP32:
+ if options.mcuType == MCUType.ESP8266:
+ if args.flash == UploadMethod.uart:
+ return upload_esp8266_uart(args)
+ elif args.flash == UploadMethod.wifi:
+ return upload_wifi(args, options, ['elrs_tx', 'elrs_tx.local'], False)
+ elif options.mcuType == MCUType.ESP32:
if args.flash == UploadMethod.edgetx:
return upload_esp32_etx(args)
elif args.flash == UploadMethod.uart:
diff --git a/src/python/build_env_setup.py b/src/python/build_env_setup.py
index 46504ccc89..60d9f0149a 100644
--- a/src/python/build_env_setup.py
+++ b/src/python/build_env_setup.py
@@ -6,6 +6,7 @@
import opentx
import upload_via_esp8266_backpack
import esp_compress
+import elrs_helpers
import BFinitPassthrough
import ETXinitPassthrough
import UnifiedConfiguration
@@ -143,3 +144,7 @@ def copyBootApp0bin(source, target, env):
if platform in ['espressif32']:
env.AddPreAction("$BUILD_DIR/${PROGNAME}.bin", copyBootApp0bin)
+
+if platform in ['espressif32', 'espressif8266']:
+ if not os.path.exists('hardware'):
+ elrs_helpers.git_cmd('clone', 'https://github.com/ExpressLRS/targets', 'hardware')
diff --git a/src/python/build_flags.py b/src/python/build_flags.py
index 7274f73c44..ca2efbaa1c 100644
--- a/src/python/build_flags.py
+++ b/src/python/build_flags.py
@@ -36,7 +36,7 @@ def dequote(str):
return str
def process_json_flag(define):
- parts = re.search("-D(.*)\s*=\s*(.*)$", define)
+ parts = re.search(r"-D(.*)\s*=\s*(.*)$", define)
if parts and define.startswith("-D"):
if parts.group(1) == "MY_BINDING_PHRASE":
json_flags['uid'] = [x for x in hashlib.md5(define.encode()).digest()[0:6]]
@@ -45,26 +45,24 @@ def process_json_flag(define):
if parts.group(1) == "HOME_WIFI_PASSWORD":
json_flags['wifi-password'] = dequote(parts.group(2))
if parts.group(1) == "AUTO_WIFI_ON_INTERVAL":
- parts = re.search("-D(.*)\s*=\s*\"?([0-9]+).*\"?$", define)
+ parts = re.search(r"-D(.*)\s*=\s*\"?([0-9]+).*\"?$", define)
json_flags['wifi-on-interval'] = int(dequote(parts.group(2)))
if parts.group(1) == "TLM_REPORT_INTERVAL_MS" and not isRX:
- parts = re.search("-D(.*)\s*=\s*\"?([0-9]+).*\"?$", define)
+ parts = re.search(r"-D(.*)\s*=\s*\"?([0-9]+).*\"?$", define)
json_flags['tlm-interval'] = int(dequote(parts.group(2)))
if parts.group(1) == "FAN_MIN_RUNTIME" and not isRX:
- parts = re.search("-D(.*)\s*=\s*\"?([0-9]+).*\"?$", define)
+ parts = re.search(r"-D(.*)\s*=\s*\"?([0-9]+).*\"?$", define)
json_flags['fan-runtime'] = int(dequote(parts.group(2)))
if parts.group(1) == "RCVR_UART_BAUD" and isRX:
- parts = re.search("-D(.*)\s*=\s*\"?([0-9]+).*\"?$", define)
+ parts = re.search(r"-D(.*)\s*=\s*\"?([0-9]+).*\"?$", define)
json_flags['rcvr-uart-baud'] = int(dequote(parts.group(2)))
if parts.group(1) == "USE_AIRPORT_AT_BAUD":
- parts = re.search("-D(.*)\s*=\s*\"?([0-9]+).*\"?$", define)
+ parts = re.search(r"-D(.*)\s*=\s*\"?([0-9]+).*\"?$", define)
json_flags['is-airport'] = True
if isRX:
json_flags['rcvr-uart-baud'] = int(dequote(parts.group(2)))
else:
json_flags['airport-uart-baud'] = int(dequote(parts.group(2)))
- if define == "-DUART_INVERTED" and not isRX:
- json_flags['uart-inverted'] = True
if define == "-DUNLOCK_HIGHER_POWER" and not isRX:
json_flags['unlock-higher-power'] = True
if define == "-DLOCK_ON_FIRST_CONNECTION" and isRX:
@@ -83,15 +81,15 @@ def process_build_flag(define):
parsedMelody = melodyparser.parse(define.split('"')[1::2][0])
define = "-DMY_STARTUP_MELODY_ARR=\"" + parsedMelody + "\""
if "HOME_WIFI_SSID=" in define:
- parts = re.search("(.*)=\w*\"(.*)\"$", define)
+ parts = re.search(r"(.*)=\w*\"(.*)\"$", define)
if parts and parts.group(2):
define = "-DHOME_WIFI_SSID=" + string_to_ascii(parts.group(2))
if "HOME_WIFI_PASSWORD=" in define:
- parts = re.search("(.*)=\w*\"(.*)\"$", define)
+ parts = re.search(r"(.*)=\w*\"(.*)\"$", define)
if parts and parts.group(2):
define = "-DHOME_WIFI_PASSWORD=" + string_to_ascii(parts.group(2))
if "DEVICE_NAME=" in define:
- parts = re.search("(.*)=\w*'?\"(.*)\"'?$", define)
+ parts = re.search(r"(.*)=\w*'?\"(.*)\"'?$", define)
if parts and parts.group(2):
env['DEVICE_NAME'] = parts.group(2)
if not define in build_flags:
@@ -120,7 +118,7 @@ def condense_flags():
global build_flags
for line in build_flags:
# Some lines have multiple flags so this will split them and remove them all
- for flag in re.findall("!-D\s*[^\s]+", line):
+ for flag in re.findall(r"!-D\s*[^\s]+", line):
build_flags = [x.replace(flag,"") for x in build_flags] # remove the removal flag
build_flags = [x.replace(flag[1:],"") for x in build_flags] # remove the flag if it matches the removal flag
build_flags = [x for x in build_flags if (x.strip() != "")] # remove any blank items
@@ -138,6 +136,9 @@ def get_git_sha():
def get_version():
return string_to_ascii(env.get('GIT_VERSION'))
+json_flags['flash-discriminator'] = randint(1,2**32-1)
+json_flags['wifi-on-interval'] = -1
+
process_flags("user_defines.txt")
process_flags("super_defines.txt") # allow secret super_defines to override user_defines
version_to_env()
@@ -146,13 +147,11 @@ def get_version():
build_flags.append("-DTARGET_NAME=" + re.sub("_VIA_.*", "", target_name))
condense_flags()
-json_flags['flash-discriminator'] = randint(1,2**32-1)
-
-if '-DRADIO_SX127X=1' in build_flags:
+if '-DRADIO_SX127X=1' in build_flags or '-DRADIO_LR1121=1' in build_flags:
# disallow setting 2400s for 900
if fnmatch.filter(build_flags, '*-DRegulatory_Domain_ISM_2400') or \
fnmatch.filter(build_flags, '*-DRegulatory_Domain_EU_CE_2400'):
- print_error('Regulatory_Domain 2400 not compatible with RADIO_SX127X')
+ print_error('Regulatory_Domain 2400 not compatible with RADIO_SX127X/RADIO_LR1121')
# require a domain be set for 900
if not fnmatch.filter(build_flags, '*-DRegulatory_Domain*'):
@@ -170,6 +169,10 @@ def get_version():
json_flags['domain'] = 4
if fnmatch.filter(build_flags, '*-DRegulatory_Domain_EU_433'):
json_flags['domain'] = 5
+ if fnmatch.filter(build_flags, '*-DRegulatory_Domain_US_433'):
+ json_flags['domain'] = 6
+ if fnmatch.filter(build_flags, '*-DRegulatory_Domain_US_433_WIDE'):
+ json_flags['domain'] = 7
else:
json_flags['domain'] = 0
@@ -201,3 +204,7 @@ def get_version():
stm = env.get('PIOPLATFORM', '') in ['ststm32']
if stm:
env['UPLOAD_PROTOCOL'] = 'custom'
+ # -DFLASH_DISCRIM=xxxx can't be passed on the command line or it will every file to
+ # always be rebuilt, so put it in a header that options.cpp can include
+ print(f"#define FLASH_DISCRIM {json_flags['flash-discriminator']}",
+ file=open("include/flashdiscrim.h", "w"))
diff --git a/src/python/build_html.py b/src/python/build_html.py
index 05823ec898..7038f0e97f 100644
--- a/src/python/build_html.py
+++ b/src/python/build_html.py
@@ -33,11 +33,12 @@ def build_html(mainfile, var, out, env, isTX=False):
extensions=[CoreExtension("@@")]
)
template = engine.get_template(mainfile)
+ has_sub_ghz = '-DRADIO_SX127X=1' in env['BUILD_FLAGS'] or '-DRADIO_LR1121=1' in env['BUILD_FLAGS']
data = template.render({
'VERSION': get_version(env),
'PLATFORM': re.sub("_via_.*", "", env['PIOENV']),
'isTX': isTX,
- 'sx127x': '-DRADIO_SX127X=1' in env['BUILD_FLAGS']
+ 'hasSubGHz': has_sub_ghz
})
if mainfile.endswith('.html'):
data = html_minifier.html_minify(data)
diff --git a/src/python/firmware.py b/src/python/firmware.py
index 5aeed40b5c..a684da1d9f 100644
--- a/src/python/firmware.py
+++ b/src/python/firmware.py
@@ -17,6 +17,7 @@ class DeviceType(Enum):
class RadioType(Enum):
SX127X = 0
SX1280 = 1
+ LR1121 = 2
class FirmwareOptions(NamedTuple):
hasWiFi: bool
diff --git a/src/python/pyproject.toml b/src/python/pyproject.toml
deleted file mode 100644
index 67579b9971..0000000000
--- a/src/python/pyproject.toml
+++ /dev/null
@@ -1,21 +0,0 @@
-[project]
-name = "binary_configurator"
-version = "3.3.0"
-description = "ExpressLRS Binary Installer"
-requires-python = ">=3.6"
-authors = [
- { name = "ExpressLRS Team" }
-]
-dependencies = [
- "pyserial"
-]
-classifiers = [
- "Programming Language :: Python :: 3",
- "Operating System :: OS Independent",
-]
-
-[tool.poetry]
-packages = [
- { include = "." },
- { include = "external" },
-]
diff --git a/src/python/serials_find.py b/src/python/serials_find.py
index 74baa17a67..649ca0a4ce 100644
--- a/src/python/serials_find.py
+++ b/src/python/serials_find.py
@@ -42,6 +42,7 @@ def serial_ports():
ports = glob.glob('/dev/tty.usbmodem*')
ports.extend(glob.glob('/dev/tty.SLAB*'))
ports.extend(glob.glob('/dev/tty.usbserial*'))
+ ports.extend(glob.glob('/dev/tty.wchusbserial*'))
else:
raise Exception('Unsupported platform')
@@ -52,7 +53,7 @@ def serial_ports():
result.append(port)
except (OSError, serial.SerialException) as error:
if "permission denied" in str(error).lower():
- raise Exception("You don't have persmission to use serial port!")
+ raise Exception("You don't have permission to use serial port!")
pass
result.reverse()
return result
diff --git a/src/python/serve_html.py b/src/python/serve_html.py
index a091c4902f..86b56207b6 100644
--- a/src/python/serve_html.py
+++ b/src/python/serve_html.py
@@ -7,7 +7,7 @@
open http://localhost:8080/
add the following query params for TX and/or 900Mhz testing
isTX
- sx127x
+ hasSubGHz
"""
from external.bottle import route, run, response, request
@@ -17,11 +17,11 @@
net_counter = 0
isTX = False
-sx127x = False
+hasSubGHz = False
config = {
"options": {
- 'uid': [1,2,3,4,5,6],
+ "uid": [1,2,3,4,5,6], # this is the 'flashed' UID and may be empty if using traditional binding on an RX.
"tlm-interval": 240,
"fan-runtime": 30,
"no-sync-on-arm": False,
@@ -32,35 +32,42 @@
"rcvr-invert-tx": False,
"lock-on-first-connection": True,
"domain": 1,
- "wifi-on-interval": 60,
+ # "wifi-on-interval": 60,
"wifi-password": "w1f1-pAssw0rd",
"wifi-ssid": "network-ssid"
},
"config": {
+ "uid": [1,2,3,4,5,6], # this is the 'running' UID
"uidtype": "On loan",
"ssid":"network-ssid",
"mode":"STA",
"modelid":255,
"pwm":[
{
- "config": 512,
- "pin": 0
+ # 10fs 4ch 1inv 4mode 1narrow
+ "config": 0 + 0<<10 + 0<14 + 0<<15 + 0<<19,
+ "pin": 0,
+ "features": 12
},
{
"config": 1536,
- "pin": 4
+ "pin": 4,
+ "features": 12 + 16
},
{
"config": 2048,
- "pin": 5
+ "pin": 5,
+ "features": 12 + 16
},
{
"config": 3584,
- "pin": 1
+ "pin": 1,
+ "features": 1 + 16
},
{
"config": 4608,
- "pin": 3
+ "pin": 3,
+ "features": 2 + 16
}
],
"serial-protocol": 3,
@@ -85,7 +92,7 @@
]
},
{
- "color" : 224,
+ #"color" : 224, # No color for you button 2
"action": [
{
"is-long-press": False,
@@ -104,7 +111,7 @@
}
def apply_template(mainfile):
- global isTX, sx127x
+ global isTX, hasSubGHz
engine = Engine(
loader=FileLoader(["html"]),
extensions=[CoreExtension("@@")]
@@ -112,18 +119,18 @@ def apply_template(mainfile):
template = engine.get_template(mainfile)
data = template.render({
'VERSION': 'testing (xxxxxx)',
- 'PLATFORM': '',
+ 'PLATFORM': 'Unified_ESP8285',
'isTX': isTX,
- 'sx127x': sx127x
+ 'hasSubGHz': hasSubGHz
})
return data
@route('/')
def index():
- global net_counter, isTX, sx127x
+ global net_counter, isTX, hasSubGHz
net_counter = 0
isTX = 'isTX' in request.query
- sx127x = 'sx127x' in request.query
+ hasSubGHz = 'hasSubGHz' in request.query
response.content_type = 'text/html; charset=latin9'
return apply_template('index.html')
@@ -162,7 +169,11 @@ def update_config():
if 'button-actions' in request.json:
config['config']['button-actions'] = request.json['button-actions']
if 'pwm' in request.json:
- config['config']['pwm'] = request.json['pwm']
+ i=0
+ for x in request.json['pwm']:
+ print(x)
+ config['config']['pwm'][i]['config'] = x
+ i = i + 1
if 'protocol' in request.json:
config['config']['serial-protocol'] = request.json['protocol']
if 'modelid' in request.json:
@@ -171,6 +182,11 @@ def update_config():
config['config']['force-tlm'] = request.json['forcetlm']
return "Config Updated"
+@route('/options.json', method='POST')
+def update_options():
+ config['options'] = request.json
+ return "Options Updated"
+
@route('/import', method='POST')
def import_config():
json = request.json
@@ -188,6 +204,7 @@ def mode():
net_counter = net_counter + 1
if (net_counter > 3):
return '["Test Network 1", "Test Network 2", "Test Network 3", "Test Network 4", "Test Network 5"]'
+ response.status = 204
return '[]'
if __name__ == '__main__':
diff --git a/src/python/setup.py b/src/python/setup.py
index 40a3ad9d18..1de3cf0cff 100644
--- a/src/python/setup.py
+++ b/src/python/setup.py
@@ -1,7 +1,7 @@
import setuptools
setuptools.setup(
name="binary_configurator",
- version="3.3.0",
+ version="3.4.0",
author="ExpressLRS Team",
author_email="",
description="ExpressLRS Binary Installer",
diff --git a/src/python/targets_validator.py b/src/python/targets_validator.py
deleted file mode 100644
index f3497a37fb..0000000000
--- a/src/python/targets_validator.py
+++ /dev/null
@@ -1,169 +0,0 @@
-import os, sys
-import json
-import glob
-import argparse
-
-hadError = False
-warnEnabled = False
-firmwares = set()
-
-def error(msg):
- global hadError
- hadError = True
- print(msg)
-
-def warn(msg):
- if warnEnabled:
- global hadError
- hadError = True
- print(msg)
-
-def validate_stm32(vendor, type, devname, device):
- for method in device['upload_methods']:
- if method not in ['stlink', 'dfu', 'uart', 'wifi', 'betaflight', 'stock']:
- error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"')
- if 'stlink' not in device['upload_methods']:
- error(f'STM32 based devices must always have "stlink" as an upload_method for target "{vendor}.{type}.{devname}"')
- if 'stlink' not in device:
- error(f'STM32 based devices must always have "stlink" attribute for target "{vendor}.{type}.{devname}"')
- else:
- stlink = device['stlink']
- if 'cpus' not in stlink:
- error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a list of valid "cpus"')
- if 'offset' not in stlink:
- error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a valid "offset"')
- if 'bootloader' not in stlink:
- error(f'The "stlink" attribute for target "{vendor}.{type}.{devname}" must have a valid "bootloader"')
- # could check the existence of the bootloader file
-
-def validate_esp(vendor, type, devname, device):
- if 'lua_name' not in device:
- error(f'device "{vendor}.{type}.{devname}" must have a "lua_name" child element')
- if len(device['lua_name']) > 16:
- error(f'device "{vendor}.{type}.{devname}" must have a "lua_name" of 16 characters or less')
- # validate layout_file
- if not device['firmware'].startswith('Unified'):
- error(f'ESP target "{vendor}.{type}.{devname}" must be using a Unified firmware')
- if 'layout_file' not in device:
- error(f'device "{vendor}.{type}.{devname}" must have a "layout_file" child element')
- else:
- dir = 'hardware/' + ('RX/' if type.startswith('rx') else 'TX/')
- if not os.path.isfile(dir + device['layout_file']):
- layout_file = device['layout_file']
- error(f'File specified by layout_file "{layout_file}" in target "{vendor}.{type}.{devname}", does not exist')
- # could validate overlay
- if 'prior_target_name' not in device:
- warn(f'device "{vendor}.{type}.{devname}" should have a "prior_target_name" child element')
-
-
-def validate_esp32(vendor, type, devname, device):
- for method in device['upload_methods']:
- if method not in ['uart', 'etx', 'wifi', 'betaflight']:
- error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"')
- validate_esp(vendor, type, devname, device)
-
-def validate_esp8285(vendor, type, devname, device):
- for method in device['upload_methods']:
- if method not in ['uart', 'wifi', 'betaflight']:
- error(f'Invalid upload method "{method}" for target "{vendor}.{type}.{devname}"')
- validate_esp(vendor, type, devname, device)
-
-def validate_devices(vendor, type, devname, device):
- if devname != devname.lower():
- error(f'device tag "{devname}" should be lowercase')
- if 'product_name' not in device:
- error(f'device "{vendor}.{type}.{devname}" must have a "product_name" child element')
- if 'upload_methods' not in device:
- error(f'device "{vendor}.{type}.{devname}" must have a "upload_methods" child element')
-
- if 'firmware' not in device:
- error(f'device "{vendor}.{type}.{devname}" must have a "firmware" child element')
- else:
- firmware = device['firmware']
- if firmware not in firmwares:
- error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}"')
- elif (firmware.endswith('_TX') and 'tx_' not in type):
- error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}", it must be a TX target firmware')
- elif (firmware.endswith('_RX') and 'rx_' not in type):
- error(f'device "{vendor}.{type}.{devname}" has an invalid firmware file "{firmware}", it must be an RX target firmware')
-
- if 'platform' not in device:
- error(f'device "{vendor}.{type}.{devname}" must have a "platform" child element')
- else:
- platform = device['platform']
- if platform == 'stm32':
- validate_stm32(vendor, type, devname, device)
- elif platform == 'esp32':
- validate_esp32(vendor, type, devname, device)
- elif platform == 'esp8285':
- validate_esp8285(vendor, type, devname, device)
- else:
- error(f'invalid platform "{platform}" in device "{vendor}.{type}.{devname}"')
-
- if 'features' in device:
- for feature in device['features']:
- if feature not in ['buzzer', 'unlock-higher-power', 'fan', 'sbus-uart']:
- error(f'features must contain one or more of [\'buzzer\', \'unlock-higher-power\', \'fan\', \'sbus-uart\'], if present in target "{vendor}.{type}.{devname}"')
-
-def validate_vendor(name, types):
- if name != name.lower():
- error(f'vendor tag "{vendor}" should be lowercase')
-
- if 'name' not in types:
- error(f'vendor "{vendor}" must have a "name" child element')
-
- for type in types:
- if type not in ['rx_2400', 'rx_900', 'tx_2400', 'tx_900', 'name']:
- error(f'invalid tag "{type}" in "{vendor}"')
- if type in ['rx_2400', 'rx_900', 'tx_2400', 'tx_900']:
- for device in types[type]:
- validate_devices(name, type, device, types[type][device])
-
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(description="Configure Binary Firmware")
- parser.add_argument("--warn", "-w", action='store_true', default=False, help="Print warnings")
- args = parser.parse_args()
- warnEnabled = args.warn
-
- targets = {}
- with open('hardware/targets.json') as f:
- targets = json.load(f)
-
- for inifile in glob.iglob('targets/*.ini'):
- with open(inifile) as ini:
- for line in ini:
- if line.startswith('[env:'):
- try:
- firmware_file = line[5:line.index('_via_')]
- firmwares.add(firmware_file)
- except ValueError:
- print(line)
- None
-
- for vendor in targets:
- validate_vendor(vendor, targets[vendor])
-
- for inifile in glob.iglob('targets/*.ini'):
- with open(inifile) as ini:
- for line in ini:
- if line.startswith('[env:'):
- target = line
- if line.startswith('board_config'):
- eq = line.find('=')
- board = line[eq+1:].strip()
- parts = board.split('.')
- if len(parts) != 3:
- error(f'{inifile}: board_config must have 3 parts')
- else:
- vendor = parts[0]
- type = parts[1]
- device = parts[2]
- if vendor not in targets:
- error(f'{inifile}: targets.json file does not contain vendor {vendor}')
- elif type not in targets[vendor]:
- error(f'{inifile}: targets.json "{vendor}" does not contain type {type}')
- elif device not in targets[vendor][type]:
- error(f'{inifile}: targets.json "{vendor}.{type}" does not contain device {device}')
- if hadError:
- sys.exit(1)
- sys.exit(0)
\ No newline at end of file
diff --git a/src/src/common.cpp b/src/src/common.cpp
index c1857690da..7f0a505f38 100644
--- a/src/src/common.cpp
+++ b/src/src/common.cpp
@@ -7,38 +7,76 @@
SX127xDriver DMA_ATTR Radio;
expresslrs_mod_settings_s ExpressLRS_AirRateConfig[RATE_MAX] = {
- {0, RADIO_TYPE_SX127x_LORA, RATE_LORA_200HZ, SX127x_BW_500_00_KHZ, SX127x_SF_6, SX127x_CR_4_7, TLM_RATIO_1_64, 4, 5000, 8, OTA4_PACKET_SIZE, 1},
- {1, RADIO_TYPE_SX127x_LORA, RATE_LORA_100HZ_8CH, SX127x_BW_500_00_KHZ, SX127x_SF_6, SX127x_CR_4_8, TLM_RATIO_1_32, 4, 10000, 8, OTA8_PACKET_SIZE, 1},
- {2, RADIO_TYPE_SX127x_LORA, RATE_LORA_100HZ, SX127x_BW_500_00_KHZ, SX127x_SF_7, SX127x_CR_4_7, TLM_RATIO_1_32, 4, 10000, 8, OTA4_PACKET_SIZE, 1},
- {3, RADIO_TYPE_SX127x_LORA, RATE_LORA_50HZ, SX127x_BW_500_00_KHZ, SX127x_SF_8, SX127x_CR_4_7, TLM_RATIO_1_16, 4, 20000, 10, OTA4_PACKET_SIZE, 1},
- {4, RADIO_TYPE_SX127x_LORA, RATE_LORA_25HZ, SX127x_BW_500_00_KHZ, SX127x_SF_9, SX127x_CR_4_7, TLM_RATIO_1_8, 2, 40000, 10, OTA4_PACKET_SIZE, 1},
- {5, RADIO_TYPE_SX127x_LORA, RATE_DVDA_50HZ, SX127x_BW_500_00_KHZ, SX127x_SF_6, SX127x_CR_4_7, TLM_RATIO_1_64, 2, 5000, 8, OTA4_PACKET_SIZE, 4}};
+ {0, RADIO_TYPE_SX127x_LORA, RATE_LORA_200HZ, SX127x_BW_500_00_KHZ, SX127x_SF_6, SX127x_CR_4_7, 8, TLM_RATIO_1_64, 4, 5000, OTA4_PACKET_SIZE, 1},
+ {1, RADIO_TYPE_SX127x_LORA, RATE_LORA_100HZ_8CH, SX127x_BW_500_00_KHZ, SX127x_SF_6, SX127x_CR_4_8, 8, TLM_RATIO_1_32, 4, 10000, OTA8_PACKET_SIZE, 1},
+ {2, RADIO_TYPE_SX127x_LORA, RATE_LORA_100HZ, SX127x_BW_500_00_KHZ, SX127x_SF_7, SX127x_CR_4_7, 8, TLM_RATIO_1_32, 4, 10000, OTA4_PACKET_SIZE, 1},
+ {3, RADIO_TYPE_SX127x_LORA, RATE_LORA_50HZ, SX127x_BW_500_00_KHZ, SX127x_SF_8, SX127x_CR_4_7, 10, TLM_RATIO_1_16, 4, 20000, OTA4_PACKET_SIZE, 1},
+ {4, RADIO_TYPE_SX127x_LORA, RATE_LORA_25HZ, SX127x_BW_500_00_KHZ, SX127x_SF_9, SX127x_CR_4_7, 10, TLM_RATIO_1_8, 2, 40000, OTA4_PACKET_SIZE, 1},
+ {5, RADIO_TYPE_SX127x_LORA, RATE_DVDA_50HZ, SX127x_BW_500_00_KHZ, SX127x_SF_6, SX127x_CR_4_7, 8, TLM_RATIO_1_64, 2, 5000, OTA4_PACKET_SIZE, 4}};
expresslrs_rf_pref_params_s ExpressLRS_AirRateRFperf[RATE_MAX] = {
{0, RATE_LORA_200HZ, -112, 4380, 3000, 2500, 600, 5000, SNR_SCALE( 1), SNR_SCALE(3.0)},
{1, RATE_LORA_100HZ_8CH, -112, 6690, 3500, 2500, 600, 5000, SNR_SCALE( 1), SNR_SCALE(3.0)},
{2, RATE_LORA_100HZ, -117, 8770, 3500, 2500, 600, 5000, SNR_SCALE( 1), SNR_SCALE(2.5)},
{3, RATE_LORA_50HZ, -120, 18560, 4000, 2500, 600, 5000, SNR_SCALE(-1), SNR_SCALE(1.5)},
- {4, RATE_LORA_25HZ, -123, 29950, 6000, 4000, 0, 5000, SNR_SCALE(-3), SNR_SCALE(0.5)},
+ {4, RATE_LORA_25HZ, -123, 29950, 6000, 4000, 600, 5000, SNR_SCALE(-3), SNR_SCALE(0.5)},
{5, RATE_DVDA_50HZ, -112, 4380, 3000, 2500, 600, 5000, SNR_SCALE( 1), SNR_SCALE(3.0)}};
#endif
+#if defined(RADIO_LR1121)
+
+#include "LR1121Driver.h"
+LR1121Driver DMA_ATTR Radio;
+
+expresslrs_mod_settings_s ExpressLRS_AirRateConfig[RATE_MAX] = {
+ {0, RADIO_TYPE_LR1121_LORA_900, RATE_LORA_200HZ, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF6, LR11XX_RADIO_LORA_CR_4_7, 8, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF6, LR11XX_RADIO_LORA_CR_4_7, 8, TLM_RATIO_1_64, 4, 5000, OTA4_PACKET_SIZE, 1},
+ {1, RADIO_TYPE_LR1121_LORA_900, RATE_LORA_100HZ_8CH, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF6, LR11XX_RADIO_LORA_CR_4_8, 8, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF6, LR11XX_RADIO_LORA_CR_4_8, 8, TLM_RATIO_1_32, 4, 10000, OTA8_PACKET_SIZE, 1},
+ {2, RADIO_TYPE_LR1121_LORA_900, RATE_LORA_100HZ, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF7, LR11XX_RADIO_LORA_CR_4_7, 8, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF7, LR11XX_RADIO_LORA_CR_4_7, 8, TLM_RATIO_1_32, 4, 10000, OTA4_PACKET_SIZE, 1},
+ {3, RADIO_TYPE_LR1121_LORA_900, RATE_LORA_50HZ, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF8, LR11XX_RADIO_LORA_CR_4_7, 10, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF8, LR11XX_RADIO_LORA_CR_4_7, 10, TLM_RATIO_1_16, 4, 20000, OTA4_PACKET_SIZE, 1},
+ {4, RADIO_TYPE_LR1121_LORA_2G4, RATE_LORA_500HZ, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF5, LR11XX_RADIO_LORA_CR_LI_4_6, 12, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF5, LR11XX_RADIO_LORA_CR_LI_4_6, 12, TLM_RATIO_1_128, 4, 2000, OTA4_PACKET_SIZE, 1},
+ {5, RADIO_TYPE_LR1121_LORA_2G4, RATE_LORA_333HZ_8CH, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF5, LR11XX_RADIO_LORA_CR_LI_4_8, 12, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF5, LR11XX_RADIO_LORA_CR_LI_4_8, 12, TLM_RATIO_1_128, 4, 3003, OTA8_PACKET_SIZE, 1},
+ {6, RADIO_TYPE_LR1121_LORA_2G4, RATE_LORA_250HZ, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF6, LR11XX_RADIO_LORA_CR_LI_4_8, 14, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF6, LR11XX_RADIO_LORA_CR_LI_4_8, 14, TLM_RATIO_1_64, 4, 4000, OTA4_PACKET_SIZE, 1},
+ {7, RADIO_TYPE_LR1121_LORA_2G4, RATE_LORA_150HZ, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF7, LR11XX_RADIO_LORA_CR_LI_4_8, 12, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF7, LR11XX_RADIO_LORA_CR_LI_4_8, 12, TLM_RATIO_1_32, 4, 6666, OTA4_PACKET_SIZE, 1},
+ {8, RADIO_TYPE_LR1121_LORA_2G4, RATE_LORA_100HZ_8CH, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF7, LR11XX_RADIO_LORA_CR_LI_4_8, 12, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF7, LR11XX_RADIO_LORA_CR_LI_4_8, 12, TLM_RATIO_1_32, 4, 10000, OTA8_PACKET_SIZE, 1},
+ {9, RADIO_TYPE_LR1121_LORA_2G4, RATE_LORA_50HZ, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF8, LR11XX_RADIO_LORA_CR_LI_4_8, 12, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF8, LR11XX_RADIO_LORA_CR_LI_4_8, 12, TLM_RATIO_1_16, 2, 20000, OTA4_PACKET_SIZE, 1},
+ {10, RADIO_TYPE_LR1121_LORA_DUAL, RATE_LORA_150HZ, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF6, LR11XX_RADIO_LORA_CR_4_8, 12, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF7, LR11XX_RADIO_LORA_CR_LI_4_6, 12, TLM_RATIO_1_32, 4, 6666, OTA4_PACKET_SIZE, 1},
+ {11, RADIO_TYPE_LR1121_LORA_DUAL, RATE_LORA_100HZ_8CH, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF6, LR11XX_RADIO_LORA_CR_4_8, 18, LR11XX_RADIO_LORA_BW_800, LR11XX_RADIO_LORA_SF7, LR11XX_RADIO_LORA_CR_LI_4_8, 14, TLM_RATIO_1_32, 4, 10000, OTA8_PACKET_SIZE, 1},
+ {12, RADIO_TYPE_LR1121_LORA_900, RATE_LORA_250HZ, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF5, LR11XX_RADIO_LORA_CR_4_8, 8, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF5, LR11XX_RADIO_LORA_CR_4_8, 8, TLM_RATIO_1_64, 4, 4000, OTA4_PACKET_SIZE, 1},
+ {13, RADIO_TYPE_LR1121_LORA_900, RATE_LORA_200HZ_8CH, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF5, LR11XX_RADIO_LORA_CR_4_7, 8, LR11XX_RADIO_LORA_BW_500, LR11XX_RADIO_LORA_SF5, LR11XX_RADIO_LORA_CR_4_7, 8, TLM_RATIO_1_64, 4, 5000, OTA8_PACKET_SIZE, 1}};
+
+expresslrs_rf_pref_params_s ExpressLRS_AirRateRFperf[RATE_MAX] = {
+ {0, RATE_LORA_200HZ, -112, 4380, 3000, 2500, 600, 5000, SNR_SCALE( 1), SNR_SCALE(3.0)}, // These SNR_SCALE values all need to be checked!
+ {1, RATE_LORA_100HZ_8CH, -112, 6690, 3500, 2500, 600, 5000, SNR_SCALE( 1), SNR_SCALE(3.0)},
+ {2, RATE_LORA_100HZ, -117, 8770, 3500, 2500, 600, 5000, SNR_SCALE( 1), SNR_SCALE(2.5)},
+ {3, RATE_LORA_50HZ, -120, 18560, 4000, 2500, 600, 5000, SNR_SCALE(-1), SNR_SCALE(1.5)},
+ {4, RATE_LORA_500HZ, -105, 1507, 2500, 2500, 3, 5000, SNR_SCALE( 5), SNR_SCALE(9.5)},
+ {5, RATE_LORA_333HZ_8CH, -105, 2374, 2500, 2500, 4, 5000, SNR_SCALE( 5), SNR_SCALE(9.5)},
+ {6, RATE_LORA_250HZ, -108, 3300, 3000, 2500, 6, 5000, SNR_SCALE( 3), SNR_SCALE(9.5)},
+ {7, RATE_LORA_150HZ, -112, 5871, 3500, 2500, 10, 5000, SNR_SCALE( 0), SNR_SCALE(8.5)},
+ {8, RATE_LORA_100HZ_8CH, -112, 7605, 3500, 2500, 11, 5000, SNR_SCALE( 0), SNR_SCALE(8.5)},
+ {9, RATE_LORA_50HZ, -115, 10798, 4000, 2500, 0, 5000, SNR_SCALE(-1), SNR_SCALE(6.5)},
+ {10, RATE_LORA_150HZ, -112, 5871, 3500, 2500, 10, 5000, SNR_SCALE( 0), SNR_SCALE(8.5)},
+ {11, RATE_LORA_100HZ_8CH, -112, 7456, 3500, 2500, 11, 5000, SNR_SCALE( 0), SNR_SCALE(8.5)},
+ {12, RATE_LORA_250HZ, -111, 3216, 3500, 2500, 600, 5000, SNR_SCALE( 1), SNR_SCALE(3.0)},
+ {13, RATE_LORA_200HZ_8CH, -111, 4240, 3500, 2500, 600, 5000, SNR_SCALE( 1), SNR_SCALE(3.0)}};
+#endif
+
#if defined(RADIO_SX128X)
#include "SX1280Driver.h"
SX1280Driver DMA_ATTR Radio;
expresslrs_mod_settings_s ExpressLRS_AirRateConfig[RATE_MAX] = {
- {0, RADIO_TYPE_SX128x_FLRC, RATE_FLRC_1000HZ, SX1280_FLRC_BR_0_650_BW_0_6, SX1280_FLRC_BT_1, SX1280_FLRC_CR_1_2, TLM_RATIO_1_128, 2, 1000, 32, OTA4_PACKET_SIZE, 1},
- {1, RADIO_TYPE_SX128x_FLRC, RATE_FLRC_500HZ, SX1280_FLRC_BR_0_650_BW_0_6, SX1280_FLRC_BT_1, SX1280_FLRC_CR_1_2, TLM_RATIO_1_128, 2, 2000, 32, OTA4_PACKET_SIZE, 1},
- {2, RADIO_TYPE_SX128x_FLRC, RATE_DVDA_500HZ, SX1280_FLRC_BR_0_650_BW_0_6, SX1280_FLRC_BT_1, SX1280_FLRC_CR_1_2, TLM_RATIO_1_128, 2, 1000, 32, OTA4_PACKET_SIZE, 2},
- {3, RADIO_TYPE_SX128x_FLRC, RATE_DVDA_250HZ, SX1280_FLRC_BR_0_650_BW_0_6, SX1280_FLRC_BT_1, SX1280_FLRC_CR_1_2, TLM_RATIO_1_128, 2, 1000, 32, OTA4_PACKET_SIZE, 4},
- {4, RADIO_TYPE_SX128x_LORA, RATE_LORA_500HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF5, SX1280_LORA_CR_LI_4_6, TLM_RATIO_1_128, 4, 2000, 12, OTA4_PACKET_SIZE, 1},
- {5, RADIO_TYPE_SX128x_LORA, RATE_LORA_333HZ_8CH, SX1280_LORA_BW_0800, SX1280_LORA_SF5, SX1280_LORA_CR_LI_4_8, TLM_RATIO_1_128, 4, 3003, 12, OTA8_PACKET_SIZE, 1},
- {6, RADIO_TYPE_SX128x_LORA, RATE_LORA_250HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF6, SX1280_LORA_CR_LI_4_8, TLM_RATIO_1_64, 4, 4000, 14, OTA4_PACKET_SIZE, 1},
- {7, RADIO_TYPE_SX128x_LORA, RATE_LORA_150HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF7, SX1280_LORA_CR_LI_4_8, TLM_RATIO_1_32, 4, 6666, 12, OTA4_PACKET_SIZE, 1},
- {8, RADIO_TYPE_SX128x_LORA, RATE_LORA_100HZ_8CH, SX1280_LORA_BW_0800, SX1280_LORA_SF7, SX1280_LORA_CR_LI_4_8, TLM_RATIO_1_32, 4, 10000, 12, OTA8_PACKET_SIZE, 1},
- {9, RADIO_TYPE_SX128x_LORA, RATE_LORA_50HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF8, SX1280_LORA_CR_LI_4_8, TLM_RATIO_1_16, 2, 20000, 12, OTA4_PACKET_SIZE, 1}};
+ {0, RADIO_TYPE_SX128x_FLRC, RATE_FLRC_1000HZ, SX1280_FLRC_BR_0_650_BW_0_6, SX1280_FLRC_BT_1, SX1280_FLRC_CR_1_2, 32, TLM_RATIO_1_128, 2, 1000, OTA4_PACKET_SIZE, 1},
+ {1, RADIO_TYPE_SX128x_FLRC, RATE_FLRC_500HZ, SX1280_FLRC_BR_0_650_BW_0_6, SX1280_FLRC_BT_1, SX1280_FLRC_CR_1_2, 32, TLM_RATIO_1_128, 2, 2000, OTA4_PACKET_SIZE, 1},
+ {2, RADIO_TYPE_SX128x_FLRC, RATE_DVDA_500HZ, SX1280_FLRC_BR_0_650_BW_0_6, SX1280_FLRC_BT_1, SX1280_FLRC_CR_1_2, 32, TLM_RATIO_1_128, 2, 1000, OTA4_PACKET_SIZE, 2},
+ {3, RADIO_TYPE_SX128x_FLRC, RATE_DVDA_250HZ, SX1280_FLRC_BR_0_650_BW_0_6, SX1280_FLRC_BT_1, SX1280_FLRC_CR_1_2, 32, TLM_RATIO_1_128, 2, 1000, OTA4_PACKET_SIZE, 4},
+ {4, RADIO_TYPE_SX128x_LORA, RATE_LORA_500HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF5, SX1280_LORA_CR_LI_4_6, 12, TLM_RATIO_1_128, 4, 2000, OTA4_PACKET_SIZE, 1},
+ {5, RADIO_TYPE_SX128x_LORA, RATE_LORA_333HZ_8CH, SX1280_LORA_BW_0800, SX1280_LORA_SF5, SX1280_LORA_CR_LI_4_8, 12, TLM_RATIO_1_128, 4, 3003, OTA8_PACKET_SIZE, 1},
+ {6, RADIO_TYPE_SX128x_LORA, RATE_LORA_250HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF6, SX1280_LORA_CR_LI_4_8, 14, TLM_RATIO_1_64, 4, 4000, OTA4_PACKET_SIZE, 1},
+ {7, RADIO_TYPE_SX128x_LORA, RATE_LORA_150HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF7, SX1280_LORA_CR_LI_4_8, 12, TLM_RATIO_1_32, 4, 6666, OTA4_PACKET_SIZE, 1},
+ {8, RADIO_TYPE_SX128x_LORA, RATE_LORA_100HZ_8CH, SX1280_LORA_BW_0800, SX1280_LORA_SF7, SX1280_LORA_CR_LI_4_8, 12, TLM_RATIO_1_32, 4, 10000, OTA8_PACKET_SIZE, 1},
+ {9, RADIO_TYPE_SX128x_LORA, RATE_LORA_50HZ, SX1280_LORA_BW_0800, SX1280_LORA_SF8, SX1280_LORA_CR_LI_4_8, 12, TLM_RATIO_1_16, 2, 20000, OTA4_PACKET_SIZE, 1}};
expresslrs_rf_pref_params_s ExpressLRS_AirRateRFperf[RATE_MAX] = {
{0, RATE_FLRC_1000HZ, -104, 389, 2500, 2500, 3, 5000, DYNPOWER_SNR_THRESH_NONE, DYNPOWER_SNR_THRESH_NONE},
@@ -104,18 +142,18 @@ uint8_t ICACHE_RAM_ATTR enumRatetoIndex(expresslrs_RFrates_e const eRate)
return (eRate == RATE_LORA_25HZ) ? RATE_MAX - 1 : 0;
}
-uint8_t ExpressLRS_currTlmDenom;
-expresslrs_mod_settings_s *ExpressLRS_currAirRate_Modparams;
-expresslrs_rf_pref_params_s *ExpressLRS_currAirRate_RFperfParams;
-
+// Connection state information
+uint8_t UID[UID_LEN] = {0}; // "bind phrase" ID
+bool connectionHasModelMatch = false;
+bool teamraceHasModelMatch = true; // true if isTx or teamrace disabled or (enabled and channel in correct postion)
+bool InBindingMode = false;
+uint8_t ExpressLRS_currTlmDenom = 1;
connectionState_e connectionState = disconnected;
-bool connectionHasModelMatch;
-
-uint32_t ChannelData[CRSF_NUM_CHANNELS]; // Current state of channels, CRSF format
+expresslrs_mod_settings_s *ExpressLRS_currAirRate_Modparams = nullptr;
+expresslrs_rf_pref_params_s *ExpressLRS_currAirRate_RFperfParams = nullptr;
-uint8_t MasterUID[6]; // The definitive user UID
-uint8_t UID[6]; // The currently running UID
-uint8_t BindingUID[6] = {0, 1, 2, 3, 4, 5}; // Special binding UID values
+// Current state of channels, CRSF format
+uint32_t ChannelData[CRSF_NUM_CHANNELS];
uint8_t ICACHE_RAM_ATTR TLMratioEnumToValue(expresslrs_tlm_ratio_e const enumval)
{
@@ -154,38 +192,13 @@ uint8_t TLMBurstMaxForRateRatio(uint16_t const rateHz, uint8_t const ratioDiv)
return retVal;
}
-uint32_t uidMacSeedGet(void)
+uint32_t uidMacSeedGet()
{
const uint32_t macSeed = ((uint32_t)UID[2] << 24) + ((uint32_t)UID[3] << 16) +
((uint32_t)UID[4] << 8) + (UID[5]^OTA_VERSION_ID);
return macSeed;
}
-void initUID()
-{
- // default until first sync packet calculates it
- ExpressLRS_currTlmDenom = 1;
- if (firmwareOptions.hasUID)
- {
- memcpy(MasterUID, firmwareOptions.uid, sizeof(MasterUID));
- }
- else
- {
- #ifdef PLATFORM_ESP32
- esp_err_t WiFiErr = esp_read_mac(MasterUID, ESP_MAC_WIFI_STA);
- #elif PLATFORM_STM32
- MasterUID[0] = (uint8_t)HAL_GetUIDw0();
- MasterUID[1] = (uint8_t)(HAL_GetUIDw0() >> 8);
- MasterUID[2] = (uint8_t)HAL_GetUIDw1();
- MasterUID[3] = (uint8_t)(HAL_GetUIDw1() >> 8);
- MasterUID[4] = (uint8_t)HAL_GetUIDw2();
- MasterUID[5] = (uint8_t)(HAL_GetUIDw2() >> 8);
- #endif
- }
- memcpy(UID, MasterUID, sizeof(UID));
- OtaUpdateCrcInitFromUid();
-}
-
bool ICACHE_RAM_ATTR isDualRadio()
{
return GPIO_PIN_NSS_2 != UNDEF_PIN;
diff --git a/src/src/dynpower.cpp b/src/src/dynpower.cpp
index 0ce1c26d4d..826f6eb1c2 100644
--- a/src/src/dynpower.cpp
+++ b/src/src/dynpower.cpp
@@ -1,7 +1,7 @@
-#if defined(TARGET_TX)
-
#include
-#include
+
+#if defined(TARGET_TX)
+#include
#include
// LQ-based boost defines
@@ -88,7 +88,7 @@ void DynamicPower_Update(uint32_t now)
// ============= DYNAMIC_POWER_BOOST: Switch-triggered power boost up ==============
// Or if telemetry is lost while armed (done up here because dynpower_updated is only updated on telemetry)
uint8_t boostChannel = config.GetBoostChannel();
- bool armed = CRSF::IsArmed();
+ bool armed = handset->IsArmed();
if ((connectionState == disconnected && armed) ||
(boostChannel && (CRSF_to_BIT(ChannelData[AUX9 + boostChannel - 1]) == 0)))
{
@@ -195,4 +195,33 @@ void DynamicPower_Update(uint32_t now)
}
}
-#endif // TARGET_TX
\ No newline at end of file
+#endif // TARGET_TX
+
+#if defined(TARGET_RX)
+
+/***
+ * @brief: Set power to configured power or update power to match the current TX power
+ * @param initialize: Set to true if calling from setup() to force even PWR_MATCH_TX to default power
+*/
+void DynamicPower_UpdateRx(bool initialize)
+{
+ if (config.GetPower() != PWR_MATCH_TX)
+ {
+ POWERMGNT::setPower((PowerLevels_e)config.GetPower());
+ } /* !PWR_MATCH_TX (fixed power) */
+ else
+ {
+ if (CRSF::clearUpdatedUplinkPower())
+ {
+ PowerLevels_e newPower = crsfpowerToPower(CRSF::LinkStatistics.uplink_TX_Power);
+ DBGLN("Matching TX power %u", newPower);
+ POWERMGNT::setPower(newPower);
+ }
+ else if (initialize)
+ {
+ POWERMGNT::setPower(POWERMGNT::getDefaultPower());
+ }
+ } /* PWR_MATCH_TX */
+}
+
+#endif // TARGET_RX
diff --git a/src/src/rx-serial/SerialAirPort.cpp b/src/src/rx-serial/SerialAirPort.cpp
index c24b6b8f61..a713e60f83 100644
--- a/src/src/rx-serial/SerialAirPort.cpp
+++ b/src/src/rx-serial/SerialAirPort.cpp
@@ -5,30 +5,15 @@
#include "common.h"
// Variables / constants for Airport //
-FIFO_GENERIC apInputBuffer;
-FIFO_GENERIC apOutputBuffer;
+FIFO apInputBuffer;
+FIFO apOutputBuffer;
-void SerialAirPort::setLinkQualityStats(uint16_t lq, uint16_t rssi)
-{
- // unsupported
-}
-
-void SerialAirPort::sendLinkStatisticsToFC()
-{
- // unsupported
-}
-
-uint32_t SerialAirPort::sendRCFrameToFC(bool frameAvailable, uint32_t *channelData)
+uint32_t SerialAirPort::sendRCFrame(bool frameAvailable, bool frameMissed, uint32_t *channelData)
{
return DURATION_IMMEDIATELY;
}
-void SerialAirPort::sendMSPFrameToFC(uint8_t* data)
-{
- // unsupported
-}
-
int SerialAirPort::getMaxSerialReadSize()
{
return AP_MAX_BUF_LEN - apInputBuffer.size();
@@ -38,15 +23,20 @@ void SerialAirPort::processBytes(uint8_t *bytes, u_int16_t size)
{
if (connectionState == connected)
{
- apInputBuffer.pushBytes(bytes, size);
+ apInputBuffer.atomicPushBytes(bytes, size);
}
}
-void SerialAirPort::handleUARTout()
+void SerialAirPort::sendQueuedData(uint32_t maxBytesToSend)
{
auto size = apOutputBuffer.size();
- uint8_t buf[size];
- apOutputBuffer.popBytes(buf, size);
- _outputPort->write(buf, size);
+ if (size != 0)
+ {
+ uint8_t buf[size];
+ apOutputBuffer.lock();
+ apOutputBuffer.popBytes(buf, size);
+ apOutputBuffer.unlock();
+ _outputPort->write(buf, size);
+ }
}
#endif
diff --git a/src/src/rx-serial/SerialAirPort.h b/src/src/rx-serial/SerialAirPort.h
index 7db3549997..0f23c18bad 100644
--- a/src/src/rx-serial/SerialAirPort.h
+++ b/src/src/rx-serial/SerialAirPort.h
@@ -1,26 +1,23 @@
#include "SerialIO.h"
-#include "FIFO_GENERIC.h"
+#include "FIFO.h"
#include "telemetry_protocol.h"
// Variables / constants for Airport //
-extern FIFO_GENERIC apInputBuffer;
-extern FIFO_GENERIC apOutputBuffer;
+extern FIFO apInputBuffer;
+extern FIFO apOutputBuffer;
class SerialAirPort : public SerialIO {
public:
explicit SerialAirPort(Stream &out, Stream &in) : SerialIO(&out, &in) {}
-
virtual ~SerialAirPort() {}
- void setLinkQualityStats(uint16_t lq, uint16_t rssi) override;
- uint32_t sendRCFrameToFC(bool frameAvailable, uint32_t *channelData) override;
- void sendMSPFrameToFC(uint8_t* data) override;
- void sendLinkStatisticsToFC() override;
+ void queueLinkStatisticsPacket() override {}
+ void queueMSPFrameTransmission(uint8_t* data) override {}
+ uint32_t sendRCFrame(bool frameAvailable, bool frameMissed, uint32_t *channelData) override;
int getMaxSerialReadSize() override;
- void handleUARTout() override;
+ void sendQueuedData(uint32_t maxBytesToSend) override;
private:
void processBytes(uint8_t *bytes, u_int16_t size) override;
- void processByte(uint8_t byte) override {};
};
diff --git a/src/src/rx-serial/SerialCRSF.cpp b/src/src/rx-serial/SerialCRSF.cpp
index 04a2fddd97..8a99f9fff7 100644
--- a/src/src/rx-serial/SerialCRSF.cpp
+++ b/src/src/rx-serial/SerialCRSF.cpp
@@ -1,20 +1,38 @@
#include "SerialCRSF.h"
-#include "CRSF.h"
+#include "common.h"
+#include "OTA.h"
#include "device.h"
#include "telemetry.h"
+#if defined(USE_MSP_WIFI)
+#include "msp2crsf.h"
+
+extern MSP2CROSSFIRE msp2crsf;
+#endif
extern Telemetry telemetry;
extern void reset_into_bootloader();
-extern void EnterBindingMode();
extern void UpdateModelMatch(uint8_t model);
-void SerialCRSF::setLinkQualityStats(uint16_t lq, uint16_t rssi)
+void SerialCRSF::sendQueuedData(uint32_t maxBytesToSend)
{
- linkQuality = lq;
- rssiDBM = rssi;
+ uint32_t bytesWritten = 0;
+ #if defined(USE_MSP_WIFI)
+ while (msp2crsf.FIFOout.size() > msp2crsf.FIFOout.peek() && (bytesWritten + msp2crsf.FIFOout.peek()) < maxBytesToSend)
+ {
+ msp2crsf.FIFOout.lock();
+ uint8_t OutPktLen = msp2crsf.FIFOout.pop();
+ uint8_t OutData[OutPktLen];
+ msp2crsf.FIFOout.popBytes(OutData, OutPktLen);
+ msp2crsf.FIFOout.unlock();
+ this->_outputPort->write(OutData, OutPktLen); // write the packet out
+ bytesWritten += OutPktLen;
+ }
+ #endif
+ // Call the super class to send the current FIFO (using any left-over bytes)
+ SerialIO::sendQueuedData(maxBytesToSend - bytesWritten);
}
-void SerialCRSF::sendLinkStatisticsToFC()
+void SerialCRSF::queueLinkStatisticsPacket()
{
constexpr uint8_t outBuffer[] = {
LinkStatisticsFrameLength + 4,
@@ -26,15 +44,17 @@ void SerialCRSF::sendLinkStatisticsToFC()
uint8_t crc = crsf_crc.calc(outBuffer[3]);
crc = crsf_crc.calc((byte *)&CRSF::LinkStatistics, LinkStatisticsFrameLength, crc);
+ _fifo.lock();
if (_fifo.ensure(outBuffer[0] + 1))
{
_fifo.pushBytes(outBuffer, sizeof(outBuffer));
_fifo.pushBytes((byte *)&CRSF::LinkStatistics, LinkStatisticsFrameLength);
_fifo.push(crc);
}
+ _fifo.unlock();
}
-uint32_t SerialCRSF::sendRCFrameToFC(bool frameAvailable, uint32_t *channelData)
+uint32_t SerialCRSF::sendRCFrame(bool frameAvailable, bool frameMissed, uint32_t *channelData)
{
if (!frameAvailable)
return DURATION_IMMEDIATELY;
@@ -61,8 +81,22 @@ uint32_t SerialCRSF::sendRCFrameToFC(bool frameAvailable, uint32_t *channelData)
PackedRCdataOut.ch11 = channelData[11];
PackedRCdataOut.ch12 = channelData[12];
PackedRCdataOut.ch13 = channelData[13];
- PackedRCdataOut.ch14 = linkQuality;
- PackedRCdataOut.ch15 = rssiDBM;
+
+ // In 16ch mode, do not output RSSI/LQ on channels
+ if (OtaIsFullRes && OtaSwitchModeCurrent == smHybridOr16ch)
+ {
+ PackedRCdataOut.ch14 = channelData[14];
+ PackedRCdataOut.ch15 = channelData[15];
+ }
+ else
+ {
+ // Not in 16-channel mode, send LQ and RSSI dBm
+ int32_t rssiDBM = CRSF::LinkStatistics.active_antenna == 0 ? -CRSF::LinkStatistics.uplink_RSSI_1 : -CRSF::LinkStatistics.uplink_RSSI_2;
+
+ PackedRCdataOut.ch14 = UINT10_to_CRSF(fmap(CRSF::LinkStatistics.uplink_Link_quality, 0, 100, 0, 1023));
+ PackedRCdataOut.ch15 = UINT10_to_CRSF(map(constrain(rssiDBM, ExpressLRS_currAirRate_RFperfParams->RXsensitivity, -50),
+ ExpressLRS_currAirRate_RFperfParams->RXsensitivity, -50, 0, 1023));
+ }
uint8_t crc = crsf_crc.calc(outBuffer[2]);
crc = crsf_crc.calc((byte *)&PackedRCdataOut, RCframeLength, crc);
@@ -73,38 +107,43 @@ uint32_t SerialCRSF::sendRCFrameToFC(bool frameAvailable, uint32_t *channelData)
return DURATION_IMMEDIATELY;
}
-void SerialCRSF::sendMSPFrameToFC(uint8_t* data)
+void SerialCRSF::queueMSPFrameTransmission(uint8_t* data)
{
const uint8_t totalBufferLen = CRSF_FRAME_SIZE(data[1]);
if (totalBufferLen <= CRSF_FRAME_SIZE_MAX)
{
data[0] = CRSF_ADDRESS_FLIGHT_CONTROLLER;
+ _fifo.lock();
_fifo.push(totalBufferLen);
_fifo.pushBytes(data, totalBufferLen);
+ _fifo.unlock();
}
}
-void SerialCRSF::processByte(uint8_t byte)
+void SerialCRSF::processBytes(uint8_t *bytes, uint16_t size)
{
- telemetry.RXhandleUARTin(byte);
-
- if (telemetry.ShouldCallBootloader())
- {
- reset_into_bootloader();
- }
- if (telemetry.ShouldCallEnterBind())
- {
- EnterBindingMode();
- }
- if (telemetry.ShouldCallUpdateModelMatch())
- {
- UpdateModelMatch(telemetry.GetUpdatedModelMatch());
- }
- if (telemetry.ShouldSendDeviceFrame())
+ for (int i=0 ; i= DISCOVERY_TIMEOUT))
+ {
+ discoveryMode = false;
+ }
+
+ // device polling scheduler
+ scheduleDevicePolling(now);
+
+ // CRSF packet scheduler
+ scheduleCRSFtelemetry(now);
+}
+
+void SerialHoTT_TLM::scheduleDevicePolling(uint32_t now)
+{
+ // send CMD byte 1
+ if (now - lastPoll >= HOTT_POLL_RATE)
+ {
+ lastPoll = now;
+
+ // work out next device to be polled. All devices in discovery
+ // mode, only detected devices in non-discovery mode)
+ nextDeviceID = NOT_FOUND;
+
+ for (uint i = FIRST_DEVICE; i < LAST_DEVICE; i++)
+ {
+ if (nextDevice == LAST_DEVICE)
+ {
+ nextDevice = FIRST_DEVICE;
+ }
+
+ if (device[nextDevice].present || discoveryMode)
+ {
+ nextDeviceID = device[nextDevice].deviceID;
+
+ nextDevice++;
+
+ break;
+ }
+
+ nextDevice++;
+ }
+
+ // no device found, nothing to do
+ if (nextDeviceID == NOT_FOUND)
+ {
+ return;
+ }
+
+ // clear serial in buffer
+ hottInputBuffer.flush();
+
+ // switch to half duplex TX mode and write CMD byte 1
+ setTXMode();
+ _outputPort->write(START_OF_CMD_B);
+ cmdSendState = HOTT_CMD1SENT;
+ return;
+ }
+
+ // delay sending CMD byte 2 to accomodate for slow devices
+ if ((now - lastPoll >= HOTT_CMD_DELAY) && cmdSendState == HOTT_CMD1SENT)
+ {
+ _outputPort->write(nextDeviceID);
+ cmdSendState = HOTT_CMD2SENT;
+ return;
+ }
+
+ // wait for the last byte being sent out to switch to RX mode
+ if ((now - lastPoll >= HOTT_WAIT_TX_COMPLETE) && cmdSendState == HOTT_CMD2SENT)
+ {
+ // switch to half duplex listen mode
+ setRXMode();
+ cmdSendState = HOTT_RECEIVING;
+ }
+}
+
+void SerialHoTT_TLM::processFrame()
+{
+ void *frameData = (void *)&hottBusFrame.payload;
+
+ // store received frame
+ switch (hottBusFrame.payload[DEVICE_INDEX])
+ {
+ case SENSOR_ID_GPS_B:
+ device[GPS].present = true;
+ memcpy((void *)&gps, frameData, sizeof(gps));
+ break;
+
+ case SENSOR_ID_EAM_B:
+ device[EAM].present = true;
+ memcpy((void *)&eam, frameData, sizeof(eam));
+ break;
+
+ case SENSOR_ID_GAM_B:
+ device[GAM].present = true;
+ memcpy((void *)&gam, frameData, sizeof(gam));
+ break;
+
+ case SENSOR_ID_VARIO_B:
+ device[VARIO].present = true;
+ memcpy((void *)&vario, frameData, sizeof(vario));
+ break;
+
+ case SENSOR_ID_ESC_B:
+ device[ESC].present = true;
+ memcpy((void *)&esc, frameData, sizeof(esc));
+ break;
+ }
+}
+
+uint8_t SerialHoTT_TLM::calcFrameCRC(uint8_t *buf)
+{
+ uint16_t sum = 0;
+
+ for (uint8_t i = 0; i < FRAME_SIZE - 1; i++)
+ {
+ sum += buf[i];
+ }
+
+ return sum = sum & 0xff;
+}
+
+void SerialHoTT_TLM::scheduleCRSFtelemetry(uint32_t now)
+{
+ // HoTT combined GPS/Vario -> send GPS and vario packet
+ if (device[GPS].present)
+ {
+ sendCRSFgps(now);
+ sendCRSFvario(now);
+ }
+ else
+ // HoTT stand alone Vario and no GPS/Vario -> just send vario packet
+ if (device[VARIO].present)
+ {
+ sendCRSFvario(now);
+ }
+
+ // HoTT GAM, EAM, ESC -> send battery packet
+ if (device[GAM].present || device[EAM].present || device[ESC].present)
+ {
+ sendCRSFbattery(now);
+
+ // HoTT GAM and EAM but no GPS/Vario or Vario -> send vario packet too
+ if ((!device[GPS].present && !device[VARIO].present) && (device[GAM].present || device[EAM].present))
+ {
+ sendCRSFvario(now);
+ }
+ }
+}
+
+void SerialHoTT_TLM::sendCRSFvario(uint32_t now)
+{
+ // indicate external sensor is present
+ telemetry.SetCrsfBaroSensorDetected();
+
+ // prepare CRSF telemetry packet
+ CRSF_MK_FRAME_T(crsf_sensor_baro_vario_t)
+ crsfBaro = {0};
+ crsfBaro.p.altitude = htobe16(getHoTTaltitude() * 10 + 5000); // Hott 500 = 0m, ELRS 10000 = 0.0m
+ crsfBaro.p.verticalspd = htobe16(getHoTTvv() - 30000);
+ CRSF::SetHeaderAndCrc((uint8_t *)&crsfBaro, CRSF_FRAMETYPE_BARO_ALTITUDE, CRSF_FRAME_SIZE(sizeof(crsf_sensor_baro_vario_t)), CRSF_ADDRESS_CRSF_TRANSMITTER);
+
+ // send packet only if min rate timer expired or values have changed
+ if ((now - lastVarioSent >= VARIO_MIN_CRSFRATE) || (lastVarioCRC != crsfBaro.crc))
+ {
+ lastVarioSent = now;
+
+ telemetry.AppendTelemetryPackage((uint8_t *)&crsfBaro);
+ }
+
+ lastVarioCRC = crsfBaro.crc;
+}
+
+void SerialHoTT_TLM::sendCRSFgps(uint32_t now)
+{
+ // prepare CRSF telemetry packet
+ CRSF_MK_FRAME_T(crsf_sensor_gps_t)
+ crsfGPS = {0};
+ crsfGPS.p.latitude = htobe32(getHoTTlatitude());
+ crsfGPS.p.longitude = htobe32(getHoTTlongitude());
+ crsfGPS.p.groundspeed = htobe16(getHoTTgroundspeed() * 10); // Hott 1 = 1 km/h, ELRS 1 = 0.1km/h
+ crsfGPS.p.heading = htobe16(getHoTTheading() * 100);
+ crsfGPS.p.altitude = htobe16(getHoTTMSLaltitude() + 1000); // HoTT 1 = 1m, CRSF: 0m = 1000
+ crsfGPS.p.satellites = getHoTTsatellites();
+ CRSF::SetHeaderAndCrc((uint8_t *)&crsfGPS, CRSF_FRAMETYPE_GPS, CRSF_FRAME_SIZE(sizeof(crsf_sensor_gps_t)), CRSF_ADDRESS_CRSF_TRANSMITTER);
+
+ // send packet only if min rate timer expired or values have changed
+ if ((now - lastGPSSent >= GPS_MIN_CRSFRATE) || (lastGPSCRC != crsfGPS.crc))
+ {
+ lastGPSSent = now;
+
+ telemetry.AppendTelemetryPackage((uint8_t *)&crsfGPS);
+ }
+
+ lastGPSCRC = crsfGPS.crc;
+}
+
+void SerialHoTT_TLM::sendCRSFbattery(uint32_t now)
+{
+ // indicate external sensor is present
+ telemetry.SetCrsfBatterySensorDetected();
+
+ // prepare CRSF telemetry packet
+ CRSF_MK_FRAME_T(crsf_sensor_battery_t)
+ crsfBatt = {0};
+ crsfBatt.p.voltage = htobe16(getHoTTvoltage());
+ crsfBatt.p.current = htobe16(getHoTTcurrent());
+ crsfBatt.p.capacity = htobe24(getHoTTcapacity() * 10); // HoTT: 1 = 10mAh, CRSF: 1 ? 1 = 1mAh
+ crsfBatt.p.remaining = getHoTTremaining();
+ CRSF::SetHeaderAndCrc((uint8_t *)&crsfBatt, CRSF_FRAMETYPE_BATTERY_SENSOR, CRSF_FRAME_SIZE(sizeof(crsf_sensor_battery_t)), CRSF_ADDRESS_CRSF_TRANSMITTER);
+
+ // send packet only if min rate timer expired or values have changed
+ if ((now - lastBatterySent >= BATT_MIN_CRSFRATE) || (lastBatteryCRC != crsfBatt.crc))
+ {
+ lastBatterySent = now;
+
+ telemetry.AppendTelemetryPackage((uint8_t *)&crsfBatt);
+ }
+
+ lastBatteryCRC = crsfBatt.crc;
+}
+
+// HoTT telemetry data getters
+uint16_t SerialHoTT_TLM::getHoTTvoltage()
+{
+ if (device[EAM].present)
+ {
+ return eam.mainVoltage;
+ }
+ else if (device[GAM].present)
+ {
+ return (gam.inputVoltage);
+ }
+ else if (device[ESC].present)
+ {
+ return esc.inputVoltage;
+ }
+
+ return 0;
+}
+
+uint16_t SerialHoTT_TLM::getHoTTcurrent()
+{
+ if (device[EAM].present)
+ {
+ return eam.current;
+ }
+ else if (device[GAM].present)
+ {
+ return gam.current;
+ }
+ else if (device[ESC].present)
+ {
+ return esc.current;
+ }
+
+ return 0;
+}
+
+uint32_t SerialHoTT_TLM::getHoTTcapacity()
+{
+ if (device[EAM].present)
+ {
+ return eam.capacity;
+ }
+ else if (device[GAM].present)
+ {
+ return (gam.capacity);
+ }
+ else if (device[ESC].present)
+ {
+ return esc.capacity;
+ }
+
+ return 0;
+}
+
+int16_t SerialHoTT_TLM::getHoTTaltitude()
+{
+ if (device[GPS].present)
+ {
+ return gps.altitude;
+ }
+ else if (device[VARIO].present)
+ {
+ return vario.altitude;
+ }
+ else if (device[EAM].present)
+ {
+ return eam.altitude;
+ }
+ else if (device[GAM].present)
+ {
+ return gam.altitude;
+ }
+
+ return 0;
+}
+
+int16_t SerialHoTT_TLM::getHoTTvv()
+{
+ if (device[GPS].present)
+ {
+ return (gps.mPerSec);
+ }
+ else if (device[VARIO].present)
+ {
+ return (vario.mPerSec);
+ }
+ else if (device[EAM].present)
+ {
+ return (eam.mPerSec);
+ }
+ else if (device[GAM].present)
+ {
+ return (gam.mPerSec);
+ }
+
+ return 0;
+}
+
+uint8_t SerialHoTT_TLM::getHoTTremaining()
+{
+ if (device[GAM].present)
+ {
+ return gam.fuelScale;
+ }
+
+ return 0;
+}
+
+int32_t SerialHoTT_TLM::getHoTTlatitude()
+{
+ if (!device[GPS].present)
+ {
+ return 0;
+ }
+
+ uint8_t deg = gps.latDegMin / DegMinScale;
+
+ int32_t Lat = deg * DegScale +
+ ((gps.latDegMin - (deg * DegMinScale)) * MinScale) / MinDivide +
+ (gps.latSec * SecScale) / MinDivide;
+
+ if (gps.latNS != 0)
+ {
+ Lat = -Lat;
+ }
+
+ return (Lat);
+}
+
+int32_t SerialHoTT_TLM::getHoTTlongitude()
+{
+ if (!device[GPS].present)
+ {
+ return 0;
+ }
+
+ uint8_t deg = gps.lonDegMin / DegMinScale;
+
+ int32_t Lon = deg * DegScale +
+ ((gps.lonDegMin - (deg * DegMinScale)) * MinScale) / MinDivide +
+ (gps.lonSec * SecScale) / MinDivide;
+
+ if (gps.lonEW != 0)
+ Lon = -Lon;
+
+ return Lon;
+}
+
+uint16_t SerialHoTT_TLM::getHoTTgroundspeed()
+{
+ if (device[GPS].present)
+ {
+ return gps.speed;
+ }
+
+ return 0;
+}
+
+uint16_t SerialHoTT_TLM::getHoTTheading()
+{
+ if (!device[GPS].present)
+ {
+ return 0;
+ }
+
+ uint16_t heading = gps.direction * 2;
+
+ if (heading > 180)
+ {
+ heading -= 360;
+ }
+
+ return (heading);
+}
+
+uint8_t SerialHoTT_TLM::getHoTTsatellites()
+{
+ if (device[GPS].present)
+ {
+ return gps.satellites;
+ }
+
+ return 0;
+}
+
+uint16_t SerialHoTT_TLM::getHoTTMSLaltitude()
+{
+ if (device[GPS].present)
+ {
+ return gps.mslAltitude;
+ }
+
+ return 0;
+}
+
+uint32_t SerialHoTT_TLM::htobe24(uint32_t val)
+{
+#if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+ return val;
+#else
+ uint8_t *ptrByte = (uint8_t *)&val;
+
+ uint8_t swp = ptrByte[0];
+ ptrByte[0] = ptrByte[2];
+ ptrByte[2] = swp;
+
+ return val;
+#endif
+}
+
+#endif
diff --git a/src/src/rx-serial/SerialHoTT_TLM.h b/src/src/rx-serial/SerialHoTT_TLM.h
new file mode 100644
index 0000000000..2d887152f6
--- /dev/null
+++ b/src/src/rx-serial/SerialHoTT_TLM.h
@@ -0,0 +1,363 @@
+#if defined(TARGET_RX) && (defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32))
+
+#pragma once
+
+#include "SerialIO.h"
+#include "device.h"
+
+#define PACKED __attribute__((packed))
+
+#define HOTT_MAX_BUF_LEN 64 // max buffer size for serial in data
+
+#define FRAME_SIZE 45 // HoTT telemetry frame size
+#define CMD_LEN 2 // HoTT poll command length
+#define STARTBYTE_INDEX 0 // index of start byte
+#define DEVICE_INDEX 1 // index of device ID
+#define ENDBYTE_INDEX 43 // index of end byte
+#define CRC_INDEX 44 // index of CRC
+
+#define START_FRAME_B 0x7C // HoTT start of frame marker
+#define END_FRAME 0x7D // HoTT end of frame marker
+
+#define START_OF_CMD_B 0x80 // start byte of HoTT binary cmd sequence
+#define SENSOR_ID_GPS_B 0x8A // device ID binary mode GPS module
+#define SENSOR_ID_GPS_T 0xA0 // device ID for text mode adressing
+#define SENSOR_ID_GAM_B 0x8D // device ID binary mode GAM module
+#define SENSOR_ID_GAM_T 0xD0 // device ID for text mode adressing
+#define SENSOR_ID_EAM_B 0x8E // device ID binary mode EAM module
+#define SENSOR_ID_EAM_T 0xE0 // device ID for text mode adressing
+#define SENSOR_ID_ESC_B 0x8C // device ID binary mode ESC module
+#define SENSOR_ID_ESC_T 0xC0 // device ID for text mode adressing
+#define SENSOR_ID_VARIO_B 0x89 // device ID binary mode VARIO module
+#define SENSOR_ID_VARIO_T 0x90 // device ID for text mode adressing
+
+//
+// GAM data frame data structure
+//
+typedef struct
+{
+ uint8_t startByte = START_FRAME_B; // 0 0x7C
+ uint8_t packetId = SENSOR_ID_GAM_B; // 1 0x8D HOTT_General_AIR_PACKET_ID
+ uint8_t warnBeep = 0; // 2 warn beep (0 = no beep, 0x00..0x1A warn beeps)
+ uint8_t packetIdText = SENSOR_ID_GAM_T; // 3 0xD0 Sensor ID text mode
+ uint8_t inverse1 = 0; // 4 Alarm_inverse_1
+ uint8_t inverse2 = 0; // 5 Alarm_inverse_2
+ uint8_t voltageCell1 = 0; // 6 124 = 2,48V (voltage*50)
+ uint8_t voltageCell2 = 0; // 7 132 = 2,64V
+ uint8_t voltageCell3 = 0; // 8
+ uint8_t voltageCell4 = 0; // 9
+ uint8_t voltageCell5 = 0; // 10
+ uint8_t voltageCell6 = 0; // 11
+ uint16_t battery1 = 0; // 12 51 = 5,1V Batt1 voltage
+ uint16_t battery2 = 0; // 14 51 = 5,1V Batt2 voltage
+ uint8_t temperature1 = 20; // 16 46 = 26C, 0 = -20C (temp+20)
+ uint8_t temperature2 = 20; // 17 45 = 25C, 0 = -20C (temp+20)
+ uint8_t fuelScale = 0; // 18 0..100 (scales to display 0/25/50/75/100 increments)
+ uint16_t fuel = 0; // 19 65535 = full
+ uint16_t rpm1 = 0; // 21 (rpm/10)
+ uint16_t altitude = 500; // 23 500 = 0m (altitude+500)
+ uint16_t mPerSec = 30000; // 25 1 = 0,01m/s 30000 = 0m/s (climb rate/100 + 30000)
+ uint8_t mPer3sec = 120; // 27 120 = 0m per 3 sek
+ uint16_t current = 0; // 28 1 = 0.1A (current*10)
+ uint16_t inputVoltage = 0; // 30 166 = 16,6V (voltage*10)
+ uint16_t capacity = 0; // 32 1 = 10mAh (capacity in mAh/10)
+ uint16_t speed = 0; // 34 1 = 2km/h (speed/2)
+ uint8_t lowCellVoltage = 0; // 36 124 = 2,48V (voltage*50)
+ uint8_t cellNumberLcv = 0; // 37
+ uint16_t rpm2 = 0; // 38 (rpm/10)
+ uint8_t generalError = 0; // 40 general error number (voice error = 12)
+ uint8_t pressure = 0; // 41 pressure 20 = 2bar (pressure*10)
+ uint8_t version = 0; // 42 version number
+ uint8_t endByte = END_FRAME; // 43 0x7D
+ uint8_t crc = 0x00; // 44 CRC
+} PACKED GeneralAirPacket_t;
+
+//
+// GPS data frame data structure
+//
+typedef struct
+{
+ uint8_t startByte = START_FRAME_B; // 0 0x7C
+ uint8_t packetId = SENSOR_ID_GPS_B; // 1 0x8A HOTT_GPS_PACKET_ID
+ uint8_t warnBeep = 0; // 2 warn beep (0 = no beep, 0x00..0x1A warn beeps)
+ uint8_t packetIdText = SENSOR_ID_GPS_T; // 3 0xA0 Sensor ID text mode
+ uint8_t alarmInverse = 0; // 4 0 Inverse status
+ uint8_t GpsInverse = 1; // 5 1 = no GPS signal
+ uint8_t direction = 0; // 6 1 = 2 degrees; 0 = N, 90 = E, 180 = S, 270 = W
+ uint16_t speed = 0; // 7 1 = 1 km/h
+ uint8_t latNS = 0; // 9 example: N48D39'0988'', 0 = N
+ uint16_t latDegMin = 0; // 10 48D39' = 4839 = 0x12e7
+ uint16_t latSec = 0; // 12 0988'' = 988 = 0x03DC
+ uint8_t lonEW = 0; // 14 example: E09D25'9360'', 0 = E
+ uint16_t lonDegMin = 0; // 15 09D25' = 0925 = 0x039D
+ uint16_t lonSec = 0; // 17 9360'' = 9360 = 0x2490
+ uint16_t distance = 0; // 19 1 = 1m
+ uint16_t altitude = 500; // 21 500 = 0m
+ uint16_t mPerSec = 30000; // 23 30000 = 0.00m/s (1 = 0.01m/s)
+ uint8_t mPer3sec = 120; // 25 120 = 0m/3s (1 = 1m/3s)
+ uint8_t satellites = 0; // 26 n visible satellites
+ uint8_t fixChar = ' '; // 27 GPS fix character. Display if DGPS, 2D oder 3D
+ uint8_t homeDir = 0; // 28 GPS home direction 1 = 2 degreed
+ int8_t roll = 0; // 29 signed roll angle 1 = 2 degrees
+ int8_t pitch = 0; // 30 signed pitch angle 1 = 2 degrees
+ int8_t Yaw = 0; // 31 signed yaw angle 1 = 2 degrees
+ uint8_t timeHours = 0; // 32 GPS time hours
+ uint8_t timeMinutes = 0; // 33 GPS time minutes
+ uint8_t timeSeconds = 0; // 34 GPS time seconds
+ uint8_t timeHundreds = 0; // 35 GPS time 1/100 seconds
+ uint16_t mslAltitude = 0; // 36 1 = 1m
+ uint8_t vibrations = 0; // 38 vibration level in %
+ uint8_t ascii1 = '-'; // 39 free ASCII character 1
+ uint8_t ascii2 = ' '; // 40 free ASCII character 2
+ uint8_t ascii3 = '-'; // 41 free ASCII character 3
+ uint8_t version = 0; // 42 version number
+ uint8_t endByte = END_FRAME; // 43 0x7D
+ uint8_t crc = 0x00; // 44 CRC
+} PACKED GPSPacket_t;
+
+//
+// EAM data frame data structure
+//
+typedef struct
+{
+ uint8_t startByte = START_FRAME_B; // 0 0x7C
+ uint8_t packetId = SENSOR_ID_EAM_B; // 1 0x8E HOTT_Electric_Air_ID
+ uint8_t warnBeep = 0; // 2 warn beep (0 = no beep, 0x00..0x1A warn beeps)
+ uint8_t packetIdText = SENSOR_ID_EAM_T; // 3 0xE0 Sensor ID text mode
+ uint8_t inverse1 = 0; // 4 alarm bitmask. Value is displayed inverted
+ // Bit# Alarm field
+ // 0 mAh
+ // 1 Battery 1
+ // 2 Battery 2
+ // 3 Temperature 1
+ // 4 Temperature 2
+ // 5 Altitude
+ // 6 Current
+ // 7 Main power voltage
+ uint8_t inverse2 = 0; // 5 alarm bitmask. Value is displayed inverted
+ // Bit# Alarm Field
+ // 0 m/s
+ // 1 m/3s
+ // 2 Altitude (duplicate?)
+ // 3 m/s (duplicate?)
+ // 4 m/3s (duplicate?)
+ // 5 unknown/unused
+ // 6 unknown/unused
+ // 7 "ON" sign/text msg active
+ uint8_t cellL[7] = {0}; // 06 cell 1 voltage lower value. 0.02V steps, 124=2.48V
+ uint8_t cellH[7] = {0}; // 13 cell 1 voltage high value. 0.02V steps, 124=2.48V
+ uint16_t battVoltage1 = 0; // 20 battery 1 voltage lower value in 100mv steps, 50=5V. optionally cell8_L value 0.02V steps
+ uint16_t battVoltage2 = 0; // 22 battery 2 voltage lower value in 100mv steps, 50=5V. optionally cell8_H value. 0.02V steps
+ uint8_t temp1 = 20; // 24 Temperature sensor 1. 20=0�, 46=26� - offset of 20.
+ uint8_t temp2 = 20; // 25 temperature sensor 2
+ uint16_t altitude = 500; // 26 Altitude lower value. unit: meters. Value of 500 = 0m
+ uint16_t current = 0; // 28 Current in 0.1 steps
+ uint16_t mainVoltage = 0; // 30 Main power voltage (drive) in 0.1V steps
+ uint16_t capacity = 0; // 32 used battery capacity in 10mAh steps
+ uint16_t mPerSec = 30000; // 34 climb rate in 0.01m/s. Value of 30000 = 0.00 m/s
+ uint8_t mPer3sec = 120; // 36 climbrate in m/3sec. Value of 120 = 0m/3sec
+ uint16_t rpm = 0; // 37 RPM. Steps: 10 U/min
+ uint8_t electricMin = 0; // 39 Electric minutes. Time does start, when motor current is > 3 A
+ uint8_t electricSec = 0; // 40 Electric seconds.
+ uint16_t speed = 0; // 41 speed in km/h. Steps 1km/h
+ uint8_t endByte = END_FRAME; // 43 0x7D
+ uint8_t Crc; // 44 CRC
+} PACKED ElectricAirPacket_t;
+
+//
+// ESC data frame data structure
+//
+typedef struct HOTT_AIRESC_MSG_s
+{
+ uint8_t startByte = START_FRAME_B; // 0 0x7C
+ uint8_t packetId = SENSOR_ID_ESC_B; // 1 0x8C HOTT_General_AIR_PACKET_ID
+ uint8_t warnBeep = 0; // 2 warn beep (0 = no beep, 0x00..0x1A warn beeps)
+ uint8_t packetIdText = SENSOR_ID_ESC_T; // 3 0xC0 Sensor ID text mode
+ uint8_t inverse1 = 0; // 4 TODO: more info
+ uint8_t inverse2 = 0; // 5 TODO: more info
+ uint16_t inputVoltage = 0; // 6 Input voltage
+ uint16_t inputVoltageMin = 0; // 8 Input min. voltage
+ uint16_t capacity = 0; // 10 battery capacity in 10mAh steps
+ uint8_t escTemp = 0; // 12 ESC temperature
+ uint8_t escMaxTemp = 0; // 13 ESC max. temperature
+ uint16_t current = 0; // 14 Current in 0.1 steps
+ uint16_t currentMax = 0; // 16 Current max. in 0.1 steps
+ uint16_t rpm = 0; // 18 RPM in 10U/min steps
+ uint16_t rpmMax = 0; // 20 RPM max
+ uint8_t motorTemp = 0; // 22 motor temp
+ uint8_t motortempMax = 0; // 23 motor temp max
+ uint16_t speed = 0; // 24 Speed
+ uint16_t speedMax = 0; // 26 Speed max
+ uint8_t pwm = 0; // 28 PWM in %
+ uint8_t throttle = 0; // 29 throttle in %
+ uint8_t becVoltage = 0; // 30 BEC voltage
+ uint8_t becVoltageMin = 0; // 31 BEC voltage min
+ uint8_t becCurrent = 0; // 32 BEC current
+ uint8_t becTemp = 0; // 34 BEC temperature
+ uint8_t capacitorTemp = 0; // 35 Capacitor temperature
+ uint8_t motorTiming = 0; // 36 motor timing
+ uint8_t auxTemp = 0; // 37 advanced timing/aux temp
+ uint16_t thrust = 0; // 38 thrust/force
+ uint8_t pumpTemp = 0; // 40 pump temperature
+ uint8_t turbineNumber = 0; // 41 engine number (200 = #1, 201 = #2)
+ uint8_t version = 3; // 42 version number = 3 for new protocol
+ uint8_t endByte = END_FRAME; // 43 0x7D
+ uint8_t crc; // 44 CRC
+} PACKED AirESCPacket_t;
+
+//
+// VARIO data frame data structure
+//
+typedef struct HOTT_VARIO_MSG_s
+{
+ uint8_t startByte = START_FRAME_B; // 0 0x7C
+ uint8_t packetId = SENSOR_ID_VARIO_B; // 1 0x89 HOTT_Vario_PACKET_ID
+ uint8_t warnBeep = 0; // 2 warn beep (0 = no beep, 0x00..0x1A warn beeps)
+ uint8_t packetIdText = SENSOR_ID_VARIO_T; // 3 0x90 Sensor ID text mode
+ uint8_t inverse1 = 0; // 4 Inverse display (alarm?) bitmask
+ uint16_t altitude = 500; // 5 Altitude low uint8_t. In meters. A value of 500 means 0m
+ uint16_t altitudeMax = 500; // 7 Max. measured altitude low uint8_t. In meters. A value of 500 means 0m
+ uint16_t altitudeMin = 500; // 9 Min. measured altitude low uint8_t. In meters. A value of 500 means 0m
+ uint16_t mPerSec = 30000; // 11 Climb rate in m/s. Steps of 0.01m/s. Value of 30000 = 0.00 m/s
+ uint16_t mPerSec3s = 30000; // 13 Climb rate in m/3s. Steps of 0.01m/3s. Value of 30000 = 0.00 m/3s
+ uint16_t mPerSec10s = 30000; // 15 Climb rate m/10s. Steps of 0.01m/10s. Value of 30000 = 0.00 m/10s
+ uint8_t textMsg[21]; // 17 Free ASCII text message
+ uint8_t freeChar1 = ' '; // 38 Free ASCII character. appears right to home distance
+ uint8_t freeChar2 = ' '; // 39 Free ASCII character. appears right to home direction
+ uint8_t freeChar3 = ' '; // 40 Free ASCII character. appears? TODO: Check where this char appears
+ uint8_t compassDir = 0; // 41 Compass heading in 2 degrees steps. 1 = 2 degrees
+ uint8_t version = 0; // 42 version number = 3 for new protocol
+ uint8_t endByte = END_FRAME; // 43 0x7D
+ uint8_t crc; // 44 CRC
+} PACKED VarioPacket_t;
+
+typedef struct
+{
+ uint8_t cmdMirror[CMD_LEN];
+ uint8_t payload[FRAME_SIZE];
+} PACKED hottBusFrame_t;
+
+enum HoTTDevices
+{
+ FIRST_DEVICE = 0,
+ GPS = FIRST_DEVICE,
+ EAM,
+ GAM,
+ ESC,
+ VARIO,
+ LAST_DEVICE
+};
+
+typedef struct
+{
+ uint8_t deviceID;
+ bool present;
+} hottDevice_t;
+
+enum {
+ HOTT_RECEIVING,
+ HOTT_CMD1SENT,
+ HOTT_CMD2SENT
+};
+
+class SerialHoTT_TLM : public SerialIO
+{
+public:
+ explicit SerialHoTT_TLM(Stream &out, Stream &in)
+ : SerialIO(&out, &in)
+ {
+ // use UART0 default TX pin for half duplex if not defined otherwise
+ halfDuplexPin = GPIO_PIN_RCSIGNAL_TX == UNDEF_PIN ? U0TXD_GPIO_NUM : GPIO_PIN_RCSIGNAL_TX;
+
+ uint32_t now = millis();
+
+ lastPoll = now;
+ discoveryTimerStart = now;
+
+ cmdSendState = HOTT_RECEIVING;
+ }
+
+ virtual ~SerialHoTT_TLM() {}
+
+ void queueLinkStatisticsPacket() override {}
+ void queueMSPFrameTransmission(uint8_t *data) override {}
+ uint32_t sendRCFrame(bool frameAvailable, bool frameMissed, uint32_t *channelData) override { return DURATION_IMMEDIATELY; };
+
+ int getMaxSerialReadSize() override;
+ void sendQueuedData(uint32_t maxBytesToSend) override;
+
+private:
+ void setTXMode();
+ void setRXMode();
+
+ void processBytes(uint8_t *bytes, u_int16_t size) override;
+ void processFrame();
+ uint8_t calcFrameCRC(uint8_t *buf);
+
+ void scheduleDevicePolling(uint32_t now);
+
+ void scheduleCRSFtelemetry(uint32_t now);
+ void sendCRSFvario(uint32_t now);
+ void sendCRSFgps(uint32_t now);
+ void sendCRSFbattery(uint32_t now);
+
+ uint16_t getHoTTvoltage();
+ uint16_t getHoTTcurrent();
+ uint32_t getHoTTcapacity();
+ int16_t getHoTTaltitude();
+ int16_t getHoTTvv();
+ uint8_t getHoTTremaining();
+ int32_t getHoTTlatitude();
+ int32_t getHoTTlongitude();
+ uint16_t getHoTTgroundspeed();
+ uint16_t getHoTTheading();
+ uint8_t getHoTTsatellites();
+ uint16_t getHoTTMSLaltitude();
+
+ uint32_t htobe24(uint32_t val);
+
+ // last received HoTT telemetry packets
+ GPSPacket_t gps;
+ GeneralAirPacket_t gam;
+ AirESCPacket_t esc;
+ VarioPacket_t vario;
+ ElectricAirPacket_t eam;
+
+ // received HoTT bus fra,e
+ hottBusFrame_t hottBusFrame;
+
+ // discoverd devices
+ hottDevice_t device[LAST_DEVICE] = {
+ {SENSOR_ID_GPS_B, false},
+ {SENSOR_ID_EAM_B, false},
+ {SENSOR_ID_GAM_B, false},
+ {SENSOR_ID_ESC_B, false},
+ {SENSOR_ID_VARIO_B, false}};
+
+ FIFO hottInputBuffer;
+
+ uint8_t halfDuplexPin;
+
+ bool discoveryMode = true;
+ uint8_t nextDevice = FIRST_DEVICE;
+ uint8_t nextDeviceID;
+
+ uint32_t lastPoll;
+ uint8_t cmdSendState;
+ uint32_t discoveryTimerStart;
+
+ uint32_t lastVarioSent = 0;
+ uint32_t lastVarioCRC = 0;
+ uint32_t lastGPSSent = 0;
+ uint32_t lastGPSCRC = 0;
+ uint32_t lastBatterySent = 0;
+ uint32_t lastBatteryCRC = 0;
+
+ const uint8_t DegMinScale = 100;
+ const uint8_t SecScale = 100;
+ const uint8_t MinDivide = 6;
+ const int32_t MinScale = 1000000L;
+ const int32_t DegScale = 10000000L;
+};
+
+#endif
diff --git a/src/src/rx-serial/SerialIO.cpp b/src/src/rx-serial/SerialIO.cpp
index 182b109877..9e67400699 100644
--- a/src/src/rx-serial/SerialIO.cpp
+++ b/src/src/rx-serial/SerialIO.cpp
@@ -1,40 +1,11 @@
#include "SerialIO.h"
-#if defined(USE_MSP_WIFI)
-#include "crsf2msp.h"
-#include "msp2crsf.h"
-
-extern CROSSFIRE2MSP crsf2msp;
-extern MSP2CROSSFIRE msp2crsf;
-#endif
-
-void SerialIO::handleUARTout()
+void SerialIO::setFailsafe(bool failsafe)
{
- // don't write more than 128 bytes at a time to avoid RX buffer overflow
- const int maxBytesPerCall = 128;
- uint32_t bytesWritten = 0;
- #if defined(USE_MSP_WIFI)
- while (msp2crsf.FIFOout.size() > msp2crsf.FIFOout.peek() && (bytesWritten + msp2crsf.FIFOout.peek()) < maxBytesPerCall)
- {
- uint8_t OutPktLen = msp2crsf.FIFOout.pop();
- uint8_t OutData[OutPktLen];
- msp2crsf.FIFOout.popBytes(OutData, OutPktLen);
- this->_outputPort->write(OutData, OutPktLen); // write the packet out
- bytesWritten += OutPktLen;
- }
- #endif
-
- while (_fifo.size() > _fifo.peek() && (bytesWritten + _fifo.peek()) < maxBytesPerCall)
- {
- uint8_t OutPktLen = _fifo.pop();
- uint8_t OutData[OutPktLen];
- _fifo.popBytes(OutData, OutPktLen);
- this->_outputPort->write(OutData, OutPktLen); // write the packet out
- bytesWritten += OutPktLen;
- }
+ this->failsafe = failsafe;
}
-void SerialIO::handleUARTin()
+void SerialIO::processSerialInput()
{
auto maxBytes = getMaxSerialReadSize();
uint8_t buffer[maxBytes];
@@ -43,15 +14,18 @@ void SerialIO::handleUARTin()
processBytes(buffer, size);
}
-void SerialIO::processBytes(uint8_t *bytes, uint16_t size)
+void SerialIO::sendQueuedData(uint32_t maxBytesToSend)
{
- for (int i=0 ; i _fifo.peek() && (bytesWritten + _fifo.peek()) < maxBytesToSend)
{
- processByte(bytes[i]);
+ _fifo.lock();
+ uint8_t OutPktLen = _fifo.pop();
+ uint8_t OutData[OutPktLen];
+ _fifo.popBytes(OutData, OutPktLen);
+ _fifo.unlock();
+ this->_outputPort->write(OutData, OutPktLen); // write the packet out
+ bytesWritten += OutPktLen;
}
}
-
-void SerialIO::setFailsafe(bool failsafe)
-{
- this->failsafe = failsafe;
-}
diff --git a/src/src/rx-serial/SerialIO.h b/src/src/rx-serial/SerialIO.h
index 4d1a1ad813..babd3ce3be 100644
--- a/src/src/rx-serial/SerialIO.h
+++ b/src/src/rx-serial/SerialIO.h
@@ -2,31 +2,129 @@
#include "targets.h"
#include "FIFO.h"
+#include "device.h"
+/**
+ * @brief Abstract class that is to be extended by implementation classes for different serial protocols on the receiver side.
+ *
+ * At a minimum, a new protocol extension class should implement the following functions
+ *
+ * * queueLinkStatisticsPacket
+ * * queueMSPFrameTransmission
+ * * sendRCFrame
+ * * sendQueuedData
+ * * processBytes
+ */
class SerialIO {
public:
- const int defaultMaxSerialReadSize = 64;
SerialIO(Stream *output, Stream *input) : _outputPort(output), _inputPort(input) {}
virtual ~SerialIO() {}
- virtual void setLinkQualityStats(uint16_t lq, uint16_t rssi) = 0;
- virtual void sendLinkStatisticsToFC() = 0;
- virtual void sendMSPFrameToFC(uint8_t* data) = 0;
- virtual void setFailsafe(bool failsafe);
+ /**
+ * @brief Set the Failsafe flag
+ *
+ * This allows the serial protocol implementation to react accordingly.
+ * e.g. if it needs to set a flag in the serial messages, or stop sending over
+ * serial port.
+ *
+ * @param failsafe true when the firmware has detected a failsafe condition.
+ */
+ void setFailsafe(bool failsafe);
- virtual uint32_t sendRCFrameToFC(bool frameAvailable, uint32_t *channelData) = 0;
+ /**
+ * @brief Signals the protocol to queue a link statistics packet
+ *
+ * The packet should be queued into the `_fifo` member variable as RC packets
+ * are prioritised and ancillary data is sent after the RC data when the
+ * `sendQueuedData` function is called.
+ */
+ virtual void queueLinkStatisticsPacket() = 0;
- virtual int getMaxSerialReadSize() { return defaultMaxSerialReadSize; }
- virtual void handleUARTout();
- virtual void handleUARTin();
+ /**
+ * @brief Signals that the MSP frame should be queued for transmission.
+ *
+ * The MSP frame should be queued as in `queueLinkStatisticsPacket` so it can be
+ * sent after any RC data.
+ *
+ * @param data pointer to the MSP packet
+ */
+ virtual void queueMSPFrameTransmission(uint8_t* data) = 0;
+
+ /**
+ * @brief send the RC channel data to the serial port stream `_outputPort` member
+ * variable.
+ *
+ * If the function wishes to be called as fast as possible, then it should return
+ * DURATION_IMMEDIATE, otherwise it should return the number of milliseconds delay
+ * before this method is called again.
+ *
+ * @param frameAvailable indicates that a new OTA frame of data has been received
+ * since the last call to this function
+ * @param frameMissed indicates that a frame was not received in the OTA window
+ * @param channelData pointer to the 16 channels of data
+ * @return number of milliseconds to delay before this method is called again
+ */
+ virtual uint32_t sendRCFrame(bool frameAvailable, bool frameMissed, uint32_t *channelData) = 0;
+
+ /**
+ * @brief send any previously queued data to the serial port stream `_outputPort`
+ * member variable.
+ */
+ virtual void sendQueuedData(uint32_t maxBytesToSend);
+
+ /**
+ * @brief read bytes from the serial port and process them.
+ *
+ * The maximum number of bytes to read per call is obtained
+ * from the `getMaxSerialReadSize` method call.
+ *
+ * This method *should* not be overridden by custom implementations, it is
+ * only overridden by the `SerialNOOP` implementation.
+ */
+ virtual void processSerialInput();
+
+ /**
+ * @brief Get the maximum number of bytes to write to the serial port in each call.
+ *
+ * @return maximum number of bytes to write
+ */
+ virtual int getMaxSerialWriteSize() { return defaultMaxSerialWriteSize; }
protected:
+ /// @brief the output stream for the serial port
Stream *_outputPort;
- Stream *_inputPort;
- FIFO _fifo;
+ /// @brief flag that indicates the receiver is in the failsafe state
bool failsafe = false;
- virtual void processBytes(uint8_t *bytes, uint16_t size);
- virtual void processByte(uint8_t byte) = 0;
+ static const uint32_t SERIAL_OUTPUT_FIFO_SIZE = 256U;
+
+
+ /**
+ * @brief the FIFO that should be used to queue serial data to in the
+ * `queueLinkStatisticsPacket` and `queueMSPFrameTransmission` method implementations.
+ */
+ FIFO _fifo;
+
+ /**
+ * @brief Get the maximum number of bytes to read from the serial port per call
+ *
+ * @return the maximum number of bytes to read
+ */
+ virtual int getMaxSerialReadSize() { return defaultMaxSerialReadSize; }
+
+ /**
+ * @brief Protocol specific method to process the bytes that have been read
+ * from the serial port by the framework calling the `processSerialInput` method.
+ *
+ * @param bytes pointer to the byte array that contains the serial data
+ * @param size number of bytes in the buffer
+ */
+ virtual void processBytes(uint8_t *bytes, uint16_t size) = 0;
+
+private:
+ const int defaultMaxSerialReadSize = 64;
+ const int defaultMaxSerialWriteSize = 128;
+
+ Stream *_inputPort;
};
diff --git a/src/src/rx-serial/SerialNOOP.h b/src/src/rx-serial/SerialNOOP.h
index 3554da42f0..73e76901f7 100644
--- a/src/src/rx-serial/SerialNOOP.h
+++ b/src/src/rx-serial/SerialNOOP.h
@@ -5,18 +5,14 @@
class SerialNOOP : public SerialIO {
public:
explicit SerialNOOP() : SerialIO(nullptr, nullptr) {}
-
virtual ~SerialNOOP() {}
- void setLinkQualityStats(uint16_t lq, uint16_t rssi) override {}
- void sendLinkStatisticsToFC() override {}
- void sendMSPFrameToFC(uint8_t* data) override {}
-
- uint32_t sendRCFrameToFC(bool frameAvailable, uint32_t *channelData) override { return 10; }
+ void queueLinkStatisticsPacket() override {}
+ void queueMSPFrameTransmission(uint8_t* data) override {}
+ uint32_t sendRCFrame(bool frameAvailable, bool frameMissed, uint32_t *channelData) override { return DURATION_NEVER; }
- void handleUARTin() {}
- void handleUARTout() { _fifo.flush(); }
+ void processSerialInput() override {}
private:
- void processByte(uint8_t byte) override {}
+ void processBytes(uint8_t *bytes, uint16_t size) override {}
};
diff --git a/src/src/rx-serial/SerialSBUS.cpp b/src/src/rx-serial/SerialSBUS.cpp
index 44ca43af7d..be24e2c933 100644
--- a/src/src/rx-serial/SerialSBUS.cpp
+++ b/src/src/rx-serial/SerialSBUS.cpp
@@ -8,32 +8,27 @@
#define SBUS_FLAG_SIGNAL_LOSS (1 << 2)
#define SBUS_FLAG_FAILSAFE_ACTIVE (1 << 3)
-extern RxConfig config;
-
+const auto UNCONNECTED_CALLBACK_INTERVAL_MS = 10;
const auto SBUS_CALLBACK_INTERVAL_MS = 9;
-void SerialSBUS::setLinkQualityStats(uint16_t lq, uint16_t rssi)
-{
- // unsupported
-}
-
-void SerialSBUS::sendLinkStatisticsToFC()
-{
- // unsupported
-}
-
-uint32_t SerialSBUS::sendRCFrameToFC(bool frameAvailable, uint32_t *channelData)
+uint32_t SerialSBUS::sendRCFrame(bool frameAvailable, bool frameMissed, uint32_t *channelData)
{
static auto sendPackets = false;
- if ((failsafe && config.GetFailsafeMode() == FAILSAFE_NO_PULSES) || (!sendPackets && connectionState != connected))
+ bool effectivelyFailsafed = failsafe || (!connectionHasModelMatch) || (!teamraceHasModelMatch);
+ if ((effectivelyFailsafed && config.GetFailsafeMode() == FAILSAFE_NO_PULSES) || (!sendPackets && connectionState != connected))
{
- return SBUS_CALLBACK_INTERVAL_MS;
+ return UNCONNECTED_CALLBACK_INTERVAL_MS;
}
sendPackets = true;
+ if ((!frameAvailable && !frameMissed) || _outputPort->availableForWrite() < 25)
+ {
+ return DURATION_IMMEDIATELY;
+ }
+
// TODO: if failsafeMode == FAILSAFE_SET_POSITION then we use the set positions rather than the last values
crsf_channels_s PackedRCdataOut;
-
+
if (config.GetSerialProtocol() == PROTOCOL_DJI_RS_PRO)
{
PackedRCdataOut.ch0 = fmap(channelData[0], CRSF_CHANNEL_VALUE_MIN, CRSF_CHANNEL_VALUE_MAX, 352, 1696);
@@ -74,8 +69,8 @@ uint32_t SerialSBUS::sendRCFrameToFC(bool frameAvailable, uint32_t *channelData)
}
uint8_t extraData = 0;
- extraData |= failsafe ? SBUS_FLAG_FAILSAFE_ACTIVE : 0;
- extraData |= frameAvailable ? 0 : SBUS_FLAG_SIGNAL_LOSS;
+ extraData |= effectivelyFailsafed ? SBUS_FLAG_FAILSAFE_ACTIVE : 0;
+ extraData |= frameMissed ? SBUS_FLAG_SIGNAL_LOSS : 0;
_outputPort->write(0x0F); // HEADER
_outputPort->write((byte *)&PackedRCdataOut, RCframeLength);
@@ -84,9 +79,4 @@ uint32_t SerialSBUS::sendRCFrameToFC(bool frameAvailable, uint32_t *channelData)
return SBUS_CALLBACK_INTERVAL_MS;
}
-void SerialSBUS::sendMSPFrameToFC(uint8_t* data)
-{
- // unsupported
-}
-
#endif
diff --git a/src/src/rx-serial/SerialSBUS.h b/src/src/rx-serial/SerialSBUS.h
index 1341b0fa80..6143ba8831 100644
--- a/src/src/rx-serial/SerialSBUS.h
+++ b/src/src/rx-serial/SerialSBUS.h
@@ -3,14 +3,12 @@
class SerialSBUS : public SerialIO {
public:
explicit SerialSBUS(Stream &out, Stream &in) : SerialIO(&out, &in) {}
+ ~SerialSBUS() override = default;
- virtual ~SerialSBUS() {}
-
- void setLinkQualityStats(uint16_t lq, uint16_t rssi) override;
- uint32_t sendRCFrameToFC(bool frameAvailable, uint32_t *channelData) override;
- void sendMSPFrameToFC(uint8_t* data) override;
- void sendLinkStatisticsToFC() override;
+ void queueLinkStatisticsPacket() override {}
+ void queueMSPFrameTransmission(uint8_t* data) override {}
+ uint32_t sendRCFrame(bool frameAvailable, bool frameMissed, uint32_t *channelData) override;
private:
- void processByte(uint8_t byte) override {};
+ void processBytes(uint8_t *bytes, uint16_t size) override {};
};
diff --git a/src/src/rx-serial/SerialSUMD.cpp b/src/src/rx-serial/SerialSUMD.cpp
index d12ec39e54..ab62b2fc1e 100644
--- a/src/src/rx-serial/SerialSUMD.cpp
+++ b/src/src/rx-serial/SerialSUMD.cpp
@@ -5,95 +5,76 @@
#define SUMD_HEADER_SIZE 3 // 3 Bytes header
#define SUMD_DATA_SIZE_16CH (16*2) // 2 Bytes per channel
#define SUMD_CRC_SIZE 2 // 16 bit CRC
-#define SUMD_FRAME_16CH_LEN (SUMD_HEADER_SIZE+SUMD_DATA_SIZE_16CH+SUMD_CRC_SIZE)
+#define SUMD_FRAME_16CH_LEN (SUMD_HEADER_SIZE+SUMD_DATA_SIZE_16CH+SUMD_CRC_SIZE)
-void SerialSUMD::setLinkQualityStats(uint16_t lq, uint16_t rssi)
-{
- linkQuality = lq;
- rssiDBM = rssi;
-}
+const auto SUMD_CALLBACK_INTERVAL_MS = 10;
-uint32_t SerialSUMD::sendRCFrameToFC(bool frameAvailable, uint32_t *channelData)
+uint32_t SerialSUMD::sendRCFrame(bool frameAvailable, bool frameMissed, uint32_t *channelData)
{
if (!frameAvailable) {
return DURATION_IMMEDIATELY;
}
- uint8_t outBuffer[SUMD_FRAME_16CH_LEN];
+ uint8_t outBuffer[SUMD_FRAME_16CH_LEN];
- outBuffer[0] = 0xA8; //Graupner
- outBuffer[1] = 0x01; //SUMD
- outBuffer[2] = 0x10; //16CH
+ outBuffer[0] = 0xA8; //Graupner
+ outBuffer[1] = 0x01; //SUMD
+ outBuffer[2] = 0x10; //16CH
- uint16_t us = (CRSF_to_US(ChannelData[0]) << 3);
- outBuffer[3] = us >> 8;
+ uint16_t us = (CRSF_to_US(channelData[0]) << 3);
+ outBuffer[3] = us >> 8;
outBuffer[4] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[1]) << 3);
- outBuffer[5] = us >> 8;
+ us = (CRSF_to_US(channelData[1]) << 3);
+ outBuffer[5] = us >> 8;
outBuffer[6] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[2]) << 3);
- outBuffer[7] = us >> 8;
+ us = (CRSF_to_US(channelData[2]) << 3);
+ outBuffer[7] = us >> 8;
outBuffer[8] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[3]) << 3);
- outBuffer[9] = us >> 8;
+ us = (CRSF_to_US(channelData[3]) << 3);
+ outBuffer[9] = us >> 8;
outBuffer[10] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[7]) << 3); //channel 8 mapped to 5 to move arm channel away from the aileron function
- outBuffer[11] = us >> 8;
+ us = (CRSF_to_US(channelData[7]) << 3); //channel 8 mapped to 5 to move arm channel away from the aileron function
+ outBuffer[11] = us >> 8;
outBuffer[12] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[5]) << 3);
- outBuffer[13] = us >> 8;
+ us = (CRSF_to_US(channelData[5]) << 3);
+ outBuffer[13] = us >> 8;
outBuffer[14] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[6]) << 3);
- outBuffer[15] = us >> 8;
+ us = (CRSF_to_US(channelData[6]) << 3);
+ outBuffer[15] = us >> 8;
outBuffer[16] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[4]) << 3); //channel 5 mapped to 8
- outBuffer[17] = us >> 8;
+ us = (CRSF_to_US(channelData[4]) << 3); //channel 5 mapped to 8
+ outBuffer[17] = us >> 8;
outBuffer[18] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[8]) << 3);
- outBuffer[19] = us >> 8;
+ us = (CRSF_to_US(channelData[8]) << 3);
+ outBuffer[19] = us >> 8;
outBuffer[20] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[9]) << 3);
- outBuffer[21] = us >> 8;
+ us = (CRSF_to_US(channelData[9]) << 3);
+ outBuffer[21] = us >> 8;
outBuffer[22] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[10]) << 3);
- outBuffer[23] = us >> 8;
+ us = (CRSF_to_US(channelData[10]) << 3);
+ outBuffer[23] = us >> 8;
outBuffer[24] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[11]) << 3);
- outBuffer[25] = us >> 8;
+ us = (CRSF_to_US(channelData[11]) << 3);
+ outBuffer[25] = us >> 8;
outBuffer[26] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[12]) << 3);
- outBuffer[27] = us >> 8;
+ us = (CRSF_to_US(channelData[12]) << 3);
+ outBuffer[27] = us >> 8;
outBuffer[28] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[13]) << 3);
- outBuffer[29] = us >> 8;
+ us = (CRSF_to_US(channelData[13]) << 3);
+ outBuffer[29] = us >> 8;
outBuffer[30] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[14]) << 3);
- outBuffer[31] = us >> 8;
+ us = (CRSF_to_US(channelData[14]) << 3);
+ outBuffer[31] = us >> 8;
outBuffer[32] = us & 0x00ff;
- us = (CRSF_to_US(ChannelData[15]) << 3);
- outBuffer[33] = us >> 8;
+ us = (CRSF_to_US(channelData[15]) << 3);
+ outBuffer[33] = us >> 8;
outBuffer[34] = us & 0x00ff;
-
- uint16_t crc = crc2Byte.calc(outBuffer, (SUMD_HEADER_SIZE + SUMD_DATA_SIZE_16CH), 0);
- outBuffer[35] = (uint8_t)(crc >> 8);
- outBuffer[36] = (uint8_t)(crc & 0x00ff);
-
- _outputPort->write(outBuffer, sizeof(outBuffer));
-
- return DURATION_IMMEDIATELY;
-}
-void SerialSUMD::sendLinkStatisticsToFC()
-{
- // unsupported
-}
+ uint16_t crc = crc2Byte.calc(outBuffer, (SUMD_HEADER_SIZE + SUMD_DATA_SIZE_16CH), 0);
+ outBuffer[35] = (uint8_t)(crc >> 8);
+ outBuffer[36] = (uint8_t)(crc & 0x00ff);
-void SerialSUMD::sendMSPFrameToFC(uint8_t* data)
-{
- (void)data;
- // unsupported
-}
+ _outputPort->write(outBuffer, sizeof(outBuffer));
-void processByte(uint8_t byte) {
- // unsupported
-}
\ No newline at end of file
+ return SUMD_CALLBACK_INTERVAL_MS;
+}
diff --git a/src/src/rx-serial/SerialSUMD.h b/src/src/rx-serial/SerialSUMD.h
index 20e542be07..8c8d212dcc 100644
--- a/src/src/rx-serial/SerialSUMD.h
+++ b/src/src/rx-serial/SerialSUMD.h
@@ -4,19 +4,13 @@
class SerialSUMD : public SerialIO {
public:
explicit SerialSUMD(Stream &out, Stream &in) : SerialIO(&out, &in) { crc2Byte.init(16, 0x1021); }
-
virtual ~SerialSUMD() {}
- Crc2Byte crc2Byte;
-
- void setLinkQualityStats(uint16_t lq, uint16_t rssi) override;
- uint32_t sendRCFrameToFC(bool frameAvailable, uint32_t *channelData) override;
- void sendMSPFrameToFC(uint8_t* data) override;
- void sendLinkStatisticsToFC() override;
+ void queueLinkStatisticsPacket() override {}
+ void queueMSPFrameTransmission(uint8_t* data) override {}
+ uint32_t sendRCFrame(bool frameAvailable, bool frameMissed, uint32_t *channelData) override;
private:
- uint16_t linkQuality = 0;
- uint16_t rssiDBM = 0;
-
- void processByte(uint8_t byte) override {};
+ Crc2Byte crc2Byte;
+ void processBytes(uint8_t *bytes, uint16_t size) override {};
};
diff --git a/src/src/rx-serial/devSerialIO.cpp b/src/src/rx-serial/devSerialIO.cpp
index fedb92b973..32f3b36326 100644
--- a/src/src/rx-serial/devSerialIO.cpp
+++ b/src/src/rx-serial/devSerialIO.cpp
@@ -6,16 +6,29 @@
#include "device.h"
#include "SerialIO.h"
#include "CRSF.h"
+#include "config.h"
extern SerialIO *serialIO;
static volatile bool frameAvailable = false;
+static volatile bool frameMissed = false;
+static enum teamraceOutputInhibitState_e {
+ troiPass = 0, // Allow all packets through, normal operation
+ troiDisableAwaitConfirm, // Have received one packet with another model selected, awaiting confirm to Inhibit
+ troiInhibit, // Inhibit all output
+ troiEnableAwaitConfirm, // Have received one packet with this model selected, awaiting confirm to Pass
+} teamraceOutputInhibitState;
void ICACHE_RAM_ATTR crsfRCFrameAvailable()
{
frameAvailable = true;
}
+void ICACHE_RAM_ATTR crsfRCFrameMissed()
+{
+ frameMissed = true;
+}
+
static int start()
{
return DURATION_IMMEDIATELY;
@@ -29,6 +42,106 @@ static int event()
return DURATION_IGNORE;
}
+/***
+ * @brief: Convert the current TeamraceChannel value to the appropriate config value for comparison
+*/
+static uint8_t teamraceChannelToConfigValue()
+{
+ // SWITCH3b is 1,2,3,4,5,6,x,Mid
+ // 0 1 2 3 4 5 7
+ // Config values are Disabled,1,2,3,Mid,4,5,6
+ // 0 1 2 3 4 5 6 7
+ uint8_t retVal = CRSF_to_SWITCH3b(ChannelData[config.GetTeamraceChannel()]);
+ switch (retVal)
+ {
+ case 0: // passthrough
+ case 1: // passthrough
+ case 2:
+ return retVal + 1;
+ case 3: // passthrough
+ case 4: // passthrough
+ case 5:
+ return retVal + 2;
+ case 7:
+ return 4; // "Mid"
+ default:
+ // CRSF_to_SWITCH3b should only return 0-5,7 but we must return a value
+ return 0;
+ }
+}
+
+/***
+ * @brief: Determine if FrameAvailable and it should be sent to FC
+ * @return: TRUE if a new frame is available and should be processed
+*/
+static bool confirmFrameAvailable()
+{
+ if (!frameAvailable)
+ return false;
+ frameAvailable = false;
+
+ // ModelMatch failure always prevents passing the frame on
+ if (!connectionHasModelMatch)
+ return false;
+
+ constexpr uint8_t CONFIG_TEAMRACE_POS_OFF = 0;
+ if (config.GetTeamracePosition() == CONFIG_TEAMRACE_POS_OFF)
+ {
+ teamraceOutputInhibitState = troiPass;
+ return true;
+ }
+
+ // Pass the packet on if in troiPass (of course) or
+ // troiDisableAwaitConfirm (keep sending channels until the teamracepos stabilizes)
+ bool retVal = teamraceOutputInhibitState < troiInhibit;
+
+ static uint8_t lastTeamracePosition;
+ uint8_t newTeamracePosition = teamraceChannelToConfigValue();
+ switch (teamraceOutputInhibitState)
+ {
+ case troiPass:
+ // User appears to be switching away from this model, wait for confirm
+ if (newTeamracePosition != config.GetTeamracePosition())
+ teamraceOutputInhibitState = troiDisableAwaitConfirm;
+ break;
+
+ case troiDisableAwaitConfirm:
+ // Must receive the same new position twice in a row for state to change
+ if (lastTeamracePosition == newTeamracePosition)
+ {
+ if (newTeamracePosition != config.GetTeamracePosition())
+ teamraceOutputInhibitState = troiInhibit; // disable output
+ else
+ teamraceOutputInhibitState = troiPass; // return to normal
+ }
+ break;
+
+ case troiInhibit:
+ // User appears to be switching to this model, wait for confirm
+ if (newTeamracePosition == config.GetTeamracePosition())
+ teamraceOutputInhibitState = troiEnableAwaitConfirm;
+ break;
+
+ case troiEnableAwaitConfirm:
+ // Must receive the same new position twice in a row for state to change
+ if (lastTeamracePosition == newTeamracePosition)
+ {
+ if (newTeamracePosition == config.GetTeamracePosition())
+ teamraceOutputInhibitState = troiPass; // return to normal
+ else
+ teamraceOutputInhibitState = troiInhibit; // back to disabled
+ }
+ break;
+ }
+
+ lastTeamracePosition = newTeamracePosition;
+ // troiPass or troiDisablePending indicate the model is selected still,
+ // however returning true if troiDisablePending means this RX could send
+ // telemetry and we do not want that
+ teamraceHasModelMatch = teamraceOutputInhibitState == troiPass;
+ return retVal;
+}
+
static int timeout()
{
if (connectionState == serialUpdate)
@@ -36,16 +149,32 @@ static int timeout()
return DURATION_NEVER; // stop callbacks when doing serial update
}
- uint32_t duration = 10; // 10ms callback (i.e. when no theres no model match)
- // only send frames if we have a model match
- if (connectionHasModelMatch)
- {
- duration = serialIO->sendRCFrameToFC(frameAvailable, ChannelData);
- }
- frameAvailable = false;
+ /***
+ * TODO: This contains a problem!!
+ * confirmFrameAvailable() is designed to be the thing that determines if RC frames are to be sent out
+ * which includes:
+ * - No data received to be sent (interpacket delay)
+ * - Connection does not have model match
+ * - TeamRace enabled but different position selected
+ * However, the SBUS IO writer doesn't go off new data coming in, it just sends data at a 9ms cadence
+ * and therefore does not respect any of these conditions, relying on the one-off "failsafe" member
+ * modelmatch was addressed in #2211, but resolving the merge conflict here (capnbry) re-breaks it
+ *
+ * Commiting this anyway though to work out a better resolution
+ */
+
+ noInterrupts();
+ bool missed = frameMissed;
+ frameMissed = false;
+ interrupts();
+
+ // Verify there is new ChannelData and they should be sent on
+ bool sendChannels = confirmFrameAvailable();
+ uint32_t duration = serialIO->sendRCFrame(sendChannels, missed, ChannelData);
+
// still get telemetry and send link stats if theres no model match
- serialIO->handleUARTout();
- serialIO->handleUARTin();
+ serialIO->processSerialInput();
+ serialIO->sendQueuedData(serialIO->getMaxSerialWriteSize());
return duration;
}
diff --git a/src/src/rx-serial/devSerialIO.h b/src/src/rx-serial/devSerialIO.h
index 90ac72f8e8..3b0551c8eb 100644
--- a/src/src/rx-serial/devSerialIO.h
+++ b/src/src/rx-serial/devSerialIO.h
@@ -4,3 +4,4 @@
extern device_t Serial_device;
extern void crsfRCFrameAvailable();
+extern void crsfRCFrameMissed();
diff --git a/src/src/rx_main.cpp b/src/src/rx_main.cpp
index fad8c9a6d3..7059df0036 100644
--- a/src/src/rx_main.cpp
+++ b/src/src/rx_main.cpp
@@ -12,6 +12,7 @@
#include "msptypes.h"
#include "PFD.h"
#include "options.h"
+#include "dynpower.h"
#include "MeanAccumulator.h"
#include "freqTable.h"
@@ -21,6 +22,7 @@
#include "rx-serial/SerialSBUS.h"
#include "rx-serial/SerialSUMD.h"
#include "rx-serial/SerialAirPort.h"
+#include "rx-serial/SerialHoTT_TLM.h"
#include "rx-serial/devSerialIO.h"
#include "devLED.h"
@@ -35,6 +37,7 @@
#include "devMSPVTX.h"
#if defined(PLATFORM_ESP8266)
+#include
#include
#elif defined(PLATFORM_ESP32)
#include
@@ -48,7 +51,11 @@
#define SEND_LINK_STATS_TO_FC_INTERVAL 100
#define DIVERSITY_ANTENNA_INTERVAL 5
#define DIVERSITY_ANTENNA_RSSI_TRIGGER 5
+#if defined(RADIO_LR1121)
+#define PACKET_TO_TOCK_SLACK 240 // Desired buffer time between Packet ISR and Tock ISR. Increase slack due to the LR1121s slow processing time. Mainly required for 500Hz mode.
+#else
#define PACKET_TO_TOCK_SLACK 200 // Desired buffer time between Packet ISR and Tock ISR
+#endif
///////////////////
device_affinity_t ui_devices[] = {
@@ -89,8 +96,6 @@ device_affinity_t ui_devices[] = {
uint8_t antenna = 0; // which antenna is currently in use
uint8_t geminiMode = 0;
-hwTimer hwTimer;
-POWERMGNT POWERMGNT;
PFD PFDloop;
Crc2Byte ota_crc;
ELRS_EEPROM eeprom;
@@ -211,20 +216,8 @@ static bool debugRcvrLinkstatsPending;
static uint8_t debugRcvrLinkstatsFhssIdx;
#endif
-#define LOAN_BIND_TIMEOUT_DEFAULT 60000
-#define LOAN_BIND_TIMEOUT_MSP 10000U
-
+bool BindingModeRequest = false;
-bool InBindingMode = false;
-bool InLoanBindingMode = false;
-bool returnModelFromLoan = false;
-static unsigned long loanBindTimeout = LOAN_BIND_TIMEOUT_DEFAULT;
-static unsigned long loadBindingStartedMs = 0;
-
-void reset_into_bootloader(void);
-void EnterBindingMode();
-void ExitBindingMode();
-void OnELRSBindMSP(uint8_t* packet);
extern void setWifiUpdateMode();
uint8_t getLq()
@@ -236,7 +229,7 @@ static inline void checkGeminiMode()
{
if (isDualRadio())
{
- geminiMode = config.GetAntennaMode();
+ geminiMode = config.GetAntennaMode() || FHSSuseDualBand; // Force Gemini when in DualBand mode. Whats the point of using a single frequency!
}
}
@@ -258,11 +251,6 @@ static uint8_t minLqForChaos()
void ICACHE_RAM_ATTR getRFlinkInfo()
{
- if (GPIO_PIN_NSS_2 != UNDEF_PIN)
- {
- antenna = (Radio.GetProcessingPacketRadio() == SX12XX_Radio_1) ? 0 : 1;
- }
-
int32_t rssiDBM = Radio.LastPacketRSSI;
if (GPIO_PIN_NSS_2 != UNDEF_PIN)
@@ -279,7 +267,7 @@ void ICACHE_RAM_ATTR getRFlinkInfo()
// BetaFlight/iNav expect positive values for -dBm (e.g. -80dBm -> sent as 80)
CRSF::LinkStatistics.uplink_RSSI_1 = -rssiDBM;
CRSF::LinkStatistics.uplink_RSSI_2 = -rssiDBM2;
- antenna = (rssiDBM > rssiDBM2)? 0 : 1; // report a better antenna for the reception
+ antenna = (Radio.GetProcessingPacketRadio() == SX12XX_Radio_1) ? 0 : 1;
}
else if (antenna == 0)
{
@@ -301,11 +289,6 @@ void ICACHE_RAM_ATTR getRFlinkInfo()
CRSF::LinkStatistics.uplink_RSSI_2 = -rssiDBM;
}
- serialIO->setLinkQualityStats(
- UINT10_to_CRSF(fmap(uplinkLQ, 0, 100, 0, 1023)),
- UINT10_to_CRSF(map(constrain(rssiDBM, ExpressLRS_currAirRate_RFperfParams->RXsensitivity, -50),
- ExpressLRS_currAirRate_RFperfParams->RXsensitivity, -50, 0, 1023))
- );
SnrMean.add(Radio.LastPacketSNRRaw);
CRSF::LinkStatistics.active_antenna = antenna;
@@ -327,23 +310,38 @@ void ICACHE_RAM_ATTR getRFlinkInfo()
#endif
}
-void SetRFLinkRate(uint8_t index) // Set speed of RF link
+void SetRFLinkRate(uint8_t index, bool bindMode) // Set speed of RF link
{
expresslrs_mod_settings_s *const ModParams = get_elrs_airRateConfig(index);
expresslrs_rf_pref_params_s *const RFperf = get_elrs_RFperfParams(index);
- bool invertIQ = UID[5] & 0x01;
+ // Binding always uses invertIQ
+ bool invertIQ = bindMode || (UID[5] & 0x01);
uint32_t interval = ModParams->interval;
#if defined(DEBUG_FREQ_CORRECTION) && defined(RADIO_SX128X)
interval = interval * 12 / 10; // increase the packet interval by 20% to allow adding packet header
#endif
- hwTimer.updateInterval(interval);
- Radio.Config(ModParams->bw, ModParams->sf, ModParams->cr, GetInitialFreq(),
+
+ hwTimer::updateInterval(interval);
+
+ FHSSusePrimaryFreqBand = !(ModParams->radio_type == RADIO_TYPE_LR1121_LORA_2G4);
+ FHSSuseDualBand = ModParams->radio_type == RADIO_TYPE_LR1121_LORA_DUAL;
+
+ Radio.Config(ModParams->bw, ModParams->sf, ModParams->cr, FHSSgetInitialFreq(),
ModParams->PreambleLen, invertIQ, ModParams->PayloadLength, 0
#if defined(RADIO_SX128X)
, uidMacSeedGet(), OtaCrcInitializer, (ModParams->radio_type == RADIO_TYPE_SX128x_FLRC)
#endif
);
+
+#if defined(RADIO_LR1121)
+ if (FHSSuseDualBand)
+ {
+ Radio.Config(ModParams->bw2, ModParams->sf2, ModParams->cr2, FHSSgetInitialGeminiFreq(),
+ ModParams->PreambleLen2, invertIQ, ModParams->PayloadLength, 0, SX12XX_Radio_2);
+ }
+#endif
+
Radio.FuzzySNRThreshold = (RFperf->DynpowerSnrThreshUp == DYNPOWER_SNR_THRESH_NONE) ? 0 : (RFperf->DynpowerSnrThreshDn - RFperf->DynpowerSnrThreshUp);
checkGeminiMode();
@@ -378,15 +376,17 @@ bool ICACHE_RAM_ATTR HandleFHSS()
if (geminiMode)
{
- if (((OtaNonce + 1)/ExpressLRS_currAirRate_Modparams->FHSShopInterval) % 2 == 0)
+ if ((((OtaNonce + 1)/ExpressLRS_currAirRate_Modparams->FHSShopInterval) % 2 == 0) || FHSSuseDualBand) // When in DualBand do not switch between radios. The OTA modulation paramters and HighFreq/LowFreq Tx amps are set during Config.
{
Radio.SetFrequencyReg(FHSSgetNextFreq(), SX12XX_Radio_1);
Radio.SetFrequencyReg(FHSSgetGeminiFreq(), SX12XX_Radio_2);
}
else
{
- Radio.SetFrequencyReg(FHSSgetNextFreq(), SX12XX_Radio_2);
+ // Write radio1 first. This optimises the SPI traffic order.
+ uint32_t freqRadio2 = FHSSgetNextFreq();
Radio.SetFrequencyReg(FHSSgetGeminiFreq(), SX12XX_Radio_1);
+ Radio.SetFrequencyReg(freqRadio2, SX12XX_Radio_2);
}
}
else
@@ -422,7 +422,14 @@ void ICACHE_RAM_ATTR LinkStatsToOta(OTA_LinkStats_s * const ls)
#if defined(DEBUG_FREQ_CORRECTION)
ls->SNR = FreqCorrection * 127 / FreqCorrectionMax;
#else
- ls->SNR = SnrMean.mean();
+ if (SnrMean.getCount())
+ {
+ ls->SNR = SnrMean.mean();
+ }
+ else
+ {
+ ls->SNR = SnrMean.previousMean();
+ }
#endif
}
@@ -430,15 +437,11 @@ bool ICACHE_RAM_ATTR HandleSendTelemetryResponse()
{
uint8_t modresult = (OtaNonce + 1) % ExpressLRS_currTlmDenom;
- if ((connectionState == disconnected) || (ExpressLRS_currTlmDenom == 1) || (alreadyTLMresp == true) || (modresult != 0))
+ if ((connectionState == disconnected) || (ExpressLRS_currTlmDenom == 1) || (alreadyTLMresp == true) || (modresult != 0) || !teamraceHasModelMatch)
{
return false; // don't bother sending tlm if disconnected or TLM is off
}
-#if defined(Regulatory_Domain_EU_CE_2400)
- BeginClearChannelAssessment();
-#endif
-
// ESP requires word aligned buffer
WORD_ALIGNED_ATTR OTA_Packet_s otaPkt = {0};
alreadyTLMresp = true;
@@ -581,25 +584,25 @@ void ICACHE_RAM_ATTR updatePhaseLock()
{
if (Offset > 0)
{
- hwTimer.incFreqOffset();
+ hwTimer::incFreqOffset();
}
else if (Offset < 0)
{
- hwTimer.decFreqOffset();
+ hwTimer::decFreqOffset();
}
}
}
if (connectionState != connected)
{
- hwTimer.phaseShift(RawOffset >> 1);
+ hwTimer::phaseShift(RawOffset >> 1);
}
else
{
- hwTimer.phaseShift(Offset >> 2);
+ hwTimer::phaseShift(Offset >> 2);
}
- DBGVLN("%d:%d:%d:%d:%d", Offset, RawOffset, OffsetDx, hwTimer.FreqOffset, uplinkLQ);
+ DBGVLN("%d:%d:%d:%d:%d", Offset, RawOffset, OffsetDx, hwTimer::getFreqOffset(), uplinkLQ);
UNUSED(OffsetDx); // complier warning if no debug
}
@@ -729,15 +732,33 @@ void ICACHE_RAM_ATTR HWtimerCallbackTock()
{
if (tlmSent && Radio.GetLastTransmitRadio() == SX12XX_Radio_NONE)
{
- Radio.TXdoneCallback();
+ // Since we were meant to send telemetry, but didn't, defer TXdoneCallback() to when the IRQ is normally triggered.
+ deferExecutionMicros(ExpressLRS_currAirRate_RFperfParams->TOA, []() {
+ Radio.TXdoneCallback();
+ });
}
PFDloop.intEvent(micros()); // our internal osc just fired
- if (ExpressLRS_currAirRate_Modparams->numOfSends > 1 && !(OtaNonce % ExpressLRS_currAirRate_Modparams->numOfSends) && LQCalcDVDA.currentIsSet())
+ if (ExpressLRS_currAirRate_Modparams->numOfSends > 1 && !(OtaNonce % ExpressLRS_currAirRate_Modparams->numOfSends))
+ {
+ if (LQCalcDVDA.currentIsSet())
+ {
+ crsfRCFrameAvailable();
+ if (teamraceHasModelMatch)
+ servoNewChannelsAvailable();
+ }
+ else
+ {
+ crsfRCFrameMissed();
+ }
+ }
+ else if (ExpressLRS_currAirRate_Modparams->numOfSends == 1)
{
- crsfRCFrameAvailable();
- servoNewChannelsAvaliable();
+ if (!LQCalc.currentIsSet())
+ {
+ crsfRCFrameMissed();
+ }
}
if (!didFHSS)
@@ -761,12 +782,16 @@ void ICACHE_RAM_ATTR HWtimerCallbackTock()
void LostConnection(bool resumeRx)
{
- DBGLN("lost conn fc=%d fo=%d", FreqCorrection, hwTimer.FreqOffset);
+ DBGLN("lost conn fc=%d fo=%d", FreqCorrection, hwTimer::getFreqOffset());
+
+ // Use this rate as the initial rate next time if we connected on it
+ if (connectionState == connected)
+ config.SetRateInitialIdx(ExpressLRS_nextAirRateIndex);
RFmodeCycleMultiplier = 1;
connectionState = disconnected; //set lost connection
RXtimerState = tim_disconnected;
- hwTimer.resetFreqOffset();
+ hwTimer::resetFreqOffset();
PfdPrevRawOffset = 0;
GotConnectionMillis = 0;
uplinkLQ = 0;
@@ -782,9 +807,9 @@ void LostConnection(bool resumeRx)
if (hwTimer::running)
{
while(micros() - PFDloop.getIntEventTime() > 250); // time it just after the tock()
- hwTimer.stop();
+ hwTimer::stop();
}
- SetRFLinkRate(ExpressLRS_nextAirRateIndex); // also sets to initialFreq
+ SetRFLinkRate(ExpressLRS_nextAirRateIndex, false); // also sets to initialFreq
// If not resumRx, Radio will be left in SX127x_OPMODE_STANDBY / SX1280_MODE_STDBY_XOSC
if (resumeRx)
{
@@ -805,7 +830,7 @@ void ICACHE_RAM_ATTR TentativeConnection(unsigned long now)
SnrMean.reset();
RFmodeLastCycled = now; // give another 3 sec for lock to occur
- // The caller MUST call hwTimer.resume(). It is not done here because
+ // The caller MUST call hwTimer::resume(). It is not done here because
// the timer ISR will fire immediately and preempt any other code
}
@@ -850,7 +875,10 @@ static void ICACHE_RAM_ATTR ProcessRfPacket_RC(OTA_Packet_s const * const otaPkt
if (ExpressLRS_currAirRate_Modparams->numOfSends == 1)
{
crsfRCFrameAvailable();
- servoNewChannelsAvaliable();
+ // teamrace is only checked for servos because the teamrace model select logic only runs
+ // when new frames are available, and will decide later if the frame will be forwarded
+ if (teamraceHasModelMatch)
+ servoNewChannelsAvailable();
}
else if (!LQCalcDVDA.currentIsSet())
{
@@ -862,6 +890,23 @@ static void ICACHE_RAM_ATTR ProcessRfPacket_RC(OTA_Packet_s const * const otaPkt
}
}
+void ICACHE_RAM_ATTR OnELRSBindMSP(uint8_t* newUid4)
+{
+ // Binding over MSP only contains 4 bytes due to packet size limitations, clear out any leading bytes
+ UID[0] = 0;
+ UID[1] = 0;
+ for (unsigned i = 0; i < 4; i++)
+ {
+ UID[i + 2] = newUid4[i];
+ }
+
+ DBGLN("New UID = %u, %u, %u, %u, %u, %u", UID[0], UID[1], UID[2], UID[3], UID[4], UID[5]);
+
+ // Set new UID in eeprom
+ // EEPROM commit will happen on the main thread in ExitBindingMode()
+ config.SetUID(UID);
+}
+
static void ICACHE_RAM_ATTR ProcessRfPacket_MSP(OTA_Packet_s const * const otaPktPtr)
{
uint8_t packageIndex;
@@ -1061,7 +1106,7 @@ bool ICACHE_RAM_ATTR ProcessRFPacket(SX12xxDriverCommon::rx_status const status)
if (otaPktPtr->std.type != PACKET_TYPE_SYNC) DBGW(connectionHasModelMatch ? 'R' : 'r');
#endif
if (doStartTimer)
- hwTimer.resume(); // will throw an interrupt immediately
+ hwTimer::resume(); // will throw an interrupt immediately
return true;
}
@@ -1083,10 +1128,9 @@ bool ICACHE_RAM_ATTR RXdoneISR(SX12xxDriverCommon::rx_status const status)
void ICACHE_RAM_ATTR TXdoneISR()
{
-#if defined(Regulatory_Domain_EU_CE_2400)
- BeginClearChannelAssessment();
-#else
Radio.RXnb();
+#if defined(Regulatory_Domain_EU_CE_2400)
+ SetClearChannelAssessmentTime();
#endif
#if defined(DEBUG_RX_SCOREBOARD)
DBGW('T');
@@ -1101,7 +1145,7 @@ void UpdateModelMatch(uint8_t model)
void SendMSPFrameToFC(uint8_t *mspData)
{
- serialIO->sendMSPFrameToFC(mspData);
+ serialIO->queueMSPFrameTransmission(mspData);
}
/**
@@ -1115,15 +1159,11 @@ void MspReceiveComplete()
#if defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)
// The MSP packet needs to be ACKed so the TX doesn't
// keep sending it, so defer the switch to wifi
- deferExecution(500, []() {
+ deferExecutionMillis(500, []() {
setWifiUpdateMode();
});
#endif
break;
- case MSP_ELRS_SET_RX_LOAN_MODE: //0x0F
- loanBindTimeout = LOAN_BIND_TIMEOUT_MSP;
- InLoanBindingMode = true;
- break;
default:
//handle received CRSF package
crsf_ext_header_t *receivedHeader = (crsf_ext_header_t *) MspData;
@@ -1135,6 +1175,17 @@ void MspReceiveComplete()
UpdateModelMatch(MspData[9]);
break;
}
+ else if (OPT_HAS_VTX_SPI && MspData[7] == MSP_SET_VTX_CONFIG)
+ {
+ vtxSPIFrequency = getFreqByIdx(MspData[8]);
+ if (MspData[6] >= 4) // If packet has 4 bytes it also contains power idx and pitmode.
+ {
+ vtxSPIPowerIdx = MspData[10];
+ vtxSPIPitmode = MspData[11];
+ }
+ devicesTriggerEvent();
+ break;
+ }
// FALLTHROUGH
default:
if ((receivedHeader->dest_addr == CRSF_ADDRESS_BROADCAST || receivedHeader->dest_addr == CRSF_ADDRESS_CRSF_RECEIVER))
@@ -1148,9 +1199,10 @@ void MspReceiveComplete()
break;
}
// No MSP data to the FC if no model match
- if (connectionHasModelMatch && (receivedHeader->dest_addr == CRSF_ADDRESS_BROADCAST || receivedHeader->dest_addr == CRSF_ADDRESS_FLIGHT_CONTROLLER))
+ if (connectionHasModelMatch && teamraceHasModelMatch &&
+ (receivedHeader->dest_addr == CRSF_ADDRESS_BROADCAST || receivedHeader->dest_addr == CRSF_ADDRESS_FLIGHT_CONTROLLER))
{
- serialIO->sendMSPFrameToFC(MspData);
+ serialIO->queueMSPFrameTransmission(MspData);
}
}
@@ -1162,12 +1214,22 @@ static void setupSerial()
bool sbusSerialOutput = false;
bool sumdSerialOutput = false;
+#if defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32)
+ bool hottTlmSerial = false;
+#endif
+
if (OPT_CRSF_RCVR_NO_SERIAL)
{
// For PWM receivers with no serial pins defined, only turn on the Serial port if logging is on
- #if defined(DEBUG_LOG)
+ #if defined(DEBUG_LOG) || defined(DEBUG_RCVR_LINKSTATS)
+ #if defined(PLATFORM_ESP32_S3) && !defined(ESP32_S3_USB_JTAG_ENABLED)
+ // Requires pull-down on GPIO3. If GPIO3 has a pull-up (for JTAG) this doesn't work.
+ USBSerial.begin(serialBaud);
+ SerialLogger = &USBSerial;
+ #else
Serial.begin(serialBaud);
SerialLogger = &Serial;
+ #endif
#else
SerialLogger = new NullStream();
#endif
@@ -1185,6 +1247,13 @@ static void setupSerial()
sumdSerialOutput = true;
serialBaud = 115200;
}
+#if defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32)
+ else if (config.GetSerialProtocol() == PROTOCOL_HOTT_TLM)
+ {
+ hottTlmSerial = true;
+ serialBaud = 19200;
+ }
+#endif
bool invert = config.GetSerialProtocol() == PROTOCOL_SBUS || config.GetSerialProtocol() == PROTOCOL_INVERTED_CRSF || config.GetSerialProtocol() == PROTOCOL_DJI_RS_PRO;
#ifdef PLATFORM_STM32
@@ -1239,11 +1308,31 @@ static void setupSerial()
#endif
#if defined(PLATFORM_ESP8266)
- SerialConfig config = sbusSerialOutput ? SERIAL_8E2 : SERIAL_8N1;
+ SerialConfig config = SERIAL_8N1;
+
+ if(sbusSerialOutput)
+ {
+ config = SERIAL_8E2;
+ }
+ else if(hottTlmSerial)
+ {
+ config = SERIAL_8N2;
+ }
+
SerialMode mode = (sbusSerialOutput || sumdSerialOutput) ? SERIAL_TX_ONLY : SERIAL_FULL;
Serial.begin(serialBaud, config, mode, -1, invert);
#elif defined(PLATFORM_ESP32)
- uint32_t config = sbusSerialOutput ? SERIAL_8E2 : SERIAL_8N1;
+ uint32_t config = SERIAL_8N1;
+
+ if(sbusSerialOutput)
+ {
+ config = SERIAL_8E2;
+ }
+ else if(hottTlmSerial)
+ {
+ config = SERIAL_8N2;
+ }
+
Serial.begin(serialBaud, config, GPIO_PIN_RCSIGNAL_RX, GPIO_PIN_RCSIGNAL_TX, invert);
#endif
@@ -1259,11 +1348,22 @@ static void setupSerial()
{
serialIO = new SerialSUMD(SERIAL_PROTOCOL_TX, SERIAL_PROTOCOL_RX);
}
+ #if defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32)
+ else if (hottTlmSerial)
+ {
+ serialIO = new SerialHoTT_TLM(SERIAL_PROTOCOL_TX, SERIAL_PROTOCOL_RX);
+ }
+ #endif
else
{
serialIO = new SerialCRSF(SERIAL_PROTOCOL_TX, SERIAL_PROTOCOL_RX);
}
+#if defined(PLATFORM_ESP32_S3)
+ USBSerial.begin(460800);
+ SerialLogger = &USBSerial;
+#else
SerialLogger = &Serial;
+#endif
}
static void serialShutdown()
@@ -1292,35 +1392,21 @@ static void setupConfigAndPocCheck()
config.SetStorageProvider(&eeprom); // Pass pointer to the Config class for access to storage
config.Load();
- bool doPowerCount = config.GetOnLoan() || !firmwareOptions.hasUID;
- if (doPowerCount)
+ // If bound, track number of plug/unplug cycles to go to binding mode in eeprom
+ if (config.GetIsBound() && config.GetPowerOnCounter() < 3)
{
- DBGLN("Doing power-up check for loan revocation and/or re-binding");
- // Increment the power on counter in eeprom
config.SetPowerOnCounter(config.GetPowerOnCounter() + 1);
config.Commit();
+ }
- if (config.GetPowerOnCounter() >= 3)
+ // Set a deferred function to clear the power on counter if the RX has been running for more than 2s
+ deferExecutionMillis(2000, []() {
+ if (connectionState != connected && config.GetPowerOnCounter() != 0)
{
- if (config.GetOnLoan())
- {
- config.SetOnLoan(false);
- config.SetPowerOnCounter(0);
- config.Commit();
- }
- }
- else
- {
- // We haven't reached our binding mode power cycles
- // and we've been powered on for 2s, reset the power on counter.
- // config.Commit() is done in the loop with CheckConfigChangePending().
- deferExecution(2000, []() {
- config.SetPowerOnCounter(0);
- });
+ config.SetPowerOnCounter(0);
+ config.Commit();
}
- }
-
- DBGLN("ModelId=%u", config.GetModelId());
+ });
}
static void setupTarget()
@@ -1346,32 +1432,27 @@ static void setupTarget()
static void setupBindingFromConfig()
{
- // Use the user defined binding phase if set,
- // otherwise use the bind flag and UID in eeprom for UID
- if (config.GetOnLoan())
- {
- DBGLN("RX has been loaned, reading the UID from eeprom...");
- memcpy(UID, config.GetOnLoanUID(), sizeof(UID));
- }
- // Check the byte that indicates if RX has been bound
- else if (!firmwareOptions.hasUID && config.GetIsBound())
+ // VolatileBind's only function is to prevent loading the stored UID into RAM
+ // which makes the RX boot into bind mode every time
+ if (config.GetIsBound())
{
- DBGLN("RX has been bound previously, reading the UID from eeprom...");
- memcpy(UID, config.GetUID(), sizeof(UID));
+ memcpy(UID, config.GetUID(), UID_LEN);
}
- DBGLN("UID = %d, %d, %d, %d, %d, %d", UID[0], UID[1], UID[2], UID[3], UID[4], UID[5]);
+ DBGLN("UID=(%d, %d, %d, %d, %d, %d) ModelId=%u",
+ UID[0], UID[1], UID[2], UID[3], UID[4], UID[5], config.GetModelId());
+
OtaUpdateCrcInitFromUid();
}
static void setupRadio()
{
- Radio.currFreq = GetInitialFreq();
+ Radio.currFreq = FHSSgetInitialFreq();
#if defined(RADIO_SX127X)
//Radio.currSyncWord = UID[3];
#endif
- bool init_success = Radio.Begin();
- POWERMGNT.init();
+ bool init_success = Radio.Begin(FHSSgetMinimumFreq(), FHSSgetMaximumFreq());
+ POWERMGNT::init();
if (!init_success)
{
DBGLN("Failed to detect RF chipset!!!");
@@ -1379,7 +1460,7 @@ static void setupRadio()
return;
}
- POWERMGNT.setPower((PowerLevels_e)config.GetPower());
+ DynamicPower_UpdateRx(true);
#if defined(Regulatory_Domain_EU_CE_2400)
LBTEnabled = (config.GetPower() > PWR_10mW);
@@ -1389,7 +1470,7 @@ static void setupRadio()
Radio.TXdoneCallback = &TXdoneISR;
scanIndex = config.GetRateInitialIdx();
- SetRFLinkRate(scanIndex);
+ SetRFLinkRate(scanIndex, false);
// Start slow on the selected rate to give it the best chance
// to connect before beginning rate cycling
RFmodeCycleMultiplier = RFmodeCycleMultiplierSlow / 2;
@@ -1422,9 +1503,9 @@ static void cycleRfMode(unsigned long now)
RFmodeLastCycled = now;
LastSyncPacket = now; // reset this variable
SendLinkStatstoFCForcedSends = 2;
- SetRFLinkRate(scanIndex % RATE_MAX); // switch between rates
- LQCalc.reset();
- LQCalcDVDA.reset();
+ SetRFLinkRate(scanIndex % RATE_MAX, false); // switch between rates
+ LQCalc.reset100();
+ LQCalcDVDA.reset100();
// Display the current air rate to the user as an indicator something is happening
scanIndex++;
Radio.RXnb();
@@ -1435,56 +1516,134 @@ static void cycleRfMode(unsigned long now)
} // if time to switch RF mode
}
-static void updateBindingMode(unsigned long now)
+static void EnterBindingMode()
{
-#ifndef MY_UID
- // If the eeprom is indicating that we're not bound
- // and we're not already in binding mode, enter binding
- if (!config.GetIsBound() && !InBindingMode)
+ if (InBindingMode)
{
- DBGLN("RX has not been bound, enter binding mode...");
- EnterBindingMode();
+ DBGLN("Already in binding mode");
+ return;
}
-#endif
- // If in "loan" binding mode and we're not configured then start binding
- if (!InBindingMode && InLoanBindingMode && !config.GetOnLoan()) {
- EnterBindingMode();
+
+ // Binding uses a CRCInit=0, 50Hz, and InvertIQ
+ OtaCrcInitializer = 0;
+ InBindingMode = true;
+
+ // Start attempting to bind
+ // Lock the RF rate and freq while binding
+ SetRFLinkRate(enumRatetoIndex(RATE_BINDING), true);
+ Radio.SetFrequencyReg(FHSSgetInitialFreq());
+ if (geminiMode)
+ {
+ Radio.SetFrequencyReg(FHSSgetInitialGeminiFreq(), SX12XX_Radio_2);
}
- // If in "loan" binding mode and the bind packet has come in, leave binding mode
- else if (InBindingMode && InLoanBindingMode && config.GetOnLoan()) {
- ExitBindingMode();
+ // If the Radio Params (including InvertIQ) parameter changed, need to restart RX to take effect
+ Radio.RXnb();
+
+ DBGLN("Entered binding mode at freq = %d", Radio.currFreq);
+ devicesTriggerEvent();
+}
+
+static void ExitBindingMode()
+{
+ if (!InBindingMode)
+ {
+ DBGLN("Not in binding mode");
+ return;
}
- // If in binding mode and the bind packet has come in, leave binding mode
- else if (InBindingMode && !InLoanBindingMode && config.GetIsBound())
+
+ MspReceiver.ResetState();
+
+ // Prevent any new packets from coming in
+ Radio.SetTxIdleMode();
+ // Write the values to eeprom
+ config.Commit();
+
+ OtaUpdateCrcInitFromUid();
+ FHSSrandomiseFHSSsequence(uidMacSeedGet());
+
+ #if defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)
+ webserverPreventAutoStart = true;
+ #endif
+
+ // Force RF cycling to start at the beginning immediately
+ scanIndex = RATE_MAX;
+ RFmodeLastCycled = 0;
+ LockRFmode = false;
+ LostConnection(false);
+
+ // Do this last as LostConnection() will wait for a tock that never comes
+ // if we're in binding mode
+ InBindingMode = false;
+ DBGLN("Exiting binding mode");
+ devicesTriggerEvent();
+}
+
+static void updateBindingMode()
+{
+ // Exit binding mode if the config has been modified, indicating UID has been set
+ if (InBindingMode && config.IsModified())
{
ExitBindingMode();
}
- // If in "loan" binding mode and we've been here for more than timeout period, reset UID and leave binding mode
- else if (InBindingMode && InLoanBindingMode && (now - loadBindingStartedMs) > loanBindTimeout) {
- loanBindTimeout = LOAN_BIND_TIMEOUT_DEFAULT;
- memcpy(UID, MasterUID, sizeof(MasterUID));
- setupBindingFromConfig();
- ExitBindingMode();
+
+ // If the power on counter is >=3, enter binding, the counter will be reset after 2s
+ else if (config.GetPowerOnCounter() >= 3)
+ {
+#if defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)
+ // Never enter wifi if forced to binding mode
+ webserverPreventAutoStart = true;
+#endif
+ DBGLN("Power on counter >=3, enter binding mode...");
+ EnterBindingMode();
}
- // If returning the model to the owner, set the flag and call ExitBindingMode to reset the CRC and FHSS
- else if (returnModelFromLoan && config.GetOnLoan()) {
- LostConnection(false);
- config.SetOnLoan(false);
- memcpy(UID, MasterUID, sizeof(MasterUID));
- setupBindingFromConfig();
- ExitBindingMode();
+
+ // If the eeprom is indicating that we're not bound, enter binding
+ else if (!UID_IS_BOUND(UID) && !InBindingMode)
+ {
+ DBGLN("RX has not been bound, enter binding mode...");
+ EnterBindingMode();
}
- // If the power on counter is >=3, enter binding and clear counter
- if (!firmwareOptions.hasUID && config.GetPowerOnCounter() >= 3)
+ else if (BindingModeRequest)
{
- config.SetPowerOnCounter(0);
+ DBGLN("Connected request to enter binding mode...");
+ BindingModeRequest = false;
+ if (connectionState == connected)
+ LostConnection(false);
+ EnterBindingMode();
+ }
+}
+
+void EnterBindingModeSafely()
+{
+ // Will not enter Binding mode if in the process of a passthrough update
+ // or currently binding
+ if (connectionState == serialUpdate || InBindingMode)
+ return;
+
+#if defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)
+ // Never enter wifi mode after requesting to enter binding mode
+ webserverPreventAutoStart = true;
+
+ // If the radio and everything is shut down, better to reboot and boot to binding mode
+ if (connectionState == wifiUpdate || connectionState == bleJoystick)
+ {
+ // Force 3-plug binding mode
+ config.SetPowerOnCounter(3);
config.Commit();
+ ESP.restart();
+ // Unreachable
+ }
+#endif
- DBGLN("Power on counter >=3, enter binding mode...");
- config.SetIsBound(false);
- EnterBindingMode();
+ // If connected, handle that in updateBindingMode()
+ if (connectionState == connected)
+ {
+ BindingModeRequest = true;
+ return;
}
+
+ EnterBindingMode();
}
static void checkSendLinkStatsToFc(uint32_t now)
@@ -1496,10 +1655,10 @@ static void checkSendLinkStatsToFc(uint32_t now)
getRFlinkInfo();
}
- if ((connectionState != disconnected && connectionHasModelMatch) ||
+ if ((connectionState != disconnected && connectionHasModelMatch && teamraceHasModelMatch) ||
SendLinkStatstoFCForcedSends)
{
- serialIO->sendLinkStatisticsToFC();
+ serialIO->queueLinkStatisticsPacket();
SendLinkStatstoFCintervalLastSent = now;
if (SendLinkStatstoFCForcedSends)
--SendLinkStatstoFCForcedSends;
@@ -1588,7 +1747,7 @@ static void updateSwitchMode()
static void CheckConfigChangePending()
{
- if (config.IsModified() && !InBindingMode && connectionState != wifiUpdate)
+ if (config.IsModified() && !InBindingMode && connectionState < NO_CONFIG_SAVE_STATES)
{
LostConnection(false);
config.Commit();
@@ -1628,6 +1787,10 @@ void resetConfigAndReboot()
yield();
// Remove options.json and hardware.json
SPIFFS.format();
+ yield();
+ SPIFFS.begin();
+ options_SetTrueDefaults();
+
ESP.restart();
#endif
}
@@ -1635,13 +1798,13 @@ void resetConfigAndReboot()
void setup()
{
#if defined(TARGET_UNIFIED_RX)
- // Setup default logging in case of failure, or no layout
- Serial.begin(115200);
- SerialLogger = &Serial;
-
hardwareConfigured = options_init();
if (!hardwareConfigured)
{
+ // In the failure case we set the logging to the null logger so nothing crashes
+ // if it decides to log something
+ SerialLogger = new NullStream();
+
// Register the WiFi with the framework
static device_affinity_t wifi_device[] = {
{&WIFI_device, 1}
@@ -1669,11 +1832,17 @@ void setup()
SerialLogger = new NullStream();
#endif
- initUID();
+ // External EEPROM needs I2C setup so it can load config
+ // but configurable I2C pins for PWM RX needs config loaded first
+#if (defined(TARGET_USE_EEPROM) && defined(USE_I2C))
setupTarget();
-
+#endif
// Init EEPROM and load config, checking powerup count
setupConfigAndPocCheck();
+#if !(defined(TARGET_USE_EEPROM) && defined(USE_I2C))
+ setupTarget();
+#endif
+
#if defined(OPT_HAS_SERVO_OUTPUT)
// If serial is not already defined, then see if there is serial pin configured in the PWM configuration
if (GPIO_PIN_RCSIGNAL_RX == UNDEF_PIN && GPIO_PIN_RCSIGNAL_TX == UNDEF_PIN)
@@ -1691,8 +1860,6 @@ void setup()
#endif
setupSerial();
- DBGLN("ExpressLRS Module Booting...");
-
devicesRegister(ui_devices, ARRAY_SIZE(ui_devices));
devicesInit();
@@ -1707,17 +1874,14 @@ void setup()
// RFnoiseFloor = MeasureNoiseFloor(); //TODO move MeasureNoiseFloor to driver libs
// DBGLN("RF noise floor: %d dBm", RFnoiseFloor);
- hwTimer.callbackTock = &HWtimerCallbackTock;
- hwTimer.callbackTick = &HWtimerCallbackTick;
-
MspReceiver.SetDataToReceive(MspData, ELRS_MSP_BUFFER);
Radio.RXnb();
- hwTimer.init();
+ hwTimer::init(HWtimerCallbackTick, HWtimerCallbackTock);
}
}
#if defined(HAS_BUTTON)
- registerButtonFunction(ACTION_BIND, EnterBindingMode);
+ registerButtonFunction(ACTION_BIND, EnterBindingModeSafely);
registerButtonFunction(ACTION_RESET_REBOOT, resetConfigAndReboot);
#endif
@@ -1743,7 +1907,13 @@ void loop()
#endif
CheckConfigChangePending();
- executeDeferredFunction(now);
+ executeDeferredFunction(micros());
+
+ // Clear the power-on-count
+ if ((connectionState == connected || connectionState == tentative) && config.GetPowerOnCounter() != 0)
+ {
+ config.SetPowerOnCounter(0);
+ }
if (connectionState > MODE_STATES)
{
@@ -1795,11 +1965,13 @@ void loop()
TelemetrySender.SetDataToTransmit(nextPayload, nextPlayloadSize);
}
updateTelemetryBurst();
- updateBindingMode(now);
+ updateBindingMode();
updateSwitchMode();
checkGeminiMode();
+ DynamicPower_UpdateRx(false);
debugRcvrLinkstats();
debugRcvrSignalStats(now);
+ Radio.ignoreSecondIRQ = false;
}
struct bootloader {
@@ -1836,108 +2008,3 @@ void reset_into_bootloader(void)
connectionState = serialUpdate;
#endif
}
-
-void EnterBindingMode()
-{
- if (InLoanBindingMode)
- {
- loadBindingStartedMs = millis();
- LostConnection(false);
- }
- else if (connectionState == connected || InBindingMode)
- {
- // Don't enter binding if:
- // - we're already connected
- // - we're already binding
- DBGLN("Cannot enter binding mode!");
- return;
- }
-
- // Set UID to special binding values
- UID[0] = BindingUID[0];
- UID[1] = BindingUID[1];
- UID[2] = BindingUID[2];
- UID[3] = BindingUID[3];
- UID[4] = BindingUID[4];
- UID[5] = BindingUID[5];
-
- OtaCrcInitializer = 0;
- InBindingMode = true;
-
- // Start attempting to bind
- // Lock the RF rate and freq while binding
- SetRFLinkRate(enumRatetoIndex(RATE_BINDING));
- Radio.SetFrequencyReg(GetInitialFreq());
- if (geminiMode)
- {
- Radio.SetFrequencyReg(FHSSgetInitialGeminiFreq(), SX12XX_Radio_2);
- }
- // If the Radio Params (including InvertIQ) parameter changed, need to restart RX to take effect
- Radio.RXnb();
-
- DBGLN("Entered binding mode at freq = %d", Radio.currFreq);
- devicesTriggerEvent();
-}
-
-void ExitBindingMode()
-{
- if (!InBindingMode && !returnModelFromLoan)
- {
- // Not in binding mode
- DBGLN("Cannot exit binding mode, not in binding mode!");
- return;
- }
-
- MspReceiver.ResetState();
-
- // Prevent any new packets from coming in
- Radio.SetTxIdleMode();
- // Write the values to eeprom
- config.Commit();
-
- OtaUpdateCrcInitFromUid();
- FHSSrandomiseFHSSsequence(uidMacSeedGet());
-
- #if defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)
- webserverPreventAutoStart = true;
- #endif
-
- // Force RF cycling to start at the beginning immediately
- scanIndex = RATE_MAX;
- RFmodeLastCycled = 0;
- LockRFmode = false;
- LostConnection(false);
-
- // Do this last as LostConnection() will wait for a tock that never comes
- // if we're in binding mode
- InBindingMode = false;
- InLoanBindingMode = false;
- returnModelFromLoan = false;
- DBGLN("Exiting binding mode");
- devicesTriggerEvent();
-}
-
-void ICACHE_RAM_ATTR OnELRSBindMSP(uint8_t* newUid4)
-{
- for (int i = 0; i < 4; i++)
- {
- UID[i + 2] = newUid4[i];
- }
-
- DBGLN("New UID = %d, %d, %d, %d, %d, %d", UID[0], UID[1], UID[2], UID[3], UID[4], UID[5]);
-
- // Set new UID in eeprom
- if (InLoanBindingMode)
- {
- config.SetOnLoanUID(UID);
- config.SetOnLoan(true);
- }
- else
- {
- config.SetUID(UID);
- // Set eeprom byte to indicate RX is bound
- config.SetIsBound(true);
- }
-
- // EEPROM commit will happen on the main thread in ExitBindingMode()
-}
diff --git a/src/src/rxtx_common.cpp b/src/src/rxtx_common.cpp
index 39bd3eb914..fe368fef1c 100644
--- a/src/src/rxtx_common.cpp
+++ b/src/src/rxtx_common.cpp
@@ -1,5 +1,7 @@
#include "targets.h"
#include "common.h"
+#include "config.h"
+#include "logging.h"
#include
@@ -7,26 +9,67 @@
#include
#endif
-static uint32_t startDeferredTime = 0;
-static uint32_t deferredTimeout = 0;
-static std::function deferredFunction = nullptr;
+static const int maxDeferredFunctions = 3;
+
+struct deferred_t {
+ unsigned long started;
+ unsigned long timeout;
+ std::function function;
+};
+
+static deferred_t deferred[maxDeferredFunctions] = {
+ {0, 0, nullptr},
+ {0, 0, nullptr},
+ {0, 0, nullptr},
+};
+
+boolean i2c_enabled = false;
static void setupWire()
{
#if defined(USE_I2C)
- if(GPIO_PIN_SDA != UNDEF_PIN && GPIO_PIN_SCL != UNDEF_PIN)
+ int gpio_scl = GPIO_PIN_SCL;
+ int gpio_sda = GPIO_PIN_SDA;
+
+#if defined(TARGET_RX) && defined(GPIO_PIN_PWM_OUTPUTS)
+ for (int ch = 0 ; ch < GPIO_PIN_PWM_OUTPUTS_COUNT ; ++ch)
+ {
+ auto pin = GPIO_PIN_PWM_OUTPUTS[ch];
+ auto pwm = config.GetPwmChannel(ch);
+ // if the PWM pin is nominated as SDA or SCL, and it's not configured for I2C then undef the pins
+ if ((pin == GPIO_PIN_SCL && pwm->val.mode != somSCL) || (pin == GPIO_PIN_SDA && pwm->val.mode != somSDA))
+ {
+ gpio_scl = UNDEF_PIN;
+ gpio_sda = UNDEF_PIN;
+ break;
+ }
+ // If I2C pins are not defined in the hardware, then look for configured I2C
+ if (GPIO_PIN_SCL == UNDEF_PIN && pwm->val.mode == somSCL)
+ {
+ gpio_scl = pin;
+ }
+ if (GPIO_PIN_SCL == UNDEF_PIN && pwm->val.mode == somSDA)
+ {
+ gpio_sda = pin;
+ }
+ }
+#endif
+ if(gpio_sda != UNDEF_PIN && gpio_scl != UNDEF_PIN)
{
+ DBGLN("Starting wire on SCL %d, SDA %d", gpio_scl, gpio_sda);
#if defined(PLATFORM_STM32)
// Wire::begin() passing ints is ambiguously overloaded, use the set functions
// which themselves might get the PinName overloads
- Wire.setSCL(GPIO_PIN_SCL);
- Wire.setSDA(GPIO_PIN_SDA);
+ Wire.setSCL(gpio_scl);
+ Wire.setSDA(gpio_sda);
Wire.begin();
#else
// ESP hopes to get Wire::begin(int, int)
// ESP32 hopes to get Wire::begin(int = -1, int = -1, uint32 = 0)
- Wire.begin((int)GPIO_PIN_SDA, (int)GPIO_PIN_SCL);
+ Wire.begin(gpio_sda, gpio_scl);
+ Wire.setClock(400000);
#endif
+ i2c_enabled = true;
}
#endif
}
@@ -36,19 +79,32 @@ void setupTargetCommon()
setupWire();
}
-void deferExecution(uint32_t ms, std::function f)
+void deferExecutionMicros(unsigned long us, std::function f)
{
- startDeferredTime = millis();
- deferredTimeout = ms;
- deferredFunction = f;
+ for (int i=0 ; i deferredTimeout)
+ for (int i=0 ; i deferred[i].timeout)
+ {
+ deferred[i].function();
+ deferred[i].function = nullptr;
+ }
}
}
diff --git a/src/src/tx_main.cpp b/src/src/tx_main.cpp
index 6d06a8636a..5b8e97074b 100644
--- a/src/src/tx_main.cpp
+++ b/src/src/tx_main.cpp
@@ -1,13 +1,15 @@
#include "rxtx_common.h"
+#include "CRSFHandset.h"
#include "dynpower.h"
#include "lua.h"
#include "msp.h"
+#include "msptypes.h"
#include "telemetry_protocol.h"
#include "stubborn_receiver.h"
#include "stubborn_sender.h"
-#include "devCRSF.h"
+#include "devHandset.h"
#include "devLED.h"
#include "devScreen.h"
#include "devBuzzer.h"
@@ -25,9 +27,6 @@
#define MSP_PACKET_SEND_INTERVAL 10LU
/// define some libs to use ///
-hwTimer hwTimer;
-CRSF crsf;
-POWERMGNT POWERMGNT;
MSP msp;
ELRS_EEPROM eeprom;
TxConfig config;
@@ -35,8 +34,8 @@ Stream *TxBackpack;
Stream *TxUSB;
// Variables / constants for Airport //
-FIFO_GENERIC apInputBuffer;
-FIFO_GENERIC apOutputBuffer;
+FIFO apInputBuffer;
+FIFO apOutputBuffer;
#if defined(PLATFORM_ESP8266) || defined(PLATFORM_ESP32)
unsigned long rebootTime = 0;
@@ -57,20 +56,22 @@ uint32_t SyncPacketLastSent = 0;
volatile uint32_t LastTLMpacketRecvMillis = 0;
uint32_t TLMpacketReported = 0;
+static bool commitInProgress = false;
LQCALC<25> LQCalc;
volatile bool busyTransmitting;
static volatile bool ModelUpdatePending;
-bool InBindingMode = false;
uint8_t MSPDataPackage[5];
static uint8_t BindingSendCount;
bool RxWiFiReadyToSend = false;
-static uint16_t ptrChannelData[3] = {CRSF_CHANNEL_VALUE_MID, CRSF_CHANNEL_VALUE_MID, CRSF_CHANNEL_VALUE_MID};
bool headTrackingEnabled = false;
+#if !defined(CRITICAL_FLASH)
+static uint16_t ptrChannelData[3] = {CRSF_CHANNEL_VALUE_MID, CRSF_CHANNEL_VALUE_MID, CRSF_CHANNEL_VALUE_MID};
static uint32_t lastPTRValidTimeMs;
+#endif
static TxTlmRcvPhase_e TelemetryRcvPhase = ttrpTransmitting;
StubbornReceiver TelemetryReceiver;
@@ -78,7 +79,7 @@ StubbornSender MspSender;
uint8_t CRSFinBuffer[CRSF_MAX_PACKET_LEN+1];
device_affinity_t ui_devices[] = {
- {&CRSF_device, 1},
+ {&Handset_device, 1},
#ifdef HAS_LED
{&LED_device, 0},
#endif
@@ -154,16 +155,16 @@ void ICACHE_RAM_ATTR LinkStatsFromOta(OTA_LinkStats_s * const ls)
// Antenna is the high bit in the RSSI_1 value
// RSSI received is signed, inverted polarity (positive value = -dBm)
// OpenTX's value is signed and will display +dBm and -dBm properly
- crsf.LinkStatistics.uplink_RSSI_1 = -(ls->uplink_RSSI_1);
- crsf.LinkStatistics.uplink_RSSI_2 = -(ls->uplink_RSSI_2);
- crsf.LinkStatistics.uplink_Link_quality = ls->lq;
+ CRSF::LinkStatistics.uplink_RSSI_1 = -(ls->uplink_RSSI_1);
+ CRSF::LinkStatistics.uplink_RSSI_2 = -(ls->uplink_RSSI_2);
+ CRSF::LinkStatistics.uplink_Link_quality = ls->lq;
#if defined(DEBUG_FREQ_CORRECTION)
// Don't descale the FreqCorrection value being send in SNR
- crsf.LinkStatistics.uplink_SNR = snrScaled;
+ CRSF::LinkStatistics.uplink_SNR = snrScaled;
#else
- crsf.LinkStatistics.uplink_SNR = SNR_DESCALE(snrScaled);
+ CRSF::LinkStatistics.uplink_SNR = SNR_DESCALE(snrScaled);
#endif
- crsf.LinkStatistics.active_antenna = ls->antenna;
+ CRSF::LinkStatistics.active_antenna = ls->antenna;
connectionHasModelMatch = ls->modelMatch;
// -- downlink_SNR / downlink_RSSI is updated for any packet received, not just Linkstats
// -- uplink_TX_Power is updated when sending to the handset, so it updates when missing telemetry
@@ -197,8 +198,8 @@ bool ICACHE_RAM_ATTR ProcessTLMpacket(SX12xxDriverCommon::rx_status const status
LQCalc.add();
Radio.GetLastPacketStats();
- crsf.LinkStatistics.downlink_SNR = SNR_DESCALE(Radio.LastPacketSNRRaw);
- crsf.LinkStatistics.downlink_RSSI = Radio.LastPacketRSSI;
+ CRSF::LinkStatistics.downlink_SNR = SNR_DESCALE(Radio.LastPacketSNRRaw);
+ CRSF::LinkStatistics.downlink_RSSI = Radio.LastPacketRSSI;
// Full res mode
if (OtaIsFullRes)
@@ -246,6 +247,7 @@ bool ICACHE_RAM_ATTR ProcessTLMpacket(SX12xxDriverCommon::rx_status const status
break;
}
}
+
return true;
}
@@ -264,7 +266,7 @@ expresslrs_tlm_ratio_e ICACHE_RAM_ATTR UpdateTlmRatioEffective()
// If Armed, telemetry is disabled, otherwise use STD
else if (ratioConfigured == TLM_RATIO_DISARMED)
{
- if (crsf.IsArmed())
+ if (handset->IsArmed())
{
retVal = TLM_RATIO_NO_TLM;
// Avoid updating ExpressLRS_currTlmDenom until connectionState == disconnected
@@ -312,22 +314,26 @@ void ICACHE_RAM_ATTR GenerateSyncPacketData(OTA_Sync_s * const syncPtr)
// For model match, the last byte of the binding ID is XORed with the inverse of the modelId
if (!InBindingMode && config.GetModelMatch())
{
- syncPtr->UID5 ^= (~crsf.getModelID()) & MODELMATCH_MASK;
+ syncPtr->UID5 ^= (~CRSFHandset::getModelID()) & MODELMATCH_MASK;
}
}
uint8_t adjustPacketRateForBaud(uint8_t rateIndex)
{
#if defined(RADIO_SX128X)
- if (crsf.GetCurrentBaudRate() == 115200 && GPIO_PIN_RCSIGNAL_RX == GPIO_PIN_RCSIGNAL_TX) // Packet rate limited to 150Hz if we are on 115k baud on external module
+ if (CRSFHandset::GetCurrentBaudRate() == 115200 && GPIO_PIN_RCSIGNAL_RX == GPIO_PIN_RCSIGNAL_TX) // Packet rate limited to 150Hz if we are on 115k baud on external module
{
rateIndex = get_elrs_HandsetRate_max(rateIndex, 6666);
}
- else if (crsf.GetCurrentBaudRate() == 115200) // Packet rate limited to 250Hz if we are on 115k baud (on internal module)
+ else if (CRSFHandset::GetCurrentBaudRate() == 115200) // Packet rate limited to 250Hz if we are on 115k baud (on internal module)
{
rateIndex = get_elrs_HandsetRate_max(rateIndex, 4000);
}
- else if (crsf.GetCurrentBaudRate() == 400000) // Packet rate limited to 500Hz if we are on 400k baud
+ else if (CRSFHandset::GetCurrentBaudRate() == 400000 && GPIO_PIN_RCSIGNAL_RX == GPIO_PIN_RCSIGNAL_TX) // Packet rate limited to 333Hz if we are on 400k baud on external module
+ {
+ rateIndex = get_elrs_HandsetRate_max(rateIndex, 3003);
+ }
+ else if (CRSFHandset::GetCurrentBaudRate() == 400000) // Packet rate limited to 500Hz if we are on 400k baud
{
rateIndex = get_elrs_HandsetRate_max(rateIndex, 2000);
}
@@ -335,12 +341,12 @@ uint8_t adjustPacketRateForBaud(uint8_t rateIndex)
return rateIndex;
}
-void ICACHE_RAM_ATTR SetRFLinkRate(uint8_t index) // Set speed of RF link (hz)
+void SetRFLinkRate(uint8_t index) // Set speed of RF link
{
- index = adjustPacketRateForBaud(index);
expresslrs_mod_settings_s *const ModParams = get_elrs_airRateConfig(index);
expresslrs_rf_pref_params_s *const RFperf = get_elrs_RFperfParams(index);
- bool invertIQ = UID[5] & 0x01;
+ // Binding always uses invertIQ
+ bool invertIQ = InBindingMode || (UID[5] & 0x01);
OtaSwitchMode_e newSwitchMode = (OtaSwitchMode_e)config.GetSwitchMode();
if ((ModParams == ExpressLRS_currAirRate_Modparams)
@@ -354,16 +360,29 @@ void ICACHE_RAM_ATTR SetRFLinkRate(uint8_t index) // Set speed of RF link (hz)
#if defined(DEBUG_FREQ_CORRECTION) && defined(RADIO_SX128X)
interval = interval * 12 / 10; // increase the packet interval by 20% to allow adding packet header
#endif
- hwTimer.updateInterval(interval);
- Radio.Config(ModParams->bw, ModParams->sf, ModParams->cr, GetInitialFreq(),
+ hwTimer::updateInterval(interval);
+
+ FHSSusePrimaryFreqBand = !(ModParams->radio_type == RADIO_TYPE_LR1121_LORA_2G4);
+ FHSSuseDualBand = ModParams->radio_type == RADIO_TYPE_LR1121_LORA_DUAL;
+
+ Radio.Config(ModParams->bw, ModParams->sf, ModParams->cr, FHSSgetInitialFreq(),
ModParams->PreambleLen, invertIQ, ModParams->PayloadLength, ModParams->interval
#if defined(RADIO_SX128X)
, uidMacSeedGet(), OtaCrcInitializer, (ModParams->radio_type == RADIO_TYPE_SX128x_FLRC)
#endif
);
+
+#if defined(RADIO_LR1121)
+ if (FHSSuseDualBand)
+ {
+ Radio.Config(ModParams->bw2, ModParams->sf2, ModParams->cr2, FHSSgetInitialGeminiFreq(),
+ ModParams->PreambleLen2, invertIQ, ModParams->PayloadLength, ModParams->interval, SX12XX_Radio_2);
+ }
+#endif
+
Radio.FuzzySNRThreshold = (RFperf->DynpowerSnrThreshUp == DYNPOWER_SNR_THRESH_NONE) ? 0 : (RFperf->DynpowerSnrThreshUp - RFperf->DynpowerSnrThreshDn);
- if (isDualRadio() && config.GetAntennaMode() == TX_RADIO_MODE_GEMINI) // Gemini mode
+ if ((isDualRadio() && config.GetAntennaMode() == TX_RADIO_MODE_GEMINI) || FHSSuseDualBand) // Gemini mode
{
Radio.SetFrequencyReg(FHSSgetInitialGeminiFreq(), SX12XX_Radio_2);
}
@@ -374,9 +393,9 @@ void ICACHE_RAM_ATTR SetRFLinkRate(uint8_t index) // Set speed of RF link (hz)
ExpressLRS_currAirRate_Modparams = ModParams;
ExpressLRS_currAirRate_RFperfParams = RFperf;
- crsf.LinkStatistics.rf_Mode = ModParams->enum_rate;
+ CRSF::LinkStatistics.rf_Mode = ModParams->enum_rate;
- crsf.setSyncParams(interval * ExpressLRS_currAirRate_Modparams->numOfSends);
+ handset->setPacketInterval(interval * ExpressLRS_currAirRate_Modparams->numOfSends);
connectionState = disconnected;
rfModeLastChangedMS = millis();
}
@@ -387,10 +406,22 @@ void ICACHE_RAM_ATTR HandleFHSS()
// If the next packet should be on the next FHSS frequency, do the hop
if (!InBindingMode && modresult == 0)
{
- if (isDualRadio() && config.GetAntennaMode() == TX_RADIO_MODE_GEMINI) // Gemini mode
+ // Gemini mode
+ // If using DualBand always set the correct frequency band to the radios. The HighFreq/LowFreq Tx amp is set during config.
+ if ((isDualRadio() && config.GetAntennaMode() == TX_RADIO_MODE_GEMINI) || FHSSuseDualBand)
{
- Radio.SetFrequencyReg(FHSSgetNextFreq(), SX12XX_Radio_1);
- Radio.SetFrequencyReg(FHSSgetGeminiFreq(), SX12XX_Radio_2);
+ // Optimises the SPI traffic order.
+ if (Radio.GetProcessingPacketRadio() == SX12XX_Radio_1)
+ {
+ uint32_t freqRadio = FHSSgetNextFreq();
+ Radio.SetFrequencyReg(FHSSgetGeminiFreq(), SX12XX_Radio_2);
+ Radio.SetFrequencyReg(freqRadio, SX12XX_Radio_1);
+ }
+ else
+ {
+ Radio.SetFrequencyReg(FHSSgetNextFreq(), SX12XX_Radio_1);
+ Radio.SetFrequencyReg(FHSSgetGeminiFreq(), SX12XX_Radio_2);
+ }
}
else
{
@@ -411,6 +442,7 @@ void ICACHE_RAM_ATTR HandlePrepareForTLM()
void injectBackpackPanTiltRollData(uint32_t const now)
{
+#if !defined(CRITICAL_FLASH)
// Do not override channels if the backpack is NOT communicating or PanTiltRoll is disabled
if (config.GetPTREnableChannel() == HT_OFF || backpackVersion[0] == 0)
{
@@ -431,11 +463,13 @@ void injectBackpackPanTiltRollData(uint32_t const now)
enable |= !chan;
}
}
+
if (enable != headTrackingEnabled)
{
headTrackingEnabled = enable;
HTEnableFlagReadyToSend = true;
}
+
// If enabled and this packet is less that 1 second old then use it
if (enable && now - lastPTRValidTimeMs < 1000)
{
@@ -443,12 +477,8 @@ void injectBackpackPanTiltRollData(uint32_t const now)
ChannelData[ptrStartChannel + 5] = ptrChannelData[1];
ChannelData[ptrStartChannel + 6] = ptrChannelData[2];
}
- else
- {
- ChannelData[ptrStartChannel + 4] = CRSF_CHANNEL_VALUE_MID;
- ChannelData[ptrStartChannel + 5] = CRSF_CHANNEL_VALUE_MID;
- ChannelData[ptrStartChannel + 6] = CRSF_CHANNEL_VALUE_MID;
- }
+ // else if not enabled or PTR is old, do not override ChannelData from handset
+#endif
}
void ICACHE_RAM_ATTR SendRCdataToRF()
@@ -462,7 +492,7 @@ void ICACHE_RAM_ATTR SendRCdataToRF()
uint32_t SyncInterval = (connectionState == connected && !isTlmDisarmed) ? ExpressLRS_currAirRate_RFperfParams->SyncPktIntervalConnected : ExpressLRS_currAirRate_RFperfParams->SyncPktIntervalDisconnected;
bool skipSync = InBindingMode ||
// TLM_RATIO_DISARMED keeps sending sync packets even when armed until the RX stops sending telemetry and the TLM=Off has taken effect
- (isTlmDisarmed && crsf.IsArmed() && (ExpressLRS_currTlmDenom == 1));
+ (isTlmDisarmed && handset->IsArmed() && (ExpressLRS_currTlmDenom == 1));
uint8_t NonceFHSSresult = OtaNonce % ExpressLRS_currAirRate_Modparams->FHSShopInterval;
bool WithinSyncSpamResidualWindow = now - rfModeLastChangedMS < syncSpamAResidualTimeMS;
@@ -476,7 +506,7 @@ void ICACHE_RAM_ATTR SendRCdataToRF()
}
// Regular sync rotates through 4x slots, twice on each slot, and telemetry pushes it to the next slot up
// But only on the sync FHSS channel and with a timed delay between them
- else if ((!skipSync) && ((syncSlot / 2) <= NonceFHSSresult) && (now - SyncPacketLastSent > SyncInterval) && (Radio.currFreq == GetInitialFreq()))
+ else if ((!skipSync) && ((syncSlot / 2) <= NonceFHSSresult) && (now - SyncPacketLastSent > SyncInterval) && FHSSonSyncChannel())
{
otaPkt.std.type = PACKET_TYPE_SYNC;
GenerateSyncPacketData(OtaIsFullRes ? &otaPkt.full.sync.sync : &otaPkt.std.sync);
@@ -555,26 +585,49 @@ void ICACHE_RAM_ATTR SendRCdataToRF()
#if defined(Regulatory_Domain_EU_CE_2400)
transmittingRadio &= ChannelIsClear(transmittingRadio); // weed out the radio(s) if channel in use
+
+ if (transmittingRadio == SX12XX_Radio_NONE)
+ {
+ // No packet will be sent due to LBT.
+ // Defer TXdoneCallback() to prepare for TLM when the IRQ is normally triggered.
+ deferExecutionMicros(ExpressLRS_currAirRate_RFperfParams->TOA, []() {
+ Radio.TXdoneCallback();
+ });
+ }
+ else
#endif
+ {
+ Radio.TXnb((uint8_t*)&otaPkt, ExpressLRS_currAirRate_Modparams->PayloadLength, transmittingRadio);
+ }
+}
- Radio.TXnb((uint8_t*)&otaPkt, ExpressLRS_currAirRate_Modparams->PayloadLength, transmittingRadio);
+void ICACHE_RAM_ATTR nonceAdvance()
+{
+ OtaNonce++;
+ if ((OtaNonce + 1) % ExpressLRS_currAirRate_Modparams->FHSShopInterval == 0)
+ {
+ ++FHSSptr;
+ }
}
/*
* Called as the TOCK timer ISR when there is a CRSF connection from the handset
*/
-void ICACHE_RAM_ATTR timerCallbackNormal()
+void ICACHE_RAM_ATTR timerCallback()
{
- // No packet has been sent due to LBT. Call TXdoneCallback to prepare for TLM.
- if (Radio.GetLastTransmitRadio() == SX12XX_Radio_NONE)
+ /* If we are busy writing to EEPROM (committing config changes) then we just advance the nonces, i.e. no SPI traffic */
+ if (commitInProgress)
{
- Radio.TXdoneCallback();
+ nonceAdvance();
+ return;
}
+ Radio.isFirstRxIrq = true;
+
// Sync OpenTX to this point
if (!(OtaNonce % ExpressLRS_currAirRate_Modparams->numOfSends))
{
- crsf.JustSentRFpacket();
+ handset->JustSentRFpacket();
}
// Do not transmit or advance FHSS/Nonce until in disconnected/connected state
@@ -600,9 +653,9 @@ void ICACHE_RAM_ATTR timerCallbackNormal()
TelemetryRcvPhase = ttrpExpectingTelem;
#if defined(Regulatory_Domain_EU_CE_2400)
// Use downlink LQ for LBT success ratio instead for EU/CE reg domain
- crsf.LinkStatistics.downlink_Link_quality = LBTSuccessCalc.getLQ();
+ CRSF::LinkStatistics.downlink_Link_quality = LBTSuccessCalc.getLQ();
#else
- crsf.LinkStatistics.downlink_Link_quality = LQCalc.getLQ();
+ CRSF::LinkStatistics.downlink_Link_quality = LQCalc.getLQ();
#endif
LQCalc.inc();
return;
@@ -612,16 +665,13 @@ void ICACHE_RAM_ATTR timerCallbackNormal()
// Indicate no telemetry packet received to the DP system
DynamicPower_TelemetryUpdate(DYNPOWER_UPDATE_MISSED);
}
- TelemetryRcvPhase = ttrpTransmitting;
-#if defined(Regulatory_Domain_EU_CE_2400)
- BeginClearChannelAssessment(); // Get RSSI reading here, used also for next TX if in receiveMode.
-#endif
+ TelemetryRcvPhase = ttrpTransmitting;
// Do not send a stale channels packet to the RX if one has not been received from the handset
// *Do* send data if a packet has never been received from handset and the timer is running
// this is the case when bench testing and TXing without a handset
- uint32_t lastRcData = crsf.GetRCdataLastRecv();
+ uint32_t lastRcData = handset->GetRCdataLastRecv();
if (!lastRcData || (micros() - lastRcData < 1000000))
{
busyTransmitting = true;
@@ -629,19 +679,9 @@ void ICACHE_RAM_ATTR timerCallbackNormal()
}
}
-/*
- * Called as the timer ISR while waiting for eeprom flush
- */
-void ICACHE_RAM_ATTR timerCallbackIdle()
-{
- OtaNonce++;
- if ((OtaNonce + 1) % ExpressLRS_currAirRate_Modparams->FHSShopInterval == 0)
- ++FHSSptr;
-}
-
static void UARTdisconnected()
{
- hwTimer.stop();
+ hwTimer::stop();
connectionState = noCrossfire;
}
@@ -651,7 +691,9 @@ static void UARTconnected()
webserverPreventAutoStart = true;
#endif
rfModeLastChangedMS = millis(); // force syncspam on first packets
- SetRFLinkRate(config.GetRate());
+
+ auto index = adjustPacketRateForBaud(config.GetRate());
+ config.SetRate(index);
if (connectionState == noCrossfire || connectionState < MODE_STATES)
{
// When CRSF first connects, always go into a brief delay before
@@ -660,7 +702,7 @@ static void UARTconnected()
connectionState = awaitingModelId;
}
// But start the timer to get OpenTX sync going and a ModelID update sent
- hwTimer.resume();
+ hwTimer::resume();
}
void ResetPower()
@@ -669,20 +711,20 @@ void ResetPower()
// (user may be turning up the power while flying and dropping the power may compromise the link)
if (config.GetDynamicPower())
{
- if (!crsf.IsArmed())
+ if (!handset->IsArmed())
{
// if dynamic power enabled and not armed then set to MinPower
- POWERMGNT.setPower(MinPower);
+ POWERMGNT::setPower(MinPower);
}
- else if (POWERMGNT.currPower() < config.GetPower())
+ else if (POWERMGNT::currPower() < config.GetPower())
{
// if the new config is a higher power then set it, otherwise leave it alone
- POWERMGNT.setPower((PowerLevels_e)config.GetPower());
+ POWERMGNT::setPower((PowerLevels_e)config.GetPower());
}
}
else
{
- POWERMGNT.setPower((PowerLevels_e)config.GetPower());
+ POWERMGNT::setPower((PowerLevels_e)config.GetPower());
}
// TLM interval is set on the next SYNC packet
#if defined(Regulatory_Domain_EU_CE_2400)
@@ -700,7 +742,7 @@ static void ChangeRadioParams()
void ModelUpdateReq()
{
// Force synspam with the current rate parameters in case already have a connection established
- if (config.SetModelId(crsf.getModelID()))
+ if (config.SetModelId(CRSFHandset::getModelID()))
{
syncSpamCounter = syncSpamAmount;
ModelUpdatePending = true;
@@ -718,12 +760,16 @@ void ModelUpdateReq()
static void ConfigChangeCommit()
{
+ // Adjust the air rate based on teh current baud rate
+ auto index = adjustPacketRateForBaud(config.GetRate());
+ config.SetRate(index);
+
// Write the uncommitted eeprom values (may block for a while)
config.Commit();
// Change params after the blocking finishes as a rate change will change the radio freq
ChangeRadioParams();
- // Resume the timer, will take one hop for the radio to be on the right frequency if we missed a hop
- hwTimer.callbackTock = &timerCallbackNormal;
+ // Clear the commitInProgress flag so normal processing resumes
+ commitInProgress = false;
// UpdateFolderNames is expensive so it is called directly instead of in event() which gets called a lot
luadevUpdateFolderNames();
devicesTriggerEvent();
@@ -749,16 +795,16 @@ static void CheckConfigChangePending()
// adding one cycle that will be eaten by busywaiting for the transmit to end
uint32_t pauseCycles = ((EEPROM_WRITE_DURATION + cycleInterval - 1) / cycleInterval) + 1;
// Pause won't return until paused, and has just passed the tick ISR (but not fired)
- hwTimer.pause(pauseCycles * cycleInterval);
+ hwTimer::pause(pauseCycles * cycleInterval);
while (busyTransmitting); // wait until no longer transmitting
--pauseCycles; // the last cycle will actually be a transmit
while (pauseCycles--)
- timerCallbackIdle();
+ nonceAdvance();
#endif
- // Prevent any other RF SPI traffic during the commit from RX or scheduled TX
- hwTimer.callbackTock = &timerCallbackIdle;
+ // Set the commitInProgress flag to prevent any other RF SPI traffic during the commit from RX or scheduled TX
+ commitInProgress = true;
// If telemetry expected in the next interval, the radio was in RX mode
// and will skip sending the next packet when the timer resumes.
// Return to normal send mode because if the skipped packet happened
@@ -780,6 +826,12 @@ bool ICACHE_RAM_ATTR RXdoneISR(SX12xxDriverCommon::rx_status const status)
}
bool packetSuccessful = ProcessTLMpacket(status);
+#if defined(Regulatory_Domain_EU_CE_2400)
+ if (packetSuccessful)
+ {
+ SetClearChannelAssessmentTime();
+ }
+#endif
busyTransmitting = false;
return packetSuccessful;
}
@@ -802,7 +854,7 @@ void ICACHE_RAM_ATTR TXdoneISR()
// from RX enable to valid instant RSSI values are returned.
// If rx was already started by TLM prepare above, this call will let RX
// continue as normal.
- BeginClearChannelAssessment();
+ SetClearChannelAssessmentTime();
}
#endif // non-CE
}
@@ -825,7 +877,7 @@ static void UpdateConnectDisconnectStatus()
if (connectionState != connected)
{
connectionState = connected;
- crsf.ForwardDevicePings = true;
+ CRSFHandset::ForwardDevicePings = true;
DBGLN("got downlink conn");
if (firmwareOptions.is_airport)
@@ -841,7 +893,7 @@ static void UpdateConnectDisconnectStatus()
{
connectionState = disconnected;
connectionHasModelMatch = true;
- crsf.ForwardDevicePings = false;
+ CRSFHandset::ForwardDevicePings = false;
}
}
@@ -860,18 +912,12 @@ static void SendRxWiFiOverMSP()
MspSender.SetDataToTransmit(MSPDataPackage, 1);
}
-void SendRxLoanOverMSP()
-{
- MSPDataPackage[0] = MSP_ELRS_SET_RX_LOAN_MODE;
- MspSender.SetDataToTransmit(MSPDataPackage, 1);
-}
-
static void CheckReadyToSend()
{
if (RxWiFiReadyToSend)
{
RxWiFiReadyToSend = false;
- if (!crsf.IsArmed())
+ if (!handset->IsArmed())
{
SendRxWiFiOverMSP();
}
@@ -884,7 +930,7 @@ void OnPowerGetCalibration(mspPacket_t *packet)
uint8_t index = packet->readByte();
UNUSED(index);
int8_t values[PWR_COUNT] = {0};
- POWERMGNT.GetPowerCaliValues(values, PWR_COUNT);
+ POWERMGNT::GetPowerCaliValues(values, PWR_COUNT);
DBGLN("power get calibration value %d", values[index]);
}
@@ -898,83 +944,89 @@ void OnPowerSetCalibration(mspPacket_t *packet)
DBGLN("calibration error index %d out of range", index);
return;
}
- hwTimer.stop();
+ hwTimer::stop();
delay(20);
int8_t values[PWR_COUNT] = {0};
- POWERMGNT.GetPowerCaliValues(values, PWR_COUNT);
+ POWERMGNT::GetPowerCaliValues(values, PWR_COUNT);
values[index] = value;
- POWERMGNT.SetPowerCaliValues(values, PWR_COUNT);
+ POWERMGNT::SetPowerCaliValues(values, PWR_COUNT);
DBGLN("power calibration done %d, %d", index, value);
- hwTimer.resume();
+ hwTimer::resume();
}
#endif
void SendUIDOverMSP()
{
MSPDataPackage[0] = MSP_ELRS_BIND;
- memcpy(&MSPDataPackage[1], &MasterUID[2], 4);
+ memcpy(&MSPDataPackage[1], &UID[2], 4);
BindingSendCount = 0;
MspSender.ResetState();
MspSender.SetDataToTransmit(MSPDataPackage, 5);
}
-void EnterBindingMode()
+static void EnterBindingMode()
{
- if (InBindingMode) {
- // Don't enter binding if we're already binding
+ if (InBindingMode)
return;
- }
// Disable the TX timer and wait for any TX to complete
- hwTimer.stop();
+ hwTimer::stop();
while (busyTransmitting);
// Queue up sending the Master UID as MSP packets
SendUIDOverMSP();
- // Set UID to special binding values
- memcpy(UID, BindingUID, UID_LEN);
-
+ // Binding uses a CRCInit=0, 50Hz, and InvertIQ
OtaCrcInitializer = 0;
OtaNonce = 0; // Lock the OtaNonce to prevent syncspam packets
- InBindingMode = true;
+ InBindingMode = true; // Set binding mode before SetRFLinkRate() for correct IQ
// Start attempting to bind
// Lock the RF rate and freq while binding
SetRFLinkRate(enumRatetoIndex(RATE_BINDING));
- Radio.SetFrequencyReg(GetInitialFreq());
+
+#if defined(RADIO_LR1121)
+ FHSSuseDualBand = true;
+ expresslrs_mod_settings_s *const dualBandBindingModParams = get_elrs_airRateConfig(RATE_DUALBAND_BINDING); // 2.4GHz 50Hz
+ Radio.Config(dualBandBindingModParams->bw2, dualBandBindingModParams->sf2, dualBandBindingModParams->cr2, FHSSgetInitialGeminiFreq(),
+ dualBandBindingModParams->PreambleLen2, true, dualBandBindingModParams->PayloadLength, dualBandBindingModParams->interval, SX12XX_Radio_2);
+#endif
+
+ Radio.SetFrequencyReg(FHSSgetInitialFreq());
if (isDualRadio() && config.GetAntennaMode() == TX_RADIO_MODE_GEMINI) // Gemini mode
{
Radio.SetFrequencyReg(FHSSgetInitialGeminiFreq(), SX12XX_Radio_2);
}
// Start transmitting again
- hwTimer.resume();
+ hwTimer::resume();
DBGLN("Entered binding mode at freq = %d", Radio.currFreq);
}
-void ExitBindingMode()
+static void ExitBindingMode()
{
if (!InBindingMode)
- {
- // Not in binding mode
return;
- }
MspSender.ResetState();
- // Reset UID to defined values
- memcpy(UID, MasterUID, UID_LEN);
+ // Reset CRCInit to UID-defined value
OtaUpdateCrcInitFromUid();
-
- InBindingMode = false;
+ InBindingMode = false; // Clear binding mode before SetRFLinkRate() for correct IQ
SetRFLinkRate(config.GetRate()); //return to original rate
DBGLN("Exiting binding mode");
}
+void EnterBindingModeSafely()
+{
+ // TX can always enter binding mode safely as the function handles stopping the transmitter
+ EnterBindingMode();
+}
+
+
void ProcessMSPPacket(uint32_t now, mspPacket_t *packet)
{
#if !defined(CRITICAL_FLASH)
@@ -1010,12 +1062,6 @@ void ProcessMSPPacket(uint32_t now, mspPacket_t *packet)
VtxTriggerSend();
}
-#endif
- if (packet->function == MSP_ELRS_GET_BACKPACK_VERSION)
- {
- memset(backpackVersion, 0, sizeof(backpackVersion));
- memcpy(backpackVersion, packet->payload, min((size_t)packet->payloadSize, sizeof(backpackVersion)-1));
- }
else if (packet->function == MSP_ELRS_BACKPACK_SET_PTR && packet->payloadSize == 6)
{
ptrChannelData[0] = packet->payload[0] + (packet->payload[1] << 8);
@@ -1023,15 +1069,26 @@ void ProcessMSPPacket(uint32_t now, mspPacket_t *packet)
ptrChannelData[2] = packet->payload[4] + (packet->payload[5] << 8);
lastPTRValidTimeMs = now;
}
+#endif
+ if (packet->function == MSP_ELRS_GET_BACKPACK_VERSION)
+ {
+ memset(backpackVersion, 0, sizeof(backpackVersion));
+ memcpy(backpackVersion, packet->payload, min((size_t)packet->payloadSize, sizeof(backpackVersion)-1));
+ }
}
static void HandleUARTout()
{
if (firmwareOptions.is_airport)
{
- while (apOutputBuffer.size())
+ auto size = apOutputBuffer.size();
+ if (size)
{
- TxUSB->write(apOutputBuffer.pop());
+ uint8_t buf[size];
+ apOutputBuffer.lock();
+ apOutputBuffer.popBytes(buf, size);
+ apOutputBuffer.unlock();
+ TxUSB->write(buf, size);
}
}
}
@@ -1089,8 +1146,12 @@ static void setupSerial()
serialPort = new NullStream();
}
#elif defined(TARGET_TX_FM30)
- USBSerial *serialPort = &SerialUSB; // No way to disable creating SerialUSB global, so use it
- serialPort->begin();
+ #if defined(PIO_FRAMEWORK_ARDUINO_ENABLE_CDC)
+ USBSerial *serialPort = &SerialUSB; // No way to disable creating SerialUSB global, so use it
+ serialPort->begin();
+ #else
+ Stream *serialPort = new NullStream();
+ #endif
#elif (defined(GPIO_PIN_DEBUG_RX) && GPIO_PIN_DEBUG_RX != UNDEF_PIN) || (defined(GPIO_PIN_DEBUG_TX) && GPIO_PIN_DEBUG_TX != UNDEF_PIN)
HardwareSerial *serialPort = new HardwareSerial(2);
#if defined(GPIO_PIN_DEBUG_RX) && GPIO_PIN_DEBUG_RX != UNDEF_PIN
@@ -1105,6 +1166,10 @@ static void setupSerial()
#endif
TxBackpack = serialPort;
+#if defined(PLATFORM_ESP32_S3)
+ Serial.begin(460800);
+#endif
+
// Setup TxUSB
#if defined(PLATFORM_ESP32)
if (rxPin != UNDEF_PIN && txPin != UNDEF_PIN)
@@ -1118,6 +1183,9 @@ static void setupSerial()
}
#else
TxUSB = new NullStream();
+ UNUSED(portConflict);
+ UNUSED(rxPin);
+ UNUSED(txPin);
#endif
}
@@ -1138,7 +1206,7 @@ static void setupTarget()
digitalWrite(GPIO_PIN_ANT_CTRL_FIXED, LOW); // LEFT antenna
HardwareSerial *uart2 = new HardwareSerial(USART2);
uart2->begin(57600);
- CRSF::PortSecondary = uart2;
+ CRSFHandset::PortSecondary = uart2;
#endif
#if defined(TARGET_TX_FM30_MINI)
@@ -1157,17 +1225,13 @@ static void setupTarget()
digitalWrite(GPIO_PIN_ANT_CTRL_COMPL, !diversityAntennaState);
}
- setupTargetCommon();
setupSerial();
+ setupTargetCommon();
}
bool setupHardwareFromOptions()
{
#if defined(TARGET_UNIFIED_TX)
- // Setup default logging in case of failure, or no layout
- Serial.begin(115200);
- TxBackpack = &Serial;
-
if (!options_init())
{
// Register the WiFi with the framework
@@ -1187,6 +1251,33 @@ bool setupHardwareFromOptions()
return true;
}
+static void setupBindingFromConfig()
+{
+ if (firmwareOptions.hasUID)
+ {
+ memcpy(UID, firmwareOptions.uid, UID_LEN);
+ }
+ else
+ {
+#ifdef PLATFORM_ESP32
+ esp_read_mac(UID, ESP_MAC_WIFI_STA);
+#elif PLATFORM_STM32
+ UID[0] = (uint8_t)HAL_GetUIDw0();
+ UID[1] = (uint8_t)(HAL_GetUIDw0() >> 8);
+ UID[2] = (uint8_t)HAL_GetUIDw1();
+ UID[3] = (uint8_t)(HAL_GetUIDw1() >> 8);
+ UID[4] = (uint8_t)HAL_GetUIDw2();
+ UID[5] = (uint8_t)(HAL_GetUIDw2() >> 8);
+#endif
+ }
+
+ DBGLN("UID=(%d, %d, %d, %d, %d, %d)",
+ UID[0], UID[1], UID[2], UID[3], UID[4], UID[5]);
+
+ OtaUpdateCrcInitFromUid();
+}
+
+
static void cyclePower()
{
// Only change power if we are running normally
@@ -1208,7 +1299,6 @@ void setup()
{
if (setupHardwareFromOptions())
{
- initUID();
setupTarget();
// Register the devices with the framework
devicesRegister(ui_devices, ARRAY_SIZE(ui_devices));
@@ -1216,25 +1306,21 @@ void setup()
devicesInit();
DBGLN("Initialised devices");
+ setupBindingFromConfig();
FHSSrandomiseFHSSsequence(uidMacSeedGet());
Radio.RXdoneCallback = &RXdoneISR;
Radio.TXdoneCallback = &TXdoneISR;
- crsf.connected = &UARTconnected; // it will auto init when it detects UART connection
- if (!firmwareOptions.is_airport)
- {
- crsf.disconnected = &UARTdisconnected;
- }
- crsf.RecvModelUpdate = &ModelUpdateReq;
- hwTimer.callbackTock = &timerCallbackNormal;
+ handset->registerCallbacks(UARTconnected, firmwareOptions.is_airport ? nullptr : UARTdisconnected, ModelUpdateReq, EnterBindingModeSafely);
+
DBGLN("ExpressLRS TX Module Booted...");
eeprom.Begin(); // Init the eeprom
config.SetStorageProvider(&eeprom); // Pass pointer to the Config class for access to storage
config.Load(); // Load the stored values from eeprom
- Radio.currFreq = GetInitialFreq(); //set frequency first or an error will occur!!!
+ Radio.currFreq = FHSSgetInitialFreq(); //set frequency first or an error will occur!!!
#if defined(RADIO_SX127X)
//Radio.currSyncWord = UID[3];
#endif
@@ -1244,7 +1330,7 @@ void setup()
#else
if (GPIO_PIN_SCK != UNDEF_PIN)
{
- init_success = Radio.Begin();
+ init_success = Radio.Begin(FHSSgetMinimumFreq(), FHSSgetMaximumFreq());
}
else
{
@@ -1261,19 +1347,25 @@ void setup()
{
TelemetryReceiver.SetDataToReceive(CRSFinBuffer, sizeof(CRSFinBuffer));
- POWERMGNT.init();
+ POWERMGNT::init();
DynamicPower_Init();
// Set the pkt rate, TLM ratio, and power from the stored eeprom values
ChangeRadioParams();
#if defined(Regulatory_Domain_EU_CE_2400)
- BeginClearChannelAssessment();
+ SetClearChannelAssessmentTime();
#endif
- hwTimer.init();
+ hwTimer::init(nullptr, timerCallback);
connectionState = noCrossfire;
}
}
+ else
+ {
+ // In the failure case we set the logging to the null logger so nothing crashes
+ // if it decides to log something
+ TxBackpack = new NullStream();
+ }
#if defined(HAS_BUTTON)
registerButtonFunction(ACTION_BIND, EnterBindingMode);
@@ -1294,6 +1386,8 @@ void loop()
{
uint32_t now = millis();
+ Radio.ignoreSecondIRQ = false;
+
HandleUARTout(); // Only used for non-CRSF output
#if defined(USE_BLE_JOYSTICK)
@@ -1321,7 +1415,7 @@ void loop()
}
#endif
- executeDeferredFunction(now);
+ executeDeferredFunction(micros());
if (firmwareOptions.is_airport && connectionState == connected)
{
@@ -1330,7 +1424,9 @@ void loop()
{
uint8_t buf[size];
TxUSB->readBytes(buf, size);
+ apInputBuffer.lock();
apInputBuffer.pushBytes(buf, size);
+ apInputBuffer.unlock();
}
}
@@ -1358,14 +1454,19 @@ void loop()
* is elapsed. This keeps handset happy dispite of the telemetry ratio */
if ((connectionState == connected) && (LastTLMpacketRecvMillis != 0) &&
(now >= (uint32_t)(firmwareOptions.tlm_report_interval + TLMpacketReported))) {
- crsf.sendLinkStatisticsToTX();
+ // 3 byte header + 1 byte CRC
+ uint8_t linkStatisticsFrame[LinkStatisticsFrameLength + 4];
+ CRSFHandset::makeLinkStatisticsPacket(linkStatisticsFrame);
+ handset->sendTelemetryToTX(linkStatisticsFrame);
+ crsfTelemToMSPOut(linkStatisticsFrame);
TLMpacketReported = now;
}
if (TelemetryReceiver.HasFinishedData())
{
- crsf.sendTelemetryToTX(CRSFinBuffer);
- TelemetryReceiver.Unlock();
+ handset->sendTelemetryToTX(CRSFinBuffer);
+ crsfTelemToMSPOut(CRSFinBuffer);
+ TelemetryReceiver.Unlock();
}
// only send msp data when binding is not active
@@ -1383,7 +1484,7 @@ void loop()
if (mspTransferActive)
{
// unlock buffer for msp messages
- crsf.UnlockMspMessage();
+ CRSF::UnlockMspMessage();
mspTransferActive = false;
}
// we are not sending so look for next msp package
@@ -1391,7 +1492,7 @@ void loop()
{
uint8_t* mspData;
uint8_t mspLen;
- crsf.GetMspMessage(&mspData, &mspLen);
+ CRSF::GetMspMessage(&mspData, &mspLen);
// if we have a new msp package start sending
if (mspData != nullptr)
{
diff --git a/src/targets/HGLRC_2400.ini b/src/targets/HGLRC_2400.ini
deleted file mode 100644
index 59382d3434..0000000000
--- a/src/targets/HGLRC_2400.ini
+++ /dev/null
@@ -1,25 +0,0 @@
-
-# ********************************
-# Transmitter targets
-# ********************************
-
-[env:HGLRC_Hermes_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = hglrc.tx_2400.hermes
-
-[env:HGLRC_Hermes_2400_TX_via_WIFI]
-extends = env:HGLRC_Hermes_2400_TX_via_UART
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:HGLRC_Hermes_2400_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = hglrc.rx_2400.hermes
-
-[env:HGLRC_Hermes_2400_RX_via_BetaflightPassthrough]
-extends = env:HGLRC_Hermes_2400_RX_via_UART
-
-[env:HGLRC_Hermes_2400_RX_via_WIFI]
-extends = env:HGLRC_Hermes_2400_RX_via_UART
diff --git a/src/targets/HGLRC_900.ini b/src/targets/HGLRC_900.ini
deleted file mode 100644
index a361b843af..0000000000
--- a/src/targets/HGLRC_900.ini
+++ /dev/null
@@ -1,18 +0,0 @@
-
-# ********************************
-# Transmitter targets
-# ********************************
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:HGLRC_Hermes_900_RX_via_UART]
-extends = env:Unified_ESP8285_900_RX_via_UART
-board_config = hglrc.rx_900.hermes
-
-[env:HGLRC_Hermes_900_RX_via_BetaflightPassthrough]
-extends = env:HGLRC_Hermes_900_RX_via_UART
-
-[env:HGLRC_Hermes_900_RX_via_WIFI]
-extends = env:HGLRC_Hermes_900_RX_via_UART
diff --git a/src/targets/Jumper_2400.ini b/src/targets/Jumper_2400.ini
deleted file mode 100644
index 9a8ef37321..0000000000
--- a/src/targets/Jumper_2400.ini
+++ /dev/null
@@ -1,47 +0,0 @@
-
-# ********************************
-# Transmitter targets
-# ********************************
-
-[env:Jumper_AION_2400_T-Pro_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board = pico32
-board_config = jumper.tx_2400.t-pro
-
-[env:Jumper_AION_2400_T-Pro_TX_via_WIFI]
-extends = env:Jumper_AION_2400_T-Pro_TX_via_UART
-
-[env:Jumper_AION_2400_T-Pro_TX_via_ETX]
-extends = env:Jumper_AION_2400_T-Pro_TX_via_UART
-
-[env:Jumper_AION_2400_T-Lite_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board = pico32
-board_config = jumper.tx_2400.t-lite
-
-[env:Jumper_AION_2400_T-Lite_TX_via_WIFI]
-extends = env:Jumper_AION_2400_T-Lite_TX_via_UART
-
-[env:Jumper_AION_2400_T-Lite_TX_via_ETX]
-extends = env:Jumper_AION_2400_T-Lite_TX_via_UART
-
-[env:Jumper_AION_NANO_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = jumper.tx_2400.nano
-
-[env:Jumper_AION_NANO_2400_TX_via_WIFI]
-extends = env:Jumper_AION_NANO_2400_TX_via_UART
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:Jumper_AION_Nano_2400_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = jumper.rx_2400.nano
-
-[env:Jumper_AION_Nano_2400_RX_via_BetaflightPassthrough]
-extends = env:Jumper_AION_Nano_2400_RX_via_UART
-
-[env:Jumper_AION_Nano_2400_RX_via_WIFI]
-extends = env:Jumper_AION_Nano_2400_RX_via_UART
\ No newline at end of file
diff --git a/src/targets/MATEK_2400.ini b/src/targets/MATEK_2400.ini
deleted file mode 100644
index 3fe22d5817..0000000000
--- a/src/targets/MATEK_2400.ini
+++ /dev/null
@@ -1,34 +0,0 @@
-# ********************************
-# Receiver MATEK_2400_RX
-# ********************************
-
-# ELRS-R24-D
-[env:MATEK_2400_RX_R24D_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = matek.rx_2400.r24d
-
-[env:MATEK_2400_RX_R24D_via_BetaflightPassthrough]
-extends = env:MATEK_2400_RX_R24D_via_UART
-
-[env:MATEK_2400_RX_R24D_via_WIFI]
-extends = env:MATEK_2400_RX_R24D_via_UART
-
-
-# ELRS-R24-S
-[env:MATEK_2400_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = matek.rx_2400.r24s
-
-[env:MATEK_2400_RX_via_BetaflightPassthrough]
-extends = env:MATEK_2400_RX_via_UART
-
-[env:MATEK_2400_RX_via_WIFI]
-extends = env:MATEK_2400_RX_via_UART
-
-# ELRS-R24-P
-[env:MATEK_2400_RX_PWM_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = matek.rx_2400.r24p
-
-[env:MATEK_2400_RX_PWM_via_WIFI]
-extends = env:MATEK_2400_RX_PWM_via_UART
diff --git a/src/targets/axis_2400.ini b/src/targets/axis_2400.ini
deleted file mode 100644
index 0c03c95e86..0000000000
--- a/src/targets/axis_2400.ini
+++ /dev/null
@@ -1,24 +0,0 @@
-# ********************************
-# Transmitter targets
-# ********************************
-[env:AXIS_THOR_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = axis.tx_2400.thor
-monitor_speed = 420000
-
-[env:AXIS_THOR_2400_TX_via_WIFI]
-extends = env:AXIS_THOR_2400_TX_via_UART
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:AXIS_THOR_2400_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = axis.rx_2400.thor
-
-[env:AXIS_THOR_2400_RX_via_WIFI]
-extends = env:AXIS_THOR_2400_RX_via_UART
-
-[env:AXIS_THOR_2400_RX_via_BetaflightPassthrough]
-extends = env:AXIS_THOR_2400_RX_via_UART
diff --git a/src/targets/betafpv_2400.ini b/src/targets/betafpv_2400.ini
deleted file mode 100644
index d7ac62bbcd..0000000000
--- a/src/targets/betafpv_2400.ini
+++ /dev/null
@@ -1,59 +0,0 @@
-# ********************************
-# Transmitter targets
-# ********************************
-
-[env:BETAFPV_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = betafpv.tx_2400.nano2g4
-
-[env:BETAFPV_2400_TX_via_WIFI]
-extends = env:BETAFPV_2400_TX_via_UART
-
-[env:BETAFPV_2400_TX_MICRO_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = betafpv.tx_2400.micro2g4
-
-[env:BETAFPV_2400_TX_MICRO_via_WIFI]
-extends = env:BETAFPV_2400_TX_MICRO_via_UART
-
-[env:BETAFPV_2400_TX_MICRO_1000mw_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = betafpv.tx_2400.micro1w
-
-[env:BETAFPV_2400_TX_MICRO_1000mw_via_WIFI]
-extends = env:BETAFPV_2400_TX_MICRO_1000mw_via_UART
-
-[env:BETAFPV_2400_TX_LITERADIO3_via_ETX]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = betafpv.tx_2400.literadio3
-
-[env:BETAFPV_2400_TX_LITERADIO3_via_UART]
-extends = env:BETAFPV_2400_TX_LITERADIO3_via_ETX
-
-[env:BETAFPV_2400_TX_LITERADIO3_via_WIFI]
-extends = env:BETAFPV_2400_TX_LITERADIO3_via_ETX
-
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:BETAFPV_Nano_2400_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = betafpv.rx_2400.nano
-
-[env:BETAFPV_Nano_2400_RX_via_BetaflightPassthrough]
-extends = env:BETAFPV_Nano_2400_RX_via_UART
-
-[env:BETAFPV_Nano_2400_RX_via_WIFI]
-extends = env:BETAFPV_Nano_2400_RX_via_UART
-
-[env:BETAFPV_SuperD_2400_RX_via_UART]
-extends = env:Unified_ESP32_2400_RX_via_UART
-board_config = betafpv.rx_2400.superd
-
-[env:BETAFPV_SuperD_2400_RX_via_BetaflightPassthrough]
-extends = env:BETAFPV_SuperD_2400_RX_via_UART
-
-[env:BETAFPV_SuperD_2400_RX_via_WIFI]
-extends = env:BETAFPV_SuperD_2400_RX_via_UART
diff --git a/src/targets/betafpv_900.ini b/src/targets/betafpv_900.ini
deleted file mode 100644
index 480ec5b486..0000000000
--- a/src/targets/betafpv_900.ini
+++ /dev/null
@@ -1,31 +0,0 @@
-# ********************************
-# Transmitter targets
-# ********************************
-
-[env:BETAFPV_900_TX_via_UART]
-extends = env:Unified_ESP32_900_TX_via_UART
-board_config = betafpv.tx_900.nano900
-
-[env:BETAFPV_900_TX_via_WIFI]
-extends = env:BETAFPV_900_TX_via_UART
-
-[env:BETAFPV_900_TX_MICRO_via_UART]
-extends = env:Unified_ESP32_900_TX_via_UART
-board_config = betafpv.tx_900.micro900
-
-[env:BETAFPV_900_TX_MICRO_via_WIFI]
-extends = env:BETAFPV_900_TX_MICRO_via_UART
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:BETAFPV_900_RX_via_UART]
-extends = env:Unified_ESP8285_900_RX_via_UART
-board_config = betafpv.rx_900.plain
-
-[env:BETAFPV_900_RX_via_BetaflightPassthrough]
-extends = env:BETAFPV_900_RX_via_UART
-
-[env:BETAFPV_900_RX_via_WIFI]
-extends = env:BETAFPV_900_RX_via_UART
diff --git a/src/targets/common.ini b/src/targets/common.ini
index 3a4d7a9dfa..4c089a142b 100644
--- a/src/targets/common.ini
+++ b/src/targets/common.ini
@@ -18,21 +18,26 @@ build_flags_rx = -DTARGET_RX=1 ${common_env_data.build_flags}
[radio_900]
build_flags = -DRADIO_SX127X=1
-lib_ignore = SX1280Driver
+lib_ignore =
+ LR1121Driver
+ SX1280Driver
+
+[radio_LR1121]
+build_flags = -DRADIO_LR1121=1
+lib_ignore =
+ SX127xDriver
+ SX1280Driver
[radio_2400]
build_flags = -DRADIO_SX128X=1
-lib_ignore = SX127xDriver
+lib_ignore =
+ SX127xDriver
+ LR1121Driver
# ------------------------- COMMON ESP32 DEFINITIONS -----------------
-# platform 5.1.0 uses arduino core 2.0.4 which has a "major" problem with
-# requiring the QIO bootloader on PICO devices, so upgrading older firmware
-# will be problem using OTA, unless we have a mechanism for uploading a new
-# bootloader first!
-
[env_common_esp32]
-platform = espressif32@5.2.0
+platform = espressif32@6.4.0
board = esp32dev
board_build.partitions = min_spiffs.csv
upload_speed = 460800
@@ -50,8 +55,8 @@ build_flags =
-I ${PROJECTSRC_DIR}/hal
build_src_filter = ${common_env_data.build_src_filter} - -
lib_deps =
- makuna/NeoPixelBus @ 2.7.0
ottowinter/ESPAsyncWebServer-esphome @ 3.0.0
+ esphome/AsyncTCP-esphome @ 2.0.1 # use specific version - an update to this library breaks the build
lemmingdev/ESP32-BLE-Gamepad @ 0.5.2
h2zero/NimBLE-Arduino @ 1.4.1
bblanchon/ArduinoJson @ 6.19.4
@@ -62,8 +67,17 @@ tft_lib_deps =
${env_common_esp32.lib_deps}
moononournation/GFX Library for Arduino @ 1.2.8
+[env_common_esp32s3tx]
+platform = espressif32@6.4.0
+extends = env_common_esp32
+board = esp32-s3-devkitc-1
+build_flags =
+ ${env_common_esp32.build_flags}
+ -D PLATFORM_ESP32_S3=1
+ -D ARDUINO_USB_CDC_ON_BOOT
+
[env_common_esp32rx]
-platform = espressif32@5.2.0
+platform = espressif32@6.4.0
board = esp32dev
board_build.partitions = min_spiffs.csv
upload_speed = 460800
@@ -82,11 +96,17 @@ build_flags =
-I ${PROJECTSRC_DIR}/hal
build_src_filter = ${common_env_data.build_src_filter} - -
lib_deps =
- makuna/NeoPixelBus @ 2.7.0
ottowinter/ESPAsyncWebServer-esphome @ 3.0.0
+ esphome/AsyncTCP-esphome @ 2.0.1 # use specific version - an update to this library breaks the build
bblanchon/ArduinoJson @ 6.19.4
- roboticsbrno/ServoESP32 @ 1.0.3
- dlloydev/ESP32 ESP32S2 AnalogWrite @ 3.0.3
+
+[env_common_esp32s3rx]
+platform = espressif32@6.4.0
+extends = env_common_esp32rx
+board = esp32-s3-devkitc-1
+build_flags =
+ ${env_common_esp32rx.build_flags}
+ -D PLATFORM_ESP32_S3=1
# ------------------------- COMMON ESP82xx DEFINITIONS -----------------
[env_common_esp82xx]
diff --git a/src/targets/diy_2400.ini b/src/targets/diy_2400.ini
index b94a3844b1..55ad7a8eaf 100644
--- a/src/targets/diy_2400.ini
+++ b/src/targets/diy_2400.ini
@@ -3,93 +3,10 @@
# Transmitter targets
# ********************************
-[env:DIY_2400_TX_ESP32_SX1280_Mini_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = diy.tx_2400.mini
-
-[env:DIY_2400_TX_ESP32_SX1280_Mini_via_WIFI]
-extends = env:DIY_2400_TX_ESP32_SX1280_Mini_via_UART
-
-[env:DIY_2400_TX_ESP32_SX1280_E28_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = diy.tx_2400.e28
-
-[env:DIY_2400_TX_ESP32_SX1280_E28_via_WIFI]
-extends = env:DIY_2400_TX_ESP32_SX1280_E28_via_UART
-
-[env:DIY_2400_TX_ESP32_SX1280_LORA1280F27_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = diy.tx_2400.f27
-
-[env:DIY_2400_TX_ESP32_SX1280_LORA1280F27_via_WIFI]
-extends = env:DIY_2400_TX_ESP32_SX1280_LORA1280F27_via_UART
-
-[env:DIY_2400_TX_DUPLETX_ESP_via_UART]
-extends = env:Unified_ESP8285_2400_TX_via_UART
-board_config = generic.tx_2400.dupletxesp
-
-[env:DIY_2400_TX_DUPLETX_ESP_via_WIFI]
-extends = env:DIY_2400_TX_DUPLETX_ESP_via_UART
-
-[env:DIY_2400_TX_DUPLETX_via_ETX]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = generic.tx_2400.dupletx
-
-[env:DIY_2400_TX_DUPLETX_via_UART]
-extends = env:DIY_2400_TX_DUPLETX_via_ETX
-
-[env:DIY_2400_TX_DUPLETX_via_WIFI]
-extends = env:DIY_2400_TX_DUPLETX_via_ETX
-
-[env:DIY_2400_TX_GEMINI_via_ETX]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = generic.tx_2400.gemini
-
-[env:DIY_2400_TX_GEMINI_via_UART]
-extends = env:DIY_2400_TX_GEMINI_via_ETX
-
-[env:DIY_2400_TX_GEMINI_via_WIFI]
-extends = env:DIY_2400_TX_GEMINI_via_ETX
-
-
# ********************************
# Receiver targets
# ********************************
-[env:DIY_2400_RX_ESP8285_SX1280_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = generic.rx_2400.plain
-
-[env:DIY_2400_RX_ESP8285_SX1280_via_BetaflightPassthrough]
-extends = env:DIY_2400_RX_ESP8285_SX1280_via_UART
-
-[env:DIY_2400_RX_ESP8285_SX1280_via_WIFI]
-extends = env:DIY_2400_RX_ESP8285_SX1280_via_UART
-
-[env:DIY_2400_PA_RGB_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = generic.rx_2400.pa-rgb
-
-[env:DIY_2400_PA_RGB_RX_via_BetaflightPassthrough]
-extends = env:DIY_2400_PA_RGB_RX_via_UART
-
-[env:DIY_2400_PA_RGB_RX_via_WIFI]
-extends = env:DIY_2400_PA_RGB_RX_via_UART
-
-[env:DIY_2400_RX_PWMP_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = generic.rx_2400.pwm5
-
-[env:DIY_2400_RX_PWMP_via_WIFI]
-extends = env:DIY_2400_RX_PWMP_via_UART
-
-[env:DIY_2400_RX_PWMPEX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = generic.rx_2400.pwm6
-
-[env:DIY_2400_RX_PWMPEX_via_WIFI]
-extends = env:DIY_2400_RX_PWMPEX_via_UART
-
[env:DIY_2400_RX_STM32_CCG_Nano_v0_5_via_STLINK]
extends = env_common_stm32, radio_2400
board = l432kb
@@ -117,10 +34,3 @@ upload_flags =
[env:DIY_2400_RX_STM32_CCG_Nano_v0_5_via_BetaflightPassthrough]
extends = env:DIY_2400_RX_STM32_CCG_Nano_v0_5_via_STLINK
-
-[env:DIY_2400_RX_PWM_VARIO_via_UART]
-extends = env:Unified_ESP32_2400_RX_via_UART
-board_config = generic.rx_2400.pwm-vario
-
-[env:DIY_2400_RX_PWM_VARIO_via_WIFI]
-extends = env:DIY_2400_RX_PWM_VARIO_via_UART
diff --git a/src/targets/diy_900.ini b/src/targets/diy_900.ini
index b1b3976eca..56541717cc 100644
--- a/src/targets/diy_900.ini
+++ b/src/targets/diy_900.ini
@@ -3,34 +3,6 @@
# Transmitter targets
# ********************************
-[env:DIY_900_TX_TTGO_V1_SX127x_via_UART]
-extends = env:Unified_ESP32_900_TX_via_UART
-board_config = diy.tx_900.ttgov1
-
-[env:DIY_900_TX_TTGO_V1_SX127x_via_WIFI]
-extends = env:DIY_900_TX_TTGO_V1_SX127x_via_UART
-
-[env:DIY_900_TX_TTGO_V2_SX127x_via_UART]
-extends = env:Unified_ESP32_900_TX_via_UART
-board_config = diy.tx_900.ttgov2
-
-[env:DIY_900_TX_TTGO_V2_SX127x_via_WIFI]
-extends = env:DIY_900_TX_TTGO_V2_SX127x_via_UART
-
-[env:DIY_900_TX_ESP32_SX127x_E19_via_UART]
-extends = env:Unified_ESP32_900_TX_via_UART
-board_config = diy.tx_900.e19
-
-[env:DIY_900_TX_ESP32_SX127x_E19_via_WIFI]
-extends = env:DIY_900_TX_ESP32_SX127x_E19_via_UART
-
-[env:DIY_900_TX_ESP32_SX127x_RFM95_via_UART]
-extends = env:Unified_ESP32_900_TX_via_UART
-board_config = diy.tx_900.rfm95
-
-[env:DIY_900_TX_ESP32_SX127x_RFM95_via_WIFI]
-extends = env:DIY_900_TX_ESP32_SX127x_RFM95_via_UART
-
[env:DIY_900_TX_STM32_SX1272_via_STLINK]
extends = env_common_stm32, radio_900
build_flags =
@@ -51,53 +23,6 @@ upload_flags =
# Receiver targets
# ********************************
-[env:DIY_900_RX_TTGO_V1_SX127x_via_UART]
-extends = env:Unified_ESP32_900_RX_via_UART
-board_config = diy.rx_900.ttgov1
-
-[env:DIY_900_RX_TTGO_V1_SX127x_via_BetaflightPassthrough]
-extends = env:DIY_900_RX_TTGO_V1_SX127x_via_UART
-
-[env:DIY_900_RX_TTGO_V1_SX127x_via_WIFI]
-extends = env:DIY_900_RX_TTGO_V1_SX127x_via_UART
-
-[env:DIY_900_RX_TTGO_V2_SX127x_via_UART]
-extends = env:Unified_ESP32_900_RX_via_UART
-board_config = diy.rx_900.ttgov2
-
-[env:DIY_900_RX_TTGO_V2_SX127x_via_BetaflightPassthrough]
-extends = env:DIY_900_RX_TTGO_V2_SX127x_via_UART
-
-[env:DIY_900_RX_TTGO_V2_SX127x_via_WIFI]
-extends = env:DIY_900_RX_TTGO_V2_SX127x_via_UART
-
-[env:DIY_900_RX_ESP8285_SX127x_via_UART]
-extends = env:Unified_ESP8285_900_RX_via_UART
-board_config = generic.rx_900.plain
-
-[env:DIY_900_RX_ESP8285_SX127x_via_BetaflightPassthrough]
-extends = env:DIY_900_RX_ESP8285_SX127x_via_UART
-
-[env:DIY_900_RX_ESP8285_SX127x_via_WIFI]
-extends = env:DIY_900_RX_ESP8285_SX127x_via_UART
-
-[env:DIY_900_RX_HUZZAH_RFM95W_via_UART]
-extends = env:Unified_ESP8285_900_RX_via_UART
-board_config = diy.rx_900.huzzah
-
-[env:DIY_900_RX_HUZZAH_RFM95W_via_BetaflightPassthrough]
-extends = env:DIY_900_RX_HUZZAH_RFM95W_via_UART
-
-[env:DIY_900_RX_HUZZAH_RFM95W_via_WIFI]
-extends = env:DIY_900_RX_HUZZAH_RFM95W_via_UART
-
-[env:DIY_900_RX_PWMP_via_UART]
-extends = env:Unified_ESP8285_900_RX_via_UART
-board_config = generic.rx_900.pwmp
-
-[env:DIY_900_RX_PWMP_via_WIFI]
-extends = env:DIY_900_RX_PWMP_via_UART
-
[env:DIY_900_RX_BluePill_via_STLINK]
extends = env_common_stm32, radio_900
build_flags =
diff --git a/src/targets/diy_ble_joystick.ini b/src/targets/diy_ble_joystick.ini
deleted file mode 100644
index cfc4fdbdf5..0000000000
--- a/src/targets/diy_ble_joystick.ini
+++ /dev/null
@@ -1,8 +0,0 @@
-
-# ********************************
-# DIY BLE Joystick target
-# ********************************
-
-[env:DIY_BLE_JOYSTICK_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = diy.tx_2400.ble
diff --git a/src/targets/emax_2400.ini b/src/targets/emax_2400.ini
deleted file mode 100644
index a5df44a35d..0000000000
--- a/src/targets/emax_2400.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-# ********************************
-# Transmitter targets
-# ********************************
-
-[env:EMAX_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = emax.tx_2400.plain
-
-[env:EMAX_2400_TX_via_WIFI]
-extends = env:EMAX_2400_TX_via_UART
-
-[env:EMAX_NANO_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = emax.tx_2400.nano
-
-[env:EMAX_NANO_2400_TX_via_WIFI]
-extends = env:EMAX_2400_TX_via_UART
diff --git a/src/targets/emax_900.ini b/src/targets/emax_900.ini
deleted file mode 100644
index 2221e1ae08..0000000000
--- a/src/targets/emax_900.ini
+++ /dev/null
@@ -1,17 +0,0 @@
-# ********************************
-# Transmitter targets
-# ********************************
-
-[env:EMAX_900_TX_via_UART]
-extends = env:Unified_ESP32_900_TX_via_UART
-board_config = emax.tx_900.plain
-
-[env:EMAX_900_TX_via_WIFI]
-extends = env:EMAX_900_TX_via_UART
-
-[env:EMAX_NANO_900_TX_via_UART]
-extends = env:Unified_ESP32_900_TX_via_UART
-board_config = emax.tx_900.nano
-
-[env:EMAX_NANO_900_TX_via_WIFI]
-extends = env:EMAX_NANO_900_TX_via_UART
diff --git a/src/targets/foxeer_2400.ini b/src/targets/foxeer_2400.ini
deleted file mode 100644
index 4ad2fda206..0000000000
--- a/src/targets/foxeer_2400.ini
+++ /dev/null
@@ -1,18 +0,0 @@
-
-# ********************************
-# Transmitter targets
-# ********************************
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:Foxeer_2400_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = foxeer.rx_2400.pa-rgb
-
-[env:Foxeer_2400_RX_via_BetaflightPassthrough]
-extends = env:Foxeer_2400_RX_via_UART
-
-[env:Foxeer_2400_RX_via_WIFI]
-extends = env:Foxeer_2400_RX_via_UART
diff --git a/src/targets/happymodel_2400.ini b/src/targets/happymodel_2400.ini
index c72c3c8c34..c28ed0c9dd 100644
--- a/src/targets/happymodel_2400.ini
+++ b/src/targets/happymodel_2400.ini
@@ -3,46 +3,12 @@
# Transmitter targets
# ********************************
-[env:HappyModel_ES24TX_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = happymodel.tx_2400.es24
-
-[env:HappyModel_ES24TX_2400_TX_via_WIFI]
-extends = env:HappyModel_ES24TX_2400_TX_via_UART
-
-[env:HappyModel_ES24TX_Pro_Series_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = happymodel.tx_2400.es24pro
-
-[env:HappyModel_ES24TX_Pro_Series_2400_TX_via_WIFI]
-extends = env:HappyModel_ES24TX_Pro_Series_2400_TX_via_UART
-
# ********************************
# Receiver targets
# ********************************
-[env:HappyModel_EP_2400_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = happymodel.rx_2400.ep1
-
-[env:HappyModel_EP_2400_RX_via_BetaflightPassthrough]
-extends = env:HappyModel_EP_2400_RX_via_UART
-
-[env:HappyModel_EP_2400_RX_via_WIFI]
-extends = env:HappyModel_EP_2400_RX_via_UART
-
[env:HappyModel_PP_2400_RX_via_STLINK]
extends = env:DIY_2400_RX_STM32_CCG_Nano_v0_5_via_STLINK
[env:HappyModel_PP_2400_RX_via_BetaflightPassthrough]
extends = env:HappyModel_PP_2400_RX_via_STLINK
-
-[env:HappyModel_EP_Dual_2400_RX_via_UART]
-extends = env:Unified_ESP32_2400_RX_via_UART
-board_config = happymodel.rx_2400.ep-dual
-
-[env:HappyModel_EP_Dual_2400_RX_via_BetaflightPassthrough]
-extends = env:HappyModel_EP_Dual_2400_RX_via_UART
-
-[env:HappyModel_EP_Dual_2400_RX_via_WIFI]
-extends = env:HappyModel_EP_Dual_2400_RX_via_UART
diff --git a/src/targets/happymodel_900.ini b/src/targets/happymodel_900.ini
index 4d2f52f683..e99e6913ca 100644
--- a/src/targets/happymodel_900.ini
+++ b/src/targets/happymodel_900.ini
@@ -26,14 +26,6 @@ extends = env:HappyModel_TX_ES915TX_via_STLINK
[env:HappyModel_TX_ES915TX_via_WIFI]
extends = env:HappyModel_TX_ES915TX_via_STLINK
-[env:HappyModel_TX_ES900TX_via_UART]
-extends = env:Unified_ESP32_900_TX_via_UART
-board_config = happymodel.tx_900.es900
-
-[env:HappyModel_TX_ES900TX_via_WIFI]
-extends = env:HappyModel_TX_ES900TX_via_UART
-
-
# ********************************
# Receiver targets
# ********************************
@@ -55,13 +47,3 @@ upload_flags =
[env:HappyModel_RX_ES915RX_via_BetaflightPassthrough]
extends = env:HappyModel_RX_ES915RX_via_STLINK
-
-[env:HappyModel_RX_ES900RX_via_UART]
-extends = env:Unified_ESP8285_900_RX_via_UART
-board_config = happymodel.rx_900.es900
-
-[env:HappyModel_RX_ES900RX_via_WIFI]
-extends = env:HappyModel_RX_ES900RX_via_UART
-
-[env:HappyModel_RX_ES900RX_via_BetaflightPassthrough]
-extends = env:HappyModel_RX_ES900RX_via_UART
diff --git a/src/targets/iFlight_2400.ini b/src/targets/iFlight_2400.ini
deleted file mode 100644
index 1bccdcd6d9..0000000000
--- a/src/targets/iFlight_2400.ini
+++ /dev/null
@@ -1,25 +0,0 @@
-
-# ********************************
-# Transmitter targets
-# ********************************
-
-[env:iFlight_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = iflight.tx_2400.plain
-
-[env:iFlight_2400_TX_via_WIFI]
-extends = env:iFlight_2400_TX_via_UART
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:iFlight_2400_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = iflight.rx_2400.plain
-
-[env:iFlight_2400_RX_via_BetaflightPassthrough]
-extends = env:iFlight_2400_RX_via_UART
-
-[env:iFlight_2400_RX_via_WIFI]
-extends = env:iFlight_2400_RX_via_UART
diff --git a/src/targets/iFlight_900.ini b/src/targets/iFlight_900.ini
deleted file mode 100644
index 80b35b6e97..0000000000
--- a/src/targets/iFlight_900.ini
+++ /dev/null
@@ -1,24 +0,0 @@
-# ********************************
-# Transmitter targets
-# ********************************
-
-[env:iFlight_900_TX_via_UART]
-extends = env:Unified_ESP32_900_TX_via_UART
-board_config = iflight.tx_900.plain
-
-[env:iFlight_900_TX_via_WIFI]
-extends = env:iFlight_900_TX_via_UART
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:iFlight_900_RX_via_UART]
-extends = env:Unified_ESP8285_900_RX_via_UART
-board_config = iflight.rx_900.plain
-
-[env:iFlight_900_RX_via_WIFI]
-extends = env:iFlight_900_RX_via_UART
-
-[env:iFlight_900_RX_via_BetaflightPassthrough]
-extends = env:iFlight_900_RX_via_UART
diff --git a/src/targets/namimnorc_2400.ini b/src/targets/namimnorc_2400.ini
index ae0d08798a..4ace79a545 100644
--- a/src/targets/namimnorc_2400.ini
+++ b/src/targets/namimnorc_2400.ini
@@ -22,14 +22,6 @@ upload_flags =
[env:NamimnoRC_FLASH_2400_TX_via_WIFI]
extends = env:NamimnoRC_FLASH_2400_TX_via_STLINK
-[env:NamimnoRC_FLASH_2400_OLED_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = namimnorc.tx_2400.flash
-
-[env:NamimnoRC_FLASH_2400_OLED_TX_via_WIFI]
-extends = env:NamimnoRC_FLASH_2400_OLED_TX_via_UART
-
-
# ********************************
# Receiver targets
# ********************************
@@ -51,23 +43,3 @@ upload_flags =
[env:NamimnoRC_FLASH_2400_RX_via_BetaflightPassthrough]
extends = env:NamimnoRC_FLASH_2400_RX_via_STLINK
-
-[env:NamimnoRC_FLASH_2400_ESP_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = namimnorc.rx_2400.flash
-
-[env:NamimnoRC_FLASH_2400_ESP_RX_via_BetaflightPassthrough]
-extends = env:NamimnoRC_FLASH_2400_ESP_RX_via_UART
-
-[env:NamimnoRC_FLASH_2400_ESP_RX_via_WIFI]
-extends = env:NamimnoRC_FLASH_2400_ESP_RX_via_UART
-
-[env:NamimnoRC_FLASH_2400_ESP_RX_PA_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = namimnorc.rx_2400.flash-pa
-
-[env:NamimnoRC_FLASH_2400_ESP_RX_PA_via_BetaflightPassthrough]
-extends = env:NamimnoRC_FLASH_2400_ESP_RX_PA_via_UART
-
-[env:NamimnoRC_FLASH_2400_ESP_RX_PA_via_WIFI]
-extends = env:NamimnoRC_FLASH_2400_ESP_RX_PA_via_UART
diff --git a/src/targets/namimnorc_900.ini b/src/targets/namimnorc_900.ini
index 35cb9a3b81..99b7314ab2 100644
--- a/src/targets/namimnorc_900.ini
+++ b/src/targets/namimnorc_900.ini
@@ -23,14 +23,6 @@ lib_deps =
[env:NamimnoRC_VOYAGER_900_TX_via_WIFI]
extends = env:NamimnoRC_VOYAGER_900_TX_via_STLINK
-[env:NamimnoRC_VOYAGER_900_OLED_TX_via_UART]
-extends = env:Unified_ESP32_900_TX_via_UART
-board_config = namimnorc.tx_900.voyager
-
-[env:NamimnoRC_VOYAGER_900_OLED_TX_via_WIFI]
-extends = env:NamimnoRC_VOYAGER_900_OLED_TX_via_UART
-
-
# ********************************
# Receiver targets
# ********************************
@@ -53,13 +45,3 @@ lib_deps =
[env:NamimnoRC_VOYAGER_900_RX_via_BetaflightPassthrough]
extends = env:NamimnoRC_VOYAGER_900_RX_via_STLINK
-
-[env:NamimnoRC_VOYAGER_900_ESP_RX_via_UART]
-extends = env:Unified_ESP8285_900_RX_via_UART
-board_config = namimnorc.rx_900.voyager
-
-[env:NamimnoRC_VOYAGER_900_ESP_RX_via_BetaflightPassthrough]
-extends = env:NamimnoRC_VOYAGER_900_ESP_RX_via_UART
-
-[env:NamimnoRC_VOYAGER_900_ESP_RX_via_WIFI]
-extends = env:NamimnoRC_VOYAGER_900_ESP_RX_via_UART
diff --git a/src/targets/neutronrc_900.ini b/src/targets/neutronrc_900.ini
deleted file mode 100644
index bf0101d302..0000000000
--- a/src/targets/neutronrc_900.ini
+++ /dev/null
@@ -1,19 +0,0 @@
-
-# ********************************
-# Transmitter targets
-# ********************************
-
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:NeutronRC_900_RX_via_UART]
-extends = env:Unified_ESP8285_900_RX_via_UART
-board_config = neutronrc.rx_900.plain
-
-[env:NeutronRC_900_RX_via_BetaflightPassthrough]
-extends = env:NeutronRC_900_RX_via_UART
-
-[env:NeutronRC_900_RX_via_WIFI]
-extends = env:DIY_900_RX_ESP8285_SX127x_via_WIFI
diff --git a/src/targets/quadkopters_2400.ini b/src/targets/quadkopters_2400.ini
deleted file mode 100644
index b540d962dd..0000000000
--- a/src/targets/quadkopters_2400.ini
+++ /dev/null
@@ -1,25 +0,0 @@
-
-# ********************************
-# Transmitter targets
-# ********************************
-
-[env:QuadKopters_JR_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = quadkopters.tx_2400.jr
-
-[env:QuadKopters_JR_2400_TX_via_WIFI]
-extends = env:QuadKopters_JR_2400_TX_via_UART
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:QuadKopters_NANO_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = quadkopters.rx_2400.nano
-
-[env:QuadKopters_NANO_RX_via_BetaflightPassthrough]
-extends = env:QuadKopters_NANO_RX_via_UART
-
-[env:QuadKopters_NANO_RX_via_WIFI]
-extends = env:QuadKopters_NANO_RX_via_UART
diff --git a/src/targets/radiomaster_2400.ini b/src/targets/radiomaster_2400.ini
deleted file mode 100644
index 51f12680e4..0000000000
--- a/src/targets/radiomaster_2400.ini
+++ /dev/null
@@ -1,54 +0,0 @@
-# ********************************
-# Transmitter targets
-# ********************************
-
-[env:RadioMaster_Zorro_2400_TX_via_ETX]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = radiomaster.tx_2400.zorro
-
-[env:RadioMaster_Zorro_2400_TX_via_UART]
-extends = env:RadioMaster_Zorro_2400_TX_via_ETX
-
-[env:RadioMaster_Zorro_2400_TX_via_WIFI]
-extends = env:RadioMaster_Zorro_2400_TX_via_ETX
-
-[env:RadioMaster_TX16S_2400_TX_via_ETX]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = radiomaster.tx_2400.tx16s
-
-[env:RadioMaster_TX16S_2400_TX_via_UART]
-extends = env:RadioMaster_TX16S_2400_TX_via_ETX
-
-[env:RadioMaster_TX16S_2400_TX_via_WIFI]
-extends = env:RadioMaster_TX16S_2400_TX_via_ETX
-
-[env:RadioMaster_Ranger_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = radiomaster.tx_2400.ranger
-
-[env:RadioMaster_Ranger_2400_TX_via_WIFI]
-extends = env:RadioMaster_Ranger_2400_TX_via_UART
-
-[env:RadioMaster_Ranger_Micro_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = radiomaster.tx_2400.ranger-micro
-
-[env:RadioMaster_Ranger_Micro_2400_TX_via_WIFI]
-extends = env:RadioMaster_Ranger_Micro_2400_TX_via_UART
-
-[env:RadioMaster_Ranger_Nano_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = radiomaster.tx_2400.ranger-nano
-
-[env:RadioMaster_Ranger_Nano_2400_TX_via_WIFI]
-extends = env:RadioMaster_Ranger_Nano_2400_TX_via_UART
-
-[env:RadioMaster_Boxer_2400_TX_via_ETX]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = radiomaster.tx_2400.boxer
-
-[env:RadioMaster_Boxer_2400_TX_via_UART]
-extends = env:RadioMaster_Boxer_2400_TX_via_ETX
-
-[env:RadioMaster_Boxer_2400_TX_via_WIFI]
-extends = env:RadioMaster_Boxer_2400_TX_via_ETX
diff --git a/src/targets/siyi.ini b/src/targets/siyi.ini
index ad978a5c3b..166e8c548b 100644
--- a/src/targets/siyi.ini
+++ b/src/targets/siyi.ini
@@ -10,10 +10,10 @@ build_flags =
${env_common_stm32.build_flags}
${radio_2400.build_flags}
${common_env_data.build_flags_tx}
- -flto
+ #-flto
+ #-DUSBCON
+ #-DPIO_FRAMEWORK_ARDUINO_ENABLE_CDC
-include target/FM30_TX.h
- -DUSBCON
- -DPIO_FRAMEWORK_ARDUINO_ENABLE_CDC
-DHSE_VALUE=16000000U
-DVECT_TAB_OFFSET=0x1000U
-Wl,--defsym=FLASH_APP_OFFSET=0x1000
diff --git a/src/targets/unified.ini b/src/targets/unified.ini
index b53063bbd6..d06c41cbf7 100644
--- a/src/targets/unified.ini
+++ b/src/targets/unified.ini
@@ -9,7 +9,6 @@ build_flags =
${radio_2400.build_flags}
${common_env_data.build_flags_tx}
-include target/Unified_ESP32_TX.h
- -D TARGET_AXIS_THOR_2400_TX=1
-D VTABLES_IN_FLASH=1
-O2
build_src_filter = ${env_common_esp32.build_src_filter} -
@@ -33,7 +32,6 @@ build_flags =
${radio_900.build_flags}
${common_env_data.build_flags_tx}
-include target/Unified_ESP32_TX.h
- -D TARGET_AXIS_THOR_2400_TX=1
-D VTABLES_IN_FLASH=1
-O2
build_src_filter = ${env_common_esp32.build_src_filter} -
@@ -50,6 +48,44 @@ extends = env:Unified_ESP32_900_TX_via_ETX
[env:Unified_ESP32_900_TX_via_WIFI]
extends = env:Unified_ESP32_900_TX_via_ETX
+[env:Unified_ESP8285_900_TX_via_UART]
+extends = env_common_esp82xx, radio_900
+board_build.f_cpu = 80000000L
+build_flags =
+ ${env_common_esp82xx.build_flags}
+ ${radio_900.build_flags}
+ ${common_env_data.build_flags_tx}
+ -include target/Unified_ESP8285_TX.h
+build_src_filter = ${env_common_esp82xx.build_src_filter} -
+board_build.ldscript = ./elrs.flash.1m64.ld
+
+[env:Unified_ESP8285_900_TX_via_WIFI]
+extends = env:Unified_ESP8285_900_TX_via_UART
+
+[env:Unified_ESP32_LR1121_TX_via_ETX]
+extends = env_common_esp32, radio_LR1121
+build_flags =
+ ${env_common_esp32.build_flags}
+ ${radio_LR1121.build_flags}
+ ${common_env_data.build_flags_tx}
+ -include target/Unified_ESP32_TX.h
+ -D TARGET_AXIS_THOR_2400_TX=1
+ -D VTABLES_IN_FLASH=1
+ -O2
+build_src_filter = ${env_common_esp32.build_src_filter} -
+lib_deps =
+ ${env_common_esp32.tft_lib_deps}
+ ${env_common_esp32.oled_lib_deps}
+ SPIFFS
+upload_speed = 460800
+monitor_speed = 460800
+
+[env:Unified_ESP32_LR1121_TX_via_UART]
+extends = env:Unified_ESP32_LR1121_TX_via_ETX
+
+[env:Unified_ESP32_LR1121_TX_via_WIFI]
+extends = env:Unified_ESP32_LR1121_TX_via_ETX
+
[env:Unified_ESP8285_2400_TX_via_UART]
extends = env_common_esp82xx, radio_2400
board_build.f_cpu = 80000000L
@@ -64,6 +100,52 @@ board_build.ldscript = ./elrs.flash.1m64.ld
[env:Unified_ESP8285_2400_TX_via_WIFI]
extends = env:Unified_ESP8285_2400_TX_via_UART
+[env:Unified_ESP32S3_2400_TX_via_ETX]
+extends = env_common_esp32s3tx, radio_2400
+build_flags =
+ ${env_common_esp32s3tx.build_flags}
+ ${radio_2400.build_flags}
+ ${common_env_data.build_flags_tx}
+ -include target/Unified_ESP32_TX.h
+ -D VTABLES_IN_FLASH=1
+ -O2
+build_src_filter = ${env_common_esp32s3tx.build_src_filter} -
+lib_deps =
+ ${env_common_esp32s3tx.tft_lib_deps}
+ ${env_common_esp32s3tx.oled_lib_deps}
+ SPIFFS
+upload_speed = 460800
+monitor_speed = 420000
+
+[env:Unified_ESP32S3_2400_TX_via_UART]
+extends = env:Unified_ESP32S3_2400_TX_via_ETX
+
+[env:Unified_ESP32S3_2400_TX_via_WIFI]
+extends = env:Unified_ESP32S3_2400_TX_via_ETX
+
+[env:Unified_ESP32S3_900_TX_via_ETX]
+extends = env_common_esp32s3tx, radio_900
+build_flags =
+ ${env_common_esp32s3tx.build_flags}
+ ${radio_900.build_flags}
+ ${common_env_data.build_flags_tx}
+ -include target/Unified_ESP32_TX.h
+ -D VTABLES_IN_FLASH=1
+ -O2
+build_src_filter = ${env_common_esp32s3tx.build_src_filter} -
+lib_deps =
+ ${env_common_esp32s3tx.tft_lib_deps}
+ ${env_common_esp32s3tx.oled_lib_deps}
+ SPIFFS
+upload_speed = 460800
+monitor_speed = 460800
+
+[env:Unified_ESP32S3_900_TX_via_UART]
+extends = env:Unified_ESP32S3_900_TX_via_ETX
+
+[env:Unified_ESP32S3_900_TX_via_WIFI]
+extends = env:Unified_ESP32S3_900_TX_via_ETX
+
# ********************************
# Receiver targets
@@ -101,7 +183,6 @@ extends = env:Unified_ESP8285_900_RX_via_UART
[env:Unified_ESP8285_900_RX_via_WIFI]
extends = env:Unified_ESP8285_900_RX_via_UART
-
[env:Unified_ESP32_2400_RX_via_UART]
extends = env_common_esp32rx, radio_2400
build_flags =
@@ -130,4 +211,51 @@ build_src_filter = ${env_common_esp32rx.build_src_filter} -
extends = env:Unified_ESP32_900_RX_via_UART
[env:Unified_ESP32_900_RX_via_WIFI]
-extends = env:Unified_ESP32_900_RX_via_UART
\ No newline at end of file
+extends = env:Unified_ESP32_900_RX_via_UART
+
+[env:Unified_ESP32_LR1121_RX_via_UART]
+extends = env_common_esp32rx, radio_LR1121
+build_flags =
+ ${env_common_esp32rx.build_flags}
+ ${radio_LR1121.build_flags}
+ ${common_env_data.build_flags_rx}
+ -include target/Unified_ESP_RX.h
+build_src_filter = ${env_common_esp32rx.build_src_filter} -
+
+[env:Unified_ESP32_LR1121_RX_via_BetaflightPassthrough]
+extends = env:Unified_ESP32_LR1121_RX_via_UART
+
+[env:Unified_ESP32_LR1121_RX_via_WIFI]
+extends = env:Unified_ESP32_LR1121_RX_via_UART
+
+# ESP32-S3
+
+[env:Unified_ESP32S3_2400_RX_via_UART]
+extends = env_common_esp32s3rx, radio_2400
+build_flags =
+ ${env_common_esp32s3rx.build_flags}
+ ${radio_2400.build_flags}
+ ${common_env_data.build_flags_rx}
+ -include target/Unified_ESP_RX.h
+build_src_filter = ${env_common_esp32rx.build_src_filter} -
+
+[env:Unified_ESP32S3_2400_RX_via_BetaflightPassthrough]
+extends = env:Unified_ESP32S3_2400_RX_via_UART
+
+[env:Unified_ESP32S3_2400_RX_via_WIFI]
+extends = env:Unified_ESP32S3_2400_RX_via_UART
+
+[env:Unified_ESP32S3_900_RX_via_UART]
+extends = env_common_esp32s3rx, radio_900
+build_flags =
+ ${env_common_esp32s3rx.build_flags}
+ ${radio_900.build_flags}
+ ${common_env_data.build_flags_rx}
+ -include target/Unified_ESP_RX.h
+build_src_filter = ${env_common_esp32rx.build_src_filter} -
+
+[env:Unified_ESP32S3_900_RX_via_BetaflightPassthrough]
+extends = env:Unified_ESP32S3_900_RX_via_UART
+
+[env:Unified_ESP32S3_900_RX_via_WIFI]
+extends = env:Unified_ESP32S3_900_RX_via_UART
diff --git a/src/targets/vantac_2400.ini b/src/targets/vantac_2400.ini
deleted file mode 100644
index 7eb6e0e2be..0000000000
--- a/src/targets/vantac_2400.ini
+++ /dev/null
@@ -1,25 +0,0 @@
-
-# ********************************
-# Transmitter targets
-# ********************************
-
-[env:Vantac_Lite_2400_TX_via_UART]
-extends = env:Unified_ESP32_2400_TX_via_UART
-board_config = vantac.tx_2400.lite
-
-[env:Vantac_Lite_2400_TX_via_WIFI]
-extends = env:Vantac_Lite_2400_TX_via_UART
-
-# ********************************
-# Receiver targets
-# ********************************
-
-[env:Vantac_2400_RX_via_UART]
-extends = env:Unified_ESP8285_2400_RX_via_UART
-board_config = vantac.rx_2400.plain
-
-[env:Vantac_2400_RX_via_BetaflightPassthrough]
-extends = env:Vantac_2400_RX_via_UART
-
-[env:Vantac_2400_RX_via_WIFI]
-extends = env:Vantac_2400_RX_via_UART
diff --git a/src/test/test_crc/test_crc.cpp b/src/test/test_crc/test_crc.cpp
index 5a9ed9539e..f7ab0a7416 100644
--- a/src/test/test_crc/test_crc.cpp
+++ b/src/test/test_crc/test_crc.cpp
@@ -1,4 +1,5 @@
#include
+#include
#include
#include "ucrc_t.h"
#include
diff --git a/src/test/test_crc/ucrc_t.h b/src/test/test_crc/ucrc_t.h
index 4425076405..213be5cbba 100644
--- a/src/test/test_crc/ucrc_t.h
+++ b/src/test/test_crc/ucrc_t.h
@@ -44,7 +44,7 @@
#ifndef UCRC_T_H
#define UCRC_T_H
-#include
+#include
#include
#include // for std::ifstream
#include // for std::ios_base, etc.
diff --git a/src/test/test_crsf/test_crsf.cpp b/src/test/test_crsf/test_crsf.cpp
index 26483eb91a..0f0ee126f7 100644
--- a/src/test/test_crsf/test_crsf.cpp
+++ b/src/test/test_crsf/test_crsf.cpp
@@ -1,15 +1,13 @@
#include
-#include
#include
+#include
#include "../test_msp/mock_serial.h"
#include "common.h"
-#include "devCRSF.h"
+#include "CRSF.h"
using namespace std;
-// Create a CRSF object to test,
-CRSF crsf;
uint32_t ChannelData[CRSF_NUM_CHANNELS]; // Current state of channels, CRSF format
GENERIC_CRC8 test_crc(CRSF_CRC_POLY);
@@ -44,8 +42,8 @@ void test_device_info(void)
TEST_ASSERT_EQUAL(22, DEVICE_INFORMATION_PAYLOAD_LENGTH);
TEST_ASSERT_EQUAL(28, DEVICE_INFORMATION_LENGTH);
- crsf.GetDeviceInformation(deviceInformation, 0);
- crsf.SetExtendedHeaderAndCrc(deviceInformation, CRSF_FRAMETYPE_DEVICE_INFO, DEVICE_INFORMATION_FRAME_SIZE, CRSF_ADDRESS_CRSF_RECEIVER, CRSF_ADDRESS_FLIGHT_CONTROLLER);
+ CRSF::GetDeviceInformation(deviceInformation, 0);
+ CRSF::SetExtendedHeaderAndCrc(deviceInformation, CRSF_FRAMETYPE_DEVICE_INFO, DEVICE_INFORMATION_FRAME_SIZE, CRSF_ADDRESS_CRSF_RECEIVER, CRSF_ADDRESS_FLIGHT_CONTROLLER);
crsf_ext_header_t *header = (crsf_ext_header_t *) deviceInformation;
diff --git a/src/test/test_embedded/rx_sx1280_test.cpp b/src/test/test_embedded/rx_sx1280_test.cpp
index 13e092b4da..e030fbf51b 100644
--- a/src/test/test_embedded/rx_sx1280_test.cpp
+++ b/src/test/test_embedded/rx_sx1280_test.cpp
@@ -25,7 +25,7 @@ void setup()
Serial.begin(115200);
Serial.println("Begin SX1280 testing...");
- Radio.Begin();
+ Radio.Begin(FHSSgetMinimumFreq(), FHSSgetMaximumFreq());
Radio.Config(SX1280_LORA_BW_0800, SX1280_LORA_SF6, SX1280_LORA_CR_4_7, 2420000000, 12, 8);
Radio.TXdoneCallback = &TXdoneCallback;
//Radio.TXnb(testdata, sizeof(testdata));
diff --git a/src/test/test_embedded/tx_sx1280_test.cpp b/src/test/test_embedded/tx_sx1280_test.cpp
index 78dc49f889..04181b9ac4 100644
--- a/src/test/test_embedded/tx_sx1280_test.cpp
+++ b/src/test/test_embedded/tx_sx1280_test.cpp
@@ -32,7 +32,7 @@ void setup()
Serial.begin(115200);
Serial.println("Begin SX1280 testing...");
- Radio.Begin();
+ Radio.Begin(FHSSgetMinimumFreq(), FHSSgetMaximumFreq());
//Radio.Config(SX1280_LORA_BW_0800, SX1280_LORA_SF6, SX1280_LORA_CR_4_7, 2420000000, SX1280_PREAMBLE_LENGTH_32_BITS);
Radio.TXdoneCallback = &TXdoneCallback;
Radio.RXdoneCallback = &RXdoneCallback;
diff --git a/src/test/test_fhss/test_fhss.cpp b/src/test/test_fhss/test_fhss.cpp
index c179406790..bb66696755 100644
--- a/src/test/test_fhss/test_fhss.cpp
+++ b/src/test/test_fhss/test_fhss.cpp
@@ -7,7 +7,7 @@
void test_fhss_first(void)
{
FHSSrandomiseFHSSsequence(0x01020304L);
- TEST_ASSERT_EQUAL(GetInitialFreq(), FHSSconfig->freq_start + freq_spread * sync_channel / FREQ_SPREAD_SCALE);
+ TEST_ASSERT_EQUAL(FHSSgetInitialFreq(), FHSSconfig->freq_start + freq_spread * sync_channel / FREQ_SPREAD_SCALE);
}
void test_fhss_assignment(void)
@@ -15,7 +15,7 @@ void test_fhss_assignment(void)
FHSSrandomiseFHSSsequence(0x01020304L);
const uint32_t numFhss = FHSSgetChannelCount();
- uint32_t initFreq = GetInitialFreq();
+ uint32_t initFreq = FHSSgetInitialFreq();
uint32_t freq = initFreq;
for (unsigned int i = 0; i < 512; i++) {
diff --git a/src/test/test_fifo/test_fifo.cpp b/src/test/test_fifo/test_fifo.cpp
index 852e26b03e..4af93803d7 100644
--- a/src/test/test_fifo/test_fifo.cpp
+++ b/src/test/test_fifo/test_fifo.cpp
@@ -5,15 +5,17 @@
using namespace std;
-FIFO f;
+const uint32_t fifoSize = 256;
+FIFO f;
+
void init()
{
f.flush();
- for(int i=0;i
+#include
#include
#include "msp.h"
#include "msptypes.h"
#include "mock_serial.h"
-#include "devCRSF.h"
-
-// Create a CRSF object to test
-CRSF crsf;
+#include "CRSF.h"
void test_encapsulated_msp_send(void)
{
// TEST CASE:
// GIVEN the CRSF class has been instantiated using a mock UART
- // WHEN the crsf.sendMSPFrameToFC() function is called with a valid mspPacket_t MSP command to change the VTX to F1
+ // WHEN the CRSF::sendMSPFrameToFC() function is called with a valid mspPacket_t MSP command to change the VTX to F1
// THEN the mspPacket_t will be transcoded into an embedded crsf msp packet
// AND the transcoded packet will be sent to the Stream object associated with the CRSF class
// Make sure no msp messages are in the fifo
- crsf.ResetMspQueue();
+ CRSF::ResetMspQueue();
// Build an MSP packet with the MSP_SET_VTX_CONFIG cmd
mspPacket_t packet;
@@ -31,11 +30,11 @@ void test_encapsulated_msp_send(void)
packet.addByte(0x00); // don't enable pitmode
// Ask the CRSF class to send the encapsulated packet to the stream
- crsf.AddMspMessage(&packet);
+ CRSF::AddMspMessage(&packet, CRSF_ADDRESS_FLIGHT_CONTROLLER);
uint8_t* data;
uint8_t len;
- crsf.GetMspMessage(&data, &len);
+ CRSF::GetMspMessage(&data, &len);
// Assert that the correct number of total bytes were sent to the stream
TEST_ASSERT_NOT_EQUAL(NULL, data);
@@ -62,12 +61,12 @@ void test_encapsulated_msp_send_too_long(void)
{
// TEST CASE:
// GIVEN the CRSF class has been instantiated using a mock UART
- // WHEN the crsf.sendMSPFrameToFC() function is called with a invalid mspPacket_t MSP command (payload too long)
+ // WHEN the CRSF::sendMSPFrameToFC() function is called with a invalid mspPacket_t MSP command (payload too long)
// THEN the mspPacket_t will NOT be transcoded into an embedded crsf msp packet
// AND nothing will be sent to the stream
// Make sure no msp messages are in the fifo
- crsf.ResetMspQueue();
+ CRSF::ResetMspQueue();
// Build an MSP packet with a payload that is too long to send (>4 bytes)
mspPacket_t packet;
@@ -82,11 +81,11 @@ void test_encapsulated_msp_send_too_long(void)
packet.addByte(0x05);
// Ask the CRSF class to send the encapsulated packet to the stream
- crsf.AddMspMessage(&packet);
+ CRSF::AddMspMessage(&packet, CRSF_ADDRESS_FLIGHT_CONTROLLER);
uint8_t* data;
uint8_t len;
- crsf.GetMspMessage(&data, &len);
+ CRSF::GetMspMessage(&data, &len);
// Assert that nothing was sent to the stream
TEST_ASSERT_EQUAL(NULL, data);
diff --git a/src/test/test_msp/mock_serial.h b/src/test/test_msp/mock_serial.h
index 970a12d586..9b3d085830 100644
--- a/src/test/test_msp/mock_serial.h
+++ b/src/test/test_msp/mock_serial.h
@@ -21,7 +21,7 @@ class StringStream : public Stream
{
buf += (char)c; return 1;
}
- size_t write(uint8_t *c, int l)
+ size_t write(const uint8_t *c, size_t l)
{
for (int i=0; i
+#include
#include
#include "msp.h"
#include "common.h"
diff --git a/src/test/test_msp_vtx/msp_vtx_tests.cpp b/src/test/test_msp_vtx/msp_vtx_tests.cpp
index 26adb68532..ca121f344f 100644
--- a/src/test/test_msp_vtx/msp_vtx_tests.cpp
+++ b/src/test/test_msp_vtx/msp_vtx_tests.cpp
@@ -1,9 +1,10 @@
#include
-#include
#include
+#include
#include "common.h"
#include "CRSF.h"
+#include "msptypes.h"
using namespace std;
diff --git a/src/test/test_ota/test_switches.cpp b/src/test/test_ota/test_switches.cpp
index b425f4c9d6..48632323f6 100644
--- a/src/test/test_ota/test_switches.cpp
+++ b/src/test/test_ota/test_switches.cpp
@@ -8,6 +8,8 @@
*/
+#include
+#include
#include
#include "targets.h"
@@ -273,7 +275,7 @@ void test_encodingHybridWide(bool highRes, uint8_t nonce)
}
// Uplink data
- crsf.LinkStatistics.uplink_TX_Power = 3; // 100mW
+ CRSF::LinkStatistics.uplink_TX_Power = 3; // 100mW
// Save the channels since they go into the same place
memcpy(ChannelsIn, ChannelData, sizeof(ChannelData));
@@ -304,7 +306,7 @@ void test_encodingHybridWide(bool highRes, uint8_t nonce)
// If slot 7, the uplink_TX_Power should be in the low 6 bits
if (switchIdx == 7)
- TEST_ASSERT_EQUAL(crsf.LinkStatistics.uplink_TX_Power, switches & 0b111111);
+ TEST_ASSERT_EQUAL(CRSF::LinkStatistics.uplink_TX_Power, switches & 0b111111);
else
{
uint16_t ch = ChannelData[5+switchIdx];
@@ -358,7 +360,7 @@ void test_decodingHybridWide(bool highRes, uint8_t nonce, uint8_t forceSwitch, u
}
// Uplink data
- crsf.LinkStatistics.uplink_TX_Power = 3; // 100mW
+ CRSF::LinkStatistics.uplink_TX_Power = 3; // 100mW
// Save the channels since they go into the same place
memcpy(ChannelsIn, ChannelData, sizeof(ChannelData));
@@ -369,7 +371,7 @@ void test_decodingHybridWide(bool highRes, uint8_t nonce, uint8_t forceSwitch, u
OtaPackChannelData(otaPktPtr, ChannelData, nonce % 2, tlmDenom);
// Clear the LinkStatistics to receive it from the encoding
- crsf.LinkStatistics.uplink_TX_Power = 0;
+ CRSF::LinkStatistics.uplink_TX_Power = 0;
// run the decoder, results in crsf->PackedRCdataOut
bool telemResult = OtaUnpackChannelData(otaPktPtr, ChannelData, tlmDenom);
@@ -390,7 +392,7 @@ void test_decodingHybridWide(bool highRes, uint8_t nonce, uint8_t forceSwitch, u
if (switchIdx == 7)
{
- TEST_ASSERT_EQUAL(crsf.LinkStatistics.uplink_TX_Power, 3);
+ TEST_ASSERT_EQUAL(CRSF::LinkStatistics.uplink_TX_Power, 3);
}
else
{
@@ -440,12 +442,12 @@ void test_encodingFullresPowerLevels()
// This is what we're testing here, just the power
uint8_t crsfPower = powerToCrsfPower((PowerLevels_e)pwr);
- crsf.LinkStatistics.uplink_TX_Power = crsfPower;
+ CRSF::LinkStatistics.uplink_TX_Power = crsfPower;
OtaPackChannelData(otaPktPtr, ChannelData, false, 0);
OtaUnpackChannelData(otaPktPtr, ChannelData, 0);
- TEST_ASSERT_EQUAL(crsfPower, crsf.LinkStatistics.uplink_TX_Power);
+ TEST_ASSERT_EQUAL(crsfPower, CRSF::LinkStatistics.uplink_TX_Power);
}
}
@@ -457,7 +459,7 @@ void test_encodingFullres8ch()
TEST_ASSERT_EQUAL(sizeof(ChannelData), sizeof(ChannelsIn));
fullres_fillChannelData();
- crsf.LinkStatistics.uplink_TX_Power = PWR_250mW;
+ CRSF::LinkStatistics.uplink_TX_Power = PWR_250mW;
// Save the channels since they go into the same place
memcpy(ChannelsIn, ChannelData, sizeof(ChannelData));
diff --git a/src/test/test_stubborn/test_stubborn.cpp b/src/test/test_stubborn/test_stubborn.cpp
index 22ce32a324..d5fb174e9e 100644
--- a/src/test/test_stubborn/test_stubborn.cpp
+++ b/src/test/test_stubborn/test_stubborn.cpp
@@ -1,10 +1,10 @@
#include
+#include
+#include
#include
#include
#include
#include
-#include
-#include
#include "targets.h"
#include "helpers.h"
diff --git a/src/test/test_telemetry/test_telemetry.cpp b/src/test/test_telemetry/test_telemetry.cpp
index dca8bee7fe..656f49d2a2 100644
--- a/src/test/test_telemetry/test_telemetry.cpp
+++ b/src/test/test_telemetry/test_telemetry.cpp
@@ -2,6 +2,8 @@
#include
#include
+#include "common.h"
+
Telemetry telemetry;
uint32_t ChannelData[CRSF_NUM_CHANNELS]; // Current state of channels, CRSF format
diff --git a/src/user_defines.txt b/src/user_defines.txt
index 8f64744e6a..bf545bc466 100644
--- a/src/user_defines.txt
+++ b/src/user_defines.txt
@@ -22,6 +22,8 @@
#-DRegulatory_Domain_IN_866
#-DRegulatory_Domain_AU_433
#-DRegulatory_Domain_EU_433
+#-DRegulatory_Domain_US_433
+#-DRegulatory_Domain_US_433_WIDE
#-DRegulatory_Domain_FCC_915
#-DRegulatory_Domain_ISM_2400
#-DRegulatory_Domain_EU_CE_2400
@@ -41,11 +43,8 @@
### COMPATIBILITY OPTIONS: ###
-# Invert the TX/RX half-duplex pin in the transmitter code. Handset external
-# modules are almost all inverted. ESP32/8286 targets only
--DUART_INVERTED
-
# Use a custom baud rate on the receiver for a KISS v1 FC (which runs at 400000) or any other oddball baud
+# Changing this will prevent flashing via_BetaflightPassthrough, which expects a 420000 baud RX
# If not defined, will default to 420000
#-DRCVR_UART_BAUD=400000
@@ -84,6 +83,12 @@
# Turn on debug messages, if disabled then all debugging options (starting with DEBUG_) are disabled
#-DDEBUG_LOG
+
+# For an ESP32S3 MCU, there are two options:
+# 1 - Pull-up on GPIO3, USB for JTAG (on MI_02), debug logging uses main UART.
+# 2 - Pull-down on GPIO3, Individual pins for JTAG (MTDI/MTMS/MTCK/MTDO), debug logging via USB for CDC UART (on MI_00)
+#-DESP32_S3_USB_JTAG_ENABLED
+
# Use DEBUG_LOG_VERBOSE instead (or both) to see verbose debug logging (spammy stuff)
#-DDEBUG_LOG_VERBOSE