From 295abb37ee20175c23aad0969cf94b74e91e5f4c Mon Sep 17 00:00:00 2001 From: Scott Penrose Date: Thu, 7 May 2026 16:00:21 +1000 Subject: [PATCH] Initial public release of WII5 Buoy firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Firmware for an autonomous wave-measurement buoy (ATmega2560-based WII5 v2 board). Reads wave motion from a Sparton AHRS-M1/M2 IMU, samples GPS and battery state, and reports back over Iridium SBD satellite telemetry. Originally developed 2012-2024. This is the first public release. Code, documentation, and field-tested operating modes (Capture, Sleep, Position, ManualTest, SelfTest, LowBattery) are licensed under Apache 2.0 — see LICENSE and NOTICE. See README.md for an overview and build instructions, CONTRIBUTING.md for how to contribute, and DEPLOYMENTS.md for the field-deployment log. --- .github/ISSUE_TEMPLATE/bug_report.md | 45 + .github/ISSUE_TEMPLATE/feature_request.md | 25 + .github/PULL_REQUEST_TEMPLATE.md | 31 + .gitignore | 29 + CHANGELOG | 30 + CODE_OF_CONDUCT.md | 24 + CONTRIBUTING.md | 90 + DEPLOYMENTS.md | 37 + Doxyfile | 78 + LICENSE | 202 + NOTICE | 11 + README.md | 129 + SECURITY.md | 46 + VERSION | 2 + WII5.cpp | 89 + WII5.h | 221 + WII5Battery.cpp | 199 + WII5Battery.h | 63 + WII5BinData.cpp | 633 + WII5BinData.h | 239 + WII5Commands.cpp | 1504 ++ WII5Commands.h | 114 + WII5Communications.cpp | 657 + WII5Communications.h | 136 + WII5Config.cpp | 358 + WII5Config.h | 140 + WII5Controller.cpp | 521 + WII5Controller.h | 136 + WII5Data.h | 506 + WII5DataShared.h | 146 + WII5Display.cpp | 339 + WII5Display.h | 46 + WII5GPS.cpp | 394 + WII5GPS.h | 107 + WII5Help.cpp | 237 + WII5Help.h | 44 + WII5Iridium.cpp | 851 + WII5Iridium.h | 265 + WII5Maths.cpp | 588 + WII5Maths.h | 119 + WII5Mode.cpp | 29 + WII5Mode.h | 41 + WII5ModeCapture.cpp | 564 + WII5ModeCapture.h | 102 + WII5ModeLowBattery.cpp | 142 + WII5ModeLowBattery.h | 67 + WII5ModeManualTest.cpp | 1757 ++ WII5ModeManualTest.h | 56 + WII5ModePosition.cpp | 172 + WII5ModePosition.h | 68 + WII5ModeSelfTest.cpp | 651 + WII5ModeSelfTest.h | 101 + WII5ModeSleep.cpp | 263 + WII5ModeSleep.h | 60 + WII5Power.cpp | 161 + WII5Power.h | 59 + WII5RTC.cpp | 147 + WII5RTC.h | 33 + WII5RadioLoRa.cpp | 82 + WII5RadioLoRa.h | 44 + WII5SerialDebug.cpp | 17 + WII5SerialDebug.h | 20 + WII5SerialManager.cpp | 553 + WII5SerialManager.h | 162 + WII5SerialMaths.cpp | 17 + WII5SerialMaths.h | 20 + WII5SerialStatus.cpp | 17 + WII5SerialStatus.h | 20 + WII5Setup.cpp | 449 + WII5Setup.h | 42 + WII5Sh3dConfig.cpp | 142 + WII5Sh3dConfig.h | 127 + WII5Sh3dConsole.cpp | 658 + WII5Sh3dConsole.h | 302 + WII5Sh3dIO.cpp | 327 + WII5Sh3dIO.h | 225 + WII5Sh3dUtil.cpp | 139 + WII5Sh3dUtil.h | 44 + WII5Sparton.cpp | 1076 ++ WII5Sparton.h | 286 + WII5Strings.cpp | 788 + WII5Strings.h | 56 + WII5Weather_18B20.cpp | 191 + WII5Weather_18B20.h | 42 + WII5_board.h | 49 + WII5_board_v2.h | 439 + app/wii5_buoy/wii5_buoy.ino | 40 + doc/EXTERNAL_REFERENCES.md | 45 + doc/MODES.txt | 218 + doc/PROCESSED.txt | 448 + doc/SPARTON.txt | 39 + test/wii5_bindata/wii5_bindata.ino | 84 + test/wii5_commands/wii5_commands.ino | 22 + test/wii5_gps/wii5_gps.ino | 102 + test/wii5_iridium/wii5_iridium.ino | 55 + test/wii5_pins/wii5_pins.ino | 179 + test/wii5_sparton/wii5_sparton.ino | 64 + .../wii5_weather_18b20/wii5_weather_18b20.ino | 25 + tools/2022 | 13 + tools/2024/11mhz.sh | 26 + tools/2024/16mhz.sh | 26 + tools/2024/Arduino.tar.gz | Bin 0 -> 647741 bytes tools/2024/avrdude.conf | 15874 ++++++++++++++++ tools/2024/bootonly.sh | 18 + tools/2024/ui.txt | 7 + tools/2024/upload.sh | 26 + tools/2024/wii5.sh | 27 + tools/bits/bits.c | 30 + tools/build_local.sh | 35 + tools/build_test.sh | 32 + tools/build_upload.sh | 22 + tools/build_version.sh | 67 + tools/copy_libs.sh | 74 + tools/icsp.sh | 30 + tools/integer_version.pl | 37 + tools/memory.pl | 111 + tools/pull.sh | 19 + tools/tag_version.sh | 42 + tools/updatelocaldev.sh | 21 + tools/upload.sh | 17 + tools/variables.sh | 14 + tools/verify.sh | 15 + 122 files changed, 38142 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 CHANGELOG create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 DEPLOYMENTS.md create mode 100644 Doxyfile create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 VERSION create mode 100644 WII5.cpp create mode 100644 WII5.h create mode 100644 WII5Battery.cpp create mode 100644 WII5Battery.h create mode 100644 WII5BinData.cpp create mode 100644 WII5BinData.h create mode 100644 WII5Commands.cpp create mode 100644 WII5Commands.h create mode 100644 WII5Communications.cpp create mode 100644 WII5Communications.h create mode 100644 WII5Config.cpp create mode 100644 WII5Config.h create mode 100644 WII5Controller.cpp create mode 100644 WII5Controller.h create mode 100644 WII5Data.h create mode 100644 WII5DataShared.h create mode 100644 WII5Display.cpp create mode 100644 WII5Display.h create mode 100644 WII5GPS.cpp create mode 100644 WII5GPS.h create mode 100644 WII5Help.cpp create mode 100644 WII5Help.h create mode 100644 WII5Iridium.cpp create mode 100644 WII5Iridium.h create mode 100644 WII5Maths.cpp create mode 100644 WII5Maths.h create mode 100644 WII5Mode.cpp create mode 100644 WII5Mode.h create mode 100644 WII5ModeCapture.cpp create mode 100644 WII5ModeCapture.h create mode 100644 WII5ModeLowBattery.cpp create mode 100644 WII5ModeLowBattery.h create mode 100644 WII5ModeManualTest.cpp create mode 100644 WII5ModeManualTest.h create mode 100644 WII5ModePosition.cpp create mode 100644 WII5ModePosition.h create mode 100644 WII5ModeSelfTest.cpp create mode 100644 WII5ModeSelfTest.h create mode 100644 WII5ModeSleep.cpp create mode 100644 WII5ModeSleep.h create mode 100644 WII5Power.cpp create mode 100644 WII5Power.h create mode 100644 WII5RTC.cpp create mode 100644 WII5RTC.h create mode 100644 WII5RadioLoRa.cpp create mode 100644 WII5RadioLoRa.h create mode 100644 WII5SerialDebug.cpp create mode 100644 WII5SerialDebug.h create mode 100644 WII5SerialManager.cpp create mode 100644 WII5SerialManager.h create mode 100644 WII5SerialMaths.cpp create mode 100644 WII5SerialMaths.h create mode 100644 WII5SerialStatus.cpp create mode 100644 WII5SerialStatus.h create mode 100644 WII5Setup.cpp create mode 100644 WII5Setup.h create mode 100644 WII5Sh3dConfig.cpp create mode 100644 WII5Sh3dConfig.h create mode 100644 WII5Sh3dConsole.cpp create mode 100644 WII5Sh3dConsole.h create mode 100644 WII5Sh3dIO.cpp create mode 100644 WII5Sh3dIO.h create mode 100644 WII5Sh3dUtil.cpp create mode 100644 WII5Sh3dUtil.h create mode 100644 WII5Sparton.cpp create mode 100644 WII5Sparton.h create mode 100644 WII5Strings.cpp create mode 100644 WII5Strings.h create mode 100644 WII5Weather_18B20.cpp create mode 100644 WII5Weather_18B20.h create mode 100644 WII5_board.h create mode 100644 WII5_board_v2.h create mode 100644 app/wii5_buoy/wii5_buoy.ino create mode 100644 doc/EXTERNAL_REFERENCES.md create mode 100644 doc/MODES.txt create mode 100644 doc/PROCESSED.txt create mode 100644 doc/SPARTON.txt create mode 100644 test/wii5_bindata/wii5_bindata.ino create mode 100644 test/wii5_commands/wii5_commands.ino create mode 100644 test/wii5_gps/wii5_gps.ino create mode 100644 test/wii5_iridium/wii5_iridium.ino create mode 100644 test/wii5_pins/wii5_pins.ino create mode 100644 test/wii5_sparton/wii5_sparton.ino create mode 100644 test/wii5_weather_18b20/wii5_weather_18b20.ino create mode 100644 tools/2022 create mode 100755 tools/2024/11mhz.sh create mode 100755 tools/2024/16mhz.sh create mode 100644 tools/2024/Arduino.tar.gz create mode 100644 tools/2024/avrdude.conf create mode 100755 tools/2024/bootonly.sh create mode 100644 tools/2024/ui.txt create mode 100755 tools/2024/upload.sh create mode 100755 tools/2024/wii5.sh create mode 100644 tools/bits/bits.c create mode 100755 tools/build_local.sh create mode 100755 tools/build_test.sh create mode 100755 tools/build_upload.sh create mode 100755 tools/build_version.sh create mode 100755 tools/copy_libs.sh create mode 100644 tools/icsp.sh create mode 100644 tools/integer_version.pl create mode 100644 tools/memory.pl create mode 100755 tools/pull.sh create mode 100755 tools/tag_version.sh create mode 100755 tools/updatelocaldev.sh create mode 100755 tools/upload.sh create mode 100755 tools/variables.sh create mode 100755 tools/verify.sh diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f11b463 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,45 @@ +--- +name: Bug report +about: Something is broken or behaving unexpectedly +title: '' +labels: bug +assignees: '' +--- + +## Summary + + + +## Firmware version + + + +## Hardware + +- Board: +- Sensors / radios involved: + +## Mode and configuration + + + +## Steps to reproduce + +1. +2. +3. + +## Expected behaviour + +## Actual behaviour + + + +``` + +``` + +## Additional context diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..f175afa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,25 @@ +--- +name: Feature request +about: Suggest an enhancement or new capability +title: '' +labels: enhancement +assignees: '' +--- + +## What problem are you trying to solve? + + + +## Proposed solution + + + +## Constraints / hardware impact + + + +## Alternatives you've considered + +## Additional context diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4173725 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ +## Summary + + + +## Linked issue + + + +## Testing performed + +- [ ] Compiles for `WII:avr:wii5_v2_2560_3V` +- [ ] Tested on real hardware +- [ ] Tested in mode(s): +- [ ] Flash / RAM usage (`Sketch uses ...` line) was checked and is + acceptable + + + +## Notes + +## Pre-submission checklist + +- [ ] I have read `CONTRIBUTING.md` +- [ ] My commits are signed off (DCO; `git commit -s`) +- [ ] No internal infrastructure references introduced (no real IPs, + hostnames, IMEIs, GPS coords, personal paths, or emails — see + `SECURITY.md`) +- [ ] Comments are accurate and use `TODO`/`FIXME` (not `XXX`) +- [ ] Any new `Serial.print` calls are inside `#ifdef WII5_DEBUG_*` blocks + or use the `console` abstraction diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83af328 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Editor / OS junk +.DS_Store +Thumbs.db +*.swp +*.swo +.idea/ +.vscode/ + +# Build artifacts +a.out +*.gch +*.o +*.a +*.elf +*.eep +*.hex +build/ + +# Tool / contributor-local config +.pio +logs +tools/upload.local.sh +tools/*.local.sh + +# Doxygen output +doc/api/ + +# Personal scratch notes +SCOTT.md diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..3c4675b --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,30 @@ +v5.x - 2024 +* TODO + +v1.x +* New BinData for sending/receiving binary data on the Iridium +* Simplified and improved the button logic. +* New logging level controls +* GPS Debug and Passthrough in any mode +* Better beep for Maths Start +* Rugged Position Mode +* Calculate time until next... + +v1.0.20 +* Battery And Temperature encouraged to read near boot timed +* Hold/Click button cycle Improved +* Power for Buzzer +* Hold feedback messages for Displaying shutdown time of Maths +* Sleep period configurable in EEPROM +* Split non-essential loop out for Maths +* Mode timeouts +* Direct maths Hold command +* Fully working Seep Mode + +v1.0.16 +* statusDump - automatically shown at Voltage and Weather to allow Local Server updates +* Using @WII5,setting,mode,default,{STRING} has worked for a while, added "swap" which switches + to sleep if it was capture, otherwise to capture. +* Updated Test help to remove incorrect documentation. +* Large number of experimental changes in Maths to facilitate start/stop/reboot + diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..b39f61d --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,24 @@ +# Code of Conduct + +This project adopts the **Contributor Covenant, version 2.1**, as its code +of conduct. The full text is published at: + + https://www.contributor-covenant.org/version/2/1/code_of_conduct/ + +In short: be respectful, be constructive, focus on the technical work. +Harassment, personal attacks, and discriminatory language are not welcome +in issues, pull requests, commit messages, code comments, or any other +project space. + +## Reporting + +If you experience or witness behaviour that violates this code, please +report it to the maintainers privately via email (see the contact in the +project README), or via a private GitHub message to a maintainer. Reports +will be handled confidentially and in good faith. + +## Scope + +This code applies to all project spaces — issues, pull requests, the code +itself, and any related communication channels. It also applies when an +individual is officially representing the project in public spaces. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b2bd6be --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,90 @@ +# Contributing to WII5 Buoy Firmware + +Thanks for your interest in contributing! This is firmware for an +Arduino/AVR-based wave-measurement buoy. Most contributors will be people +porting it to new hardware, fixing bugs found in the field, or adding sensor +support. The notes below are aimed at making PRs reviewable and avoiding the +classes of mistakes that have bitten us in the past. + +## How to discuss before coding + +For anything bigger than a typo or a small bug fix, **open an issue first** +to discuss the approach. Embedded firmware is full of subtle hardware +constraints (program-memory budget, real-time deadlines, ISR safety) and a +short up-front conversation usually saves a long review. + +## Building locally + +This is an Arduino IDE / `arduino-cli` project targeting the +ATmega2560 on a custom WII5 v2 board (see `WII5_board_v2.h`). + +The build scripts in `tools/` are parameterized via env vars; copy +`tools/upload.sh` to `tools/upload.local.sh` (gitignored) and edit, or +export `WII5_BUILD_DIR`, `WII5_AVRDUDE_CONF`, `WII5_REMOTE`, etc. as needed. +See each script's preamble for the env vars it expects. + +## Submitting changes + +1. Fork the repository, create a topic branch off `main`. +2. Keep commits small and reviewable. One logical change per commit. +3. Use clear commit messages. The first line is a short summary + (`: `), followed by a blank line and the rationale + in prose if non-obvious. +4. Sign off your commits to certify the + [Developer Certificate of Origin](https://developercertificate.org/): + ``` + git commit -s + ``` +5. Verify the sketch still compiles for `WII:avr:wii5_v2_2560_3V` before + pushing. +6. Open a pull request. The PR template will prompt you for the things + reviewers care about (testing performed, hardware impact, etc.). + +## Code conventions + +- **No internal infrastructure references.** Don't introduce hardcoded + hostnames, IP addresses, IMEIs, GPS coordinates, personal email addresses, + or developer-specific paths. Use env vars in scripts and `#define`s in + `WII5_board_*.h` for hardware-specific values. See `SECURITY.md`. +- **Don't break the flash budget.** The ATmega2560 has 256 KB of program + memory; we already use a meaningful fraction. Watch the `Sketch uses ...` + line at the end of `arduino-cli compile`. New features that cost more than + ~2 KB should mention the cost in the PR description. +- **Use the `console` abstraction**, not raw `Serial.print`. The console + routes output to whatever serial port the board is configured for, and + supports `LOG_DEBUG` / `LOG_INFO` / `LOG_WARN` / `LOG_ERROR` / `LOG_FATAL` + log levels. Raw `Serial.print` should only appear inside `#ifdef + WII5_DEBUG_*` blocks. +- **Use `F()` / `PSTR()` for string literals** to keep them in flash, not RAM. +- **TODO/FIXME convention.** `// TODO: ...` for known gaps, `// FIXME: ...` + for known-broken things that need attention. We previously used `XXX` + internally; new code should use `TODO`/`FIXME`. +- **Keep comments accurate.** A comment that contradicts the code is worse + than no comment. + +## Adding a new sensor / driver + +Subclass `WII5Power` (gives you `powerOn()` / `powerOff()` / `running` state). +Implement `controllerId()` and `driverId()` to return the appropriate +enumerators in `WII5Data.h`. Provide `begin()`, `loop()`, `start()`, `stop()`. +Wire it into `WII5Setup.cpp`'s `begin()` and `WII5Controller.cpp`'s +`loopOther()`. Look at `WII5GPS`, `WII5Battery`, or `WII5Weather_18B20` for +small worked examples. + +## Testing + +There is no automated test harness for the firmware itself — testing is done +by flashing onto real hardware and exercising the `wii5_buoy` and +`wii5_modes` sketches. The `test/` directory contains smaller sketches +demonstrating individual subsystems; please add a sketch there if you add a +new driver. + +In your PR, describe: +- What you tested on (hardware or just compile?) +- Which mode(s) you exercised (Capture, Position, Sleep, ManualTest, etc.) +- Any known gaps (e.g. "didn't test on a battery-only run"). + +## License + +By contributing, you agree that your contributions will be licensed under +the Apache License 2.0 (see `LICENSE`). diff --git a/DEPLOYMENTS.md b/DEPLOYMENTS.md new file mode 100644 index 0000000..90be141 --- /dev/null +++ b/DEPLOYMENTS.md @@ -0,0 +1,37 @@ +# Field Deployments + +A running log of notable field deployments of the WII5 buoy firmware. +This file is for crediting partners, recording lessons learned, and +documenting which firmware version went out the door for each campaign. + +Add a new section per deployment. Keep them short — link out to papers, +mission reports, or partner sites for the full story. + +--- + +## Template + +### YYYY — Deployment name + +- **Partner / institution:** ... +- **Location:** ... (broad region; do not record exact buoy coordinates here) +- **Firmware version:** `vX.Y.Z` (commit ``) +- **Hardware:** WII5 v2 board, Sparton AHRS-M*, Iridium 9602 / 9603, etc. +- **Mode profile:** Capture / Position / Sleep schedule used in the field +- **Notes:** field issues observed, fixes that came out of the campaign, + links to publications. + +--- + + diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..d6920b5 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,78 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# Doxygen configuration for the WII5 Buoy firmware. +# +# This is a minimal Doxyfile listing only the non-default options. Run +# `doxygen Doxyfile` from the repo root; output lands in doc/api/ (which +# is gitignored). Doxygen >= 1.9 is recommended. + +# --- Project identity -------------------------------------------------------- +PROJECT_NAME = "WII5 Buoy Firmware" +PROJECT_BRIEF = "Firmware for the WII5 wave-measurement buoy (ATmega2560)." +# PROJECT_NUMBER is normally injected by the build script: +# doxygen <(sed "s/^PROJECT_NUMBER.*/PROJECT_NUMBER = $(cat VERSION | grep version | cut -d= -f2)/" Doxyfile) +# Hardcoded fallback follows. +PROJECT_NUMBER = v5.5.x + +# --- Inputs ------------------------------------------------------------------ +INPUT = . app/wii5_buoy +RECURSIVE = YES +FILE_PATTERNS = *.h *.cpp *.ino +EXCLUDE = doc test tools .git build .github +EXCLUDE_PATTERNS = */legacy/* */experimental/* + +# --- Output ------------------------------------------------------------------ +OUTPUT_DIRECTORY = doc/api +GENERATE_HTML = YES +GENERATE_LATEX = NO +GENERATE_XML = YES +HTML_OUTPUT = . + +# --- Source extraction ------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_ANON_NSPACES = NO + +# Recognise Javadoc-style /** ... */ as the brief if it's a single line. +JAVADOC_AUTOBRIEF = YES +QT_AUTOBRIEF = NO + +# Source browser is useful for an exploratory codebase. +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES + +# Sort members the way they appear in the source. +SORT_MEMBER_DOCS = NO +SORT_BRIEF_DOCS = NO + +# --- Warnings ---------------------------------------------------------------- +QUIET = YES +WARN_IF_UNDOCUMENTED = NO # Tier-1 docs only; flip to YES once Tier-2 lands +WARN_IF_DOC_ERROR = YES +WARN_AS_ERROR = NO + +# --- Preprocessor (so #ifdef'd code shows up correctly) ---------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = YES +EXPAND_ONLY_PREDEF = YES +PREDEFINED = __WII5_V02__ \ + WII5_COMMS_IRIDIUM \ + WII5_GPS \ + WII5_IMU_SPARTON \ + WII5_STORAGE_SDBLOCK \ + WII5_RTC \ + F(x)=x \ + PSTR(x)=x \ + PROGMEM= + +# --- Misc -------------------------------------------------------------------- +HAVE_DOT = NO +GENERATE_TREEVIEW = YES +DISABLE_INDEX = NO +FULL_SIDEBAR = NO +TAB_SIZE = 2 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..5c11bb9 --- /dev/null +++ b/NOTICE @@ -0,0 +1,11 @@ +WII5 Buoy Firmware +Copyright 2012-2024 Scott Penrose and WII5 Buoy contributors + +This product includes software developed by the WII5 Buoy contributors. + +Licensed under the Apache License, Version 2.0 (the "License"). +You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + +See the LICENSE file in this repository for the full license text. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d097af8 --- /dev/null +++ b/README.md @@ -0,0 +1,129 @@ +# WII5 Buoy Firmware + +Firmware for an autonomous wave-measurement buoy. Reads wave motion from a +Sparton AHRS, samples GPS and battery state, and reports back over Iridium +SBD satellite telemetry. Targets the ATmega2560 on a custom WII5 v2 board. + +> "WII5 = WII3 WDT + WII1 Buoy Combined" — the firmware merges the older +> WII3 watchdog/maths-CPU lineage with the WII1 buoy I/O lineage. Design +> notes and lineage live in `doc/`. + +## Overview + +The buoy runs in one of several modes: + +- **Capture** — IMU/wave-motion data capture and SD logging +- **Position** — periodic GPS position + Iridium telemetry only (low power) +- **Sleep** — long sleeps between wake-ups, Maths CPU off +- **Manual Test** — operator-driven hardware exercise via console +- **Self Test** — automated boot-time hardware checks +- **Low Battery** — degraded operation when batteries are low + +Mode is selected from EEPROM defaults, then can be overridden by Iridium +commands, the local console, or battery-management state. See +`doc/MODES.txt` and `WII5Controller.cpp::calculateCurrentMode()`. + +## Hardware + +- **Main board:** WII5 v2 (custom AVR carrier) +- **MCU:** ATmega2560 @ 11.0592 MHz, 256 KB flash, 8 KB RAM, 4 KB EEPROM +- **IMU / wave sensor:** Sparton AHRS-M1 or AHRS-M2 (NorthTek-programmable) +- **Sat radio:** Iridium 9602 / 9603 SBD +- **GPS:** u-blox NEO-6M / NEO-7M (NMEA, parsed via TinyGPS++) +- **Storage:** dual SD cards (one active, one reserve) +- **Sensors:** Dallas DS18B20 temperature, battery voltage analog inputs + +External vendor documentation is not redistributed in this repo; see +`doc/EXTERNAL_REFERENCES.md` for links. + +## Build & flash + +This is an Arduino-IDE / `arduino-cli` project, not PlatformIO. + +```sh +# Verify the sketch compiles +arduino-cli compile \ + -b WII:avr:wii5_v2_2560_3V:cpu=atmega25603V3_11MHz230400 \ + app/wii5_buoy/wii5_buoy.ino + +# Or use the parameterized helper script +WII5_BUILD_DIR=~/Arduino/build \ +WII5_AVRDUDE_CONF=~/.arduino15/.../avrdude.conf \ +WII5_SERIAL=/dev/ttyUSB0 \ +WII5_FULL_HEX=~/Arduino/build/wii5_buoy.ino.hex \ + tools/build_local.sh +``` + +The `tools/` scripts are all parameterized via env vars — read each +script's preamble for the variables it expects. Copy any of them to +`*.local.sh` (gitignored) if you want to set defaults locally. + +Library dependencies (install via Arduino Library Manager or +`arduino-cli lib install`): TimeLib, RTClib, elapsedMillis, TinyGPSPlus, +AvgStd, IridiumSBD, OneWire, DallasTemperature, SDBlock, CRC32, +PushButton, MemoryFree, LowPower, RadioHead. + +## Architecture + +Each subsystem is a class deriving from `WII5` (base) or `WII5Power` +(power-managed peripherals). Singleton instances hang off the global +namespace (`wii5Controller`, `wii5Iridium`, `wii5Sparton`, etc.) and are +called from `WII5Controller::loop()` according to the active mode. + +Top-level entry points: + +- `app/wii5_buoy/wii5_buoy.ino` — `setup()` / `loop()` +- `WII5Setup.cpp` — initial hardware bring-up +- `WII5Controller.cpp` — main loop, mode dispatch, watchdog +- `WII5Commands.cpp` — text-protocol command handler (console + Iridium) + +## LED status codes + +LEDs are the field-diagnostic language; preserve their meanings. + +| Mode / state | LED 1 | Notes | +| --- | --- | --- | +| Capture (default after 5 min) | `LED_SHORT` | | +| Capture, waiting | `LED_SLOW` | | +| Capture, error | `LED_FAST` | | +| Capture, just started | `LED_TRIPPLE` | reverts to `LED_SHORT` after 5 min | +| Sleep | `LED_DOUBLE` | | +| Low Battery | `LED_TRIPPLE_GAP` | | +| Position | `LED_SHORT` | (same as Capture default — disambiguate by mode) | +| Off / asleep / broken | `LED_OFF` | not a "safe" indicator | +| On (no blink) | `LED_ON` | not safe — may have crashed | +| Idle / good | `LED_SLOW` | | + +## Field deployments + +A running log of notable real-world deployments lives in `DEPLOYMENTS.md`. + +## Repository layout + +``` +app/wii5_buoy/ Arduino sketch entry point +WII5*.{h,cpp} Firmware source (board root, see Architecture) +WII5_board_v2.h Pin/peripheral map for the WII5 v2 board +doc/ Design notes, command reference, hardware schematics +test/ Smaller per-subsystem test sketches +tools/ Build, flash, and version helper scripts (env-var + driven; copy to *.local.sh to set local defaults) +``` + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md). Please also read +[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and [SECURITY.md](SECURITY.md) +before opening issues or PRs. + +## Contact + +- Maintainer: Scott Penrose <scottp@dd.com.au> +- Bugs and feature requests: GitHub Issues +- Security issues: see [SECURITY.md](SECURITY.md) + +## License + +Apache License 2.0 — see [LICENSE](LICENSE) and [NOTICE](NOTICE). + +Copyright 2012-2024 Scott Penrose and WII5 Buoy contributors. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..4227caa --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,46 @@ +# Security Policy + +## Reporting a vulnerability + +If you discover a security issue in the WII5 Buoy firmware — for example, +a flaw in the Iridium SBD command-handling path, an authentication bypass +in the console protocol, or anything else that could let an attacker take +control of a deployed buoy — **please do not open a public GitHub issue**. + +Instead, report it privately: + +- Open a private security advisory via the GitHub repository's + **Security** tab → "Report a vulnerability", or +- Email the maintainer: Scott Penrose <scottp@dd.com.au> + +Please include: + +- A description of the issue and its potential impact +- Steps to reproduce, or a proof-of-concept +- The affected firmware version (`WII5_SOFTWARE_VERSION`) and hardware + variant if known + +We will acknowledge receipt within a reasonable time, work with you on a +fix, and coordinate disclosure. + +## What this project asks of contributors + +When opening issues or pull requests, **please do not include**: + +- Internal hostnames, IP addresses, or network paths from operational + deployments +- Iridium IMEIs, modem serial numbers, or device identifiers from real + deployments +- GPS coordinates of operational deployment sites +- Other contributors' personal information (emails, real names, paths) — + unless they have given explicit permission + +If you need example values to demonstrate a problem, use obviously-fake +placeholders (e.g. `192.0.2.1` from RFC 5737, IMEI `300000000000000`, +generic lat/lng like `0,0`). + +## Supported versions + +Only the latest tagged release on `main` is actively supported. Older +deployed firmware may continue to function in the field but does not +receive backported fixes. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..dcd6700 --- /dev/null +++ b/VERSION @@ -0,0 +1,2 @@ +version=v5.5.1 +short=c0b356d diff --git a/WII5.cpp b/WII5.cpp new file mode 100644 index 0000000..f6b4604 --- /dev/null +++ b/WII5.cpp @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5.cpp + * @brief Top-level base class and global buffer declarations. + */ + +/* + +WII5 - Base class for all - not much used here now + +Main methods: + * debugDo ??? + * setError(id, string); - includes auto update of time etc +*/ + +#include +#include + +/* +BUFFERS: + The buffers are defined here to allow for central control of + space, and potential to reuse them. They should be statically allocated. + Need to do more work on shared buffers. Especially for serial ports. +*/ + +#ifdef WII5_BUFFER_STRING +char wii5BufferString[WII5_BUFFER_STRING]; +#endif + +#ifdef WII5_BUFFER_CONSOLE_BINARY +char wii5BufferConsoleBinary[WII5_BUFFER_CONSOLE_BINARY]; +#endif + +#ifdef WII5_BUFFER_CONSOLE_PRINT +char wii5BufferConsolePrint[WII5_BUFFER_CONSOLE_PRINT]; +#endif + +#ifdef WII5_BUFFER_CONSOLE_CMD +char wii5BufferConsoleCmd[WII5_BUFFER_CONSOLE_CMD]; +#endif + +#ifdef WII5_RADIO_LORA +char wii5BufferRadio[WII5_BUFFER_RADIO]; +#endif + +#ifdef WII5_COMMS_IRIDIUM +char wii5BufferIridium[WII5_BUFFER_IRIDIUM]; // Used for Serial In / Out +char wii5BinaryIridium[WII5_IRIDIUM_BIN_MAX]; // Used to send and receive data to the modem +#endif +#ifdef WII5_BUFFER_SPARTON +char wii5BufferSparton[WII5_BUFFER_SPARTON]; +#endif +#ifdef WII5_BUFFER_GPS +char wii5BufferGPS[WII5_BUFFER_GPS]; +#endif + +#ifdef WII5_RADIO_LORA +RH_RF95 wii5RadioRF95(RADIO_CS); +#endif + +void debugDo(uint32_t n) { + static uint32_t count = 0; + count++; + SerialConsole.print(F("DEBUG POINT: ")); + SerialConsole.print(n); + SerialConsole.print(" "); + SerialConsole.println(count); + SerialConsole.flush(); +} + +// Simple reusable class to help with multiple boards +WII5_DRIVERS WII5::driverId() { + return WII5DRIVER_UNKNOWN; +} +WII5_CONTROLLERS WII5::controllerId() { + return WII5CONTROLLER_UNKNOWN; +} + +bool WII5::SJ(DdW when) { + if (when >= 1483511955) + return true; + else + return false; +} diff --git a/WII5.h b/WII5.h new file mode 100644 index 0000000..71c91f1 --- /dev/null +++ b/WII5.h @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5.h + * @brief Top-level base class and global buffer declarations. + */ + +#ifndef WII5_h +#define WII5_h + +#include +#include + +// Load the Board file - see board for type +#include +#include +#include +#include +#include + +#define WII5_TIMERLAPS 5 + +// Try bitRead +#define BitVal(data,y) ( (data>>y) & 1) /** Return Data.Y value **/ +#define SetBit(data,y) data |= (1 << y) /** Set Data.Y to 1 **/ +#define ClearBit(data,y) data &= ~(1 << y) /** Clear Data.Y to 0 **/ + +#ifdef WII5_BUFFER_STRING +extern char wii5BufferString[WII5_BUFFER_STRING]; +#endif + +#ifdef WII5_BUFFER_CONSOLE_BINARY +extern char wii5BufferConsoleBinary[WII5_BUFFER_CONSOLE_BINARY]; +#endif + +#ifdef WII5_BUFFER_CONSOLE_PRINT +extern char wii5BufferConsolePrint[WII5_BUFFER_CONSOLE_PRINT]; +#endif + +#ifdef WII5_BUFFER_CONSOLE_CMD +extern char wii5BufferConsoleCmd[WII5_BUFFER_CONSOLE_CMD]; +#endif + +#ifdef WII5_RADIO_LORA +extern char wii5BufferRadio[WII5_BUFFER_RADIO]; +#endif + +// Serial modules - (should have defines) +#ifdef WII5_COMMS_IRIDIUM +#define WII5_IRIDIUM_BIN_MAX 340 +extern char wii5BufferIridium[WII5_BUFFER_IRIDIUM]; // Used for Serial In / Out +extern char wii5BinaryIridium[WII5_IRIDIUM_BIN_MAX]; // Used to send and receive data to the modem +#endif + +#ifdef WII5_BUFFER_SPARTON +extern char wii5BufferSparton[WII5_BUFFER_SPARTON]; +#endif +#ifdef WII5_BUFFER_GPS +extern char wii5BufferGPS[WII5_BUFFER_GPS]; +#endif + +/** @brief Debug breadcrumb: print "DEBUG POINT: " to SerialConsole. */ +void debugDo(uint32_t n); + +/** + * @brief Common base class for WII5 drivers and controllers. + * + * Provides controllerId/driverId virtuals so subclasses identify themselves + * to logging and status helpers. Concrete subclasses include WII5Power + * (power-managed peripherals), WII5Mode (run-time modes), and the various + * sensor / radio drivers. + */ +class WII5 { + public: + WII5() { + // lastStatus = WII5STATUS_UNKNOWN; + // lastErrorCount = 0; + // lastErrorRepeat = 0; + // lastErrorStatus = WII5STATUS_UNKNOWN; + // lastError = WII5ERROR_UNKNOWN; + // lastErrorString[0] = '\0'; + } + + /** @brief Identify the controller category for logging/status. */ + virtual WII5_CONTROLLERS controllerId(); + /** @brief Identify the driver category for logging/status. */ + virtual WII5_DRIVERS driverId(); + + // void setStatus(WII5_STATUS s); + // void setErrorUnknown(); + // void setErrorNone(); + // void setError(WII5_ERRORS id, char *str); + // void setError(WII5_ERRORS id, const __FlashStringHelper *str); + // bool isError(); + + // Helpers to show values + // bool getStatusOff(); + // bool getStatusOn(); + // bool getStatusValid(); + + // virtual void displayStatus(); + // virtual void displayError(bool hideNone = false); + // virtual void displayStatusFull(bool suprressNewLine = false); + + // virtual void metadataPrint(File* fh); + + /** @brief Sanity check: is the given timestamp in a plausible epoch range? */ + virtual bool SJ(DdW when); + + /* + virtual void displaySTATS(); + void statsHighClear(); + void statsHighPointStart(); + void statsHighPointStop(); + void statsHighDisplay(); + */ + + protected: + // STATUS - Last Status + // elapsedMillis lastStatusAge; + // time_t lastStatusT; + // WII5_STATUS lastStatus; // at time of error + + // ERROR - Last Error + // elapsedMillis lastErrorAge; + // uint32_t lastErrorCount; // Count all errors... also consider repeats + // uint32_t lastErrorRepeat; // Count of repeats + // time_t lastErrorT; + // WII5_STATUS lastErrorStatus; // at time of error + // WII5_ERRORS lastError; // Error ID + // char lastErrorString[WII5_ERROR_STRING_MAX]; // Error string + + /* + // Stats - mostly automatic, but lap timers need manual input, see also displaySTATS + void statsTimerStart(); + uint8_t statsTimerLap(); + void statsTimerStop(); + elapsedMillis statsTimer; + uint32_t statsFinal; + uint32_t statsLap[WII5_TIMERLAPS]; + uint8_t statsLap_count; + + // High Speed stats loops used for some operations + uint32_t statsHigh_count; + uint32_t statsHigh_total; + elapsedMicros statsHigh_timer; + */ +}; + +#ifdef WII5_RADIO_LORA +// extern RH_RF95 wii5RadioRF95; +#endif + +// Mostly dependency stuff ! +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef WII5_RADIO_LORA +#include +#endif +#ifdef WII5_GPS +#include +#endif +#ifdef WII5_COMMS_IRIDIUM +#include +#endif +#include +#ifdef WII5_IMU_SPARTON +#include +#endif + +#ifdef WII5_STORAGE_SDBLOCK +#include +#endif + +#ifdef WII5_RTC +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Sh3d shared code +#include +#include +#include +#include + +// Data +#include + +// Usefu libs +#include + +// RTC +#ifdef WII5_RTC +#include +#include "RTClib.h" +#endif + +#endif diff --git a/WII5Battery.cpp b/WII5Battery.cpp new file mode 100644 index 0000000..f31ad6b --- /dev/null +++ b/WII5Battery.cpp @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Battery.cpp + * @brief Battery monitor: voltage sampling, running average, and threshold logic. + */ + +/* + +WII5Battery + +*/ + +#include +#include +#include +#include + +void WII5Battery::begin() { + // ??? powerSetPin(POWER_BATTERY_PIN, POWER_IMU_SERIAL_ON, POWER_BATTERY_PIN_OFF); + #ifdef BATTERY_1_VOLTS_PIN + pinMode(BATTERY_1_VOLTS_PIN, OUTPUT); + digitalWrite(BATTERY_1_VOLTS_PIN, LOW); + #endif + #ifdef BATTERY_1_VOLTS_ANALOG + pinMode(BATTERY_1_VOLTS_ANALOG, INPUT); + #endif + #ifdef BATTERY_2_VOLTS_ANALOG + pinMode(BATTERY_2_VOLTS_ANALOG, INPUT); + #endif + + // Set the age high... never got it. + age = WII5TIME_1WEEK * 1000; + + // Start by getting the voltage as early as possible + step = WII5BATTERY_ON; stepWait = 0; stepCount = 0; +}; + +bool WII5Battery::isRunning() { + return (step != WII5BATTERY_OFF); +} + +void WII5Battery::start(bool force) { + step = WII5BATTERY_ON; stepWait = 300000; stepCount = 0; +} +void WII5Battery::stop(bool force) { + step = WII5BATTERY_OFF; stepWait = 0; stepCount = 0; +} + +uint16_t WII5Battery::scale(uint16_t data, uint16_t d) { + return int(0 + (float)data * (float)((float)d / 1000)); +} + +uint32_t WII5Battery::scale(uint32_t data, uint32_t d) { + return (uint32_t)(0 + (float)data * (float)((float)d / 1000)); +} + +void WII5Battery::loop() { + // Check timeouts (e.g. how long blocked low voltage?) + + // Testing Voltage Mode + switch (step) { + case WII5BATTERY_OFF: + // TODO step counter + // TIMEOUT - Not applicable + // off(); + + // Hard coded every 5 minutes for now + if (stepWait > 300000) { + step = WII5BATTERY_ON; stepWait = 0; stepCount = 0; + } + break; + + case WII5BATTERY_ON: + digitalWrite(BATTERY_1_VOLTS_PIN, HIGH); + // on(); + avgBattery1.reset(); + avgBattery2.reset(); + step = WII5BATTERY_MEASURE; stepWait = 0; stepCount = 0; + break; + + case WII5BATTERY_MEASURE: + // How many should we measure? + if (stepCount > 100) { // TODO Hard coded? + step = WII5BATTERY_STORE; stepWait = 0; stepCount = 0; + } + else { + #ifdef BATTERY_1_VOLTS_ANALOG + // Should we scale n ow or later? + uint16_t in = analogRead(BATTERY_1_VOLTS_ANALOG); + avgBattery1.checkAndAddReading(scale((uint16_t) in, (uint16_t)BATTERY_1_VOLTS_MULT)); + #endif + #ifdef BATTERY_2_VOLTS_ANALOG + // avgBattery2.checkAndAddReading(scale((uint16_t) analogRead(BATTERY_2_VOLTS_ANALOG), (uint16_t)BATTERY_2_VOLTS_MULT); + #endif + } + break; + + case WII5BATTERY_STORE: + // TODO Where do we store this? + // - Current Metadata - sure - useful + // - SD card or SPIMemory - be nice? + // console.log(LOG_DEBUG, F("BATTERY: Storage not yet implemented")); + // TODO Check this is decivolts ! + // TODO can we use openLogs etc to write log to disk + // #ifdef BATTERY_2_VOLTS_ANALOG + //int(avgBattery2.getMean() * 100); + // #endif + value = int(avgBattery1.getMean()); + age = 0; + step = WII5BATTERY_ANALYSE; stepWait = 0; stepCount = 0; + break; + + case WII5BATTERY_ANALYSE: + if ( + (value < wii5Config.getBatteryLow()) + && (wii5Controller.getMode() != WII5MODE_LOWBATTERY) + ) { + console.log(LOG_INFO, F("Low Battery detected V=%d, Low=%d, Mid=%d"), + value, wii5Config.getBatteryLow(), wii5Config.getBatteryMid() + ); + wii5Controller.setMode(WII5MODE_LOWBATTERY); + } + + else if ( + (value > wii5Config.getBatteryMid()) + && (wii5Controller.getMode() == WII5MODE_LOWBATTERY) + ) { + console.log(LOG_INFO, F("Mid Battery detected V=%d, Low=%d, Mid=%d"), + value, wii5Config.getBatteryLow(), wii5Config.getBatteryMid() + ); + wii5Controller.setDefaultMode(); + } + + #ifdef BATTERY_1_VOLTS_ANALOG + wii5Display.atDataSend( + F("battery1"), + F("volts=%d,swing=%d,min=%d,max=%d"), + int(avgBattery1.getMean()), + int(avgBattery1.getStd()), + int(avgBattery1.getMin()), + int(avgBattery1.getMax()) + ); + #endif + #ifdef BATTERY_2_VOLTS_ANALOG + // TODO + console.printf(F("# battery: #=2 mean=%d +-%d min=%d max=%d\r\n"), + // avgAccelZ.getN(), + int(avgBattery2.getMean()), + int(avgBattery2.getStd()), + int(avgBattery2.getMin()), + int(avgBattery2.getMax()) + ); + #endif + + wii5Display.atCommandSend(F("status"), F("voltage=%ld"), + wii5Battery.value + ); + wii5Display.atCommandSend(F("status"), F("update")); + + /* + + RULES: + * Valid date tinme ? + * Check date time not too far future, and not in past + * Batter 2 - work out which to use (not this release) + * Confidition of battery - how long it has been on this average etc + if ( + // Low Battery + (TOO LOW) + // Disabled still + && (wii5Config.getDisableLowBattery() > now() + ) { + } + + */ + step = WII5BATTERY_FINISH; stepWait = 0; stepCount = 0; + break; + + case WII5BATTERY_FINISH: + digitalWrite(BATTERY_1_VOLTS_PIN, LOW); + step = WII5BATTERY_OFF; stepWait = 0; stepCount = 0; + break; + + default: + console.log(LOG_FATAL, F("BATTERY: Default step... step=%d"), step); + step = WII5BATTERY_OFF; stepWait = 0; stepCount = 0; + break; + } + + stepCount++; + return; +} + +WII5Battery wii5Battery; diff --git a/WII5Battery.h b/WII5Battery.h new file mode 100644 index 0000000..0148f1d --- /dev/null +++ b/WII5Battery.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Battery.h + * @brief Battery monitor: voltage sampling, running average, and threshold logic. + */ + +#ifndef WII5Battery_h +#define WII5Battery_h + +#include +#include +#include +#include +#include + +enum WII5BATTERY_STEPS { + WII5BATTERY_OFF, + WII5BATTERY_ON, + WII5BATTERY_MEASURE, + WII5BATTERY_STORE, + WII5BATTERY_ANALYSE, + WII5BATTERY_FINISH +}; + +class WII5Battery : public WII5Power { + public: + WII5Battery() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;} + virtual WII5_DRIVERS driverId() {return WII5DRIVER_BATTERY;} + + void begin(); + void loop(); + + void start(bool force = false); + void stop(bool force = false); + + bool isRunning(); + + elapsedMillis age; + int32_t value; + protected: + + WII5BATTERY_STEPS step; + elapsedMillis stepWait; + uint32_t stepCount; + + AvgStd avgBattery1; + AvgStd avgBattery2; + + uint16_t scale(uint16_t data, uint16_t d); + uint32_t scale(uint32_t data, uint32_t d); + + +}; + +extern WII5Battery wii5Battery; + +#endif diff --git a/WII5BinData.cpp b/WII5BinData.cpp new file mode 100644 index 0000000..136b86a --- /dev/null +++ b/WII5BinData.cpp @@ -0,0 +1,633 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5BinData.cpp + * @brief Binary message format for Iridium SBD: layout, packing, and split logic. + */ + +/* + +WII5BinData - Simple utitlity class to create and manage binary data for Iridium + +How to manage the type requested. If it needs splitting, how to do, store etc. + +NOTE: LSB 0 + +*/ + +#include + +void WII5BinData::begin() { +} + +// getSize - Literally the size of the object including the headers +uint16_t WII5BinData::getSize(uint32_t t) { + return sizeof(WII5_BINDATA_HEADER) + getSizeObj(t); +} + +uint16_t WII5BinData::getSizeOne(uint8_t b) { + switch(b) { + case 0: + return sizeof(WII5_BINDATA_0); break; + case 1: + return sizeof(WII5_BINDATA_1); break; + case 2: + return sizeof(WII5_BINDATA_2); break; + case 3: + return sizeof(WII5_BINDATA_3); break; + case 4: + return sizeof(WII5_BINDATA_4); break; + case 5: + return sizeof(WII5_BINDATA_5); break; + case 6: + return sizeof(WII5_BINDATA_6); break; + case 7: + return sizeof(WII5_BINDATA_7); break; + case 8: + return sizeof(WII5_BINDATA_8); break; + case 9: + return sizeof(WII5_BINDATA_9); break; + case 10: + return sizeof(WII5_BINDATA_10); break; + case 11: + return sizeof(WII5_BINDATA_11); break; + case 12: + return sizeof(WII5_BINDATA_12); break; + case 13: + return sizeof(WII5_BINDATA_13); break; + case 14: + return sizeof(WII5_BINDATA_14); break; + case 15: + return sizeof(WII5_BINDATA_15); break; + }; + + return 0; +} + +/* + Starting with a type of B01001011 + uint8_t nextBit = 0; + uint32_t outType = 0; + + uint32_t t = B01001011; + + uint8_t count = 0; + while (getSplit(t, 300, &nextBit, %outType)) { + console.printf(F("Splitting #=%d, original type=%lu, new type=%lu, size=%d, nextBit=%d\r\n") + count, t, outType, getSize(outType), int(nextBit) + ); + count++; + } + + There should be no overlapping bits at the end of this + +*/ +bool WII5BinData::getSplit(uint32_t in_t, uint16_t max_size, uint8_t* start_bit, uint32_t* out_t) { + uint16_t ret_size = 0; + uint16_t part_size = 0; + ret_size = sizeof(WII5_BINDATA_HEADER); + *out_t = 0; + + // TODO fix max bits + for (uint8_t b = *start_bit; b < 16; b++) { + if (BitVal(in_t, b)) { + part_size = getSizeOne(b); + + if ((ret_size + part_size) > max_size) { + // That is it, we can't do any more. + *start_bit = b; + // NOTE: This is an error condition + if (*out_t == 0) { + console.log(LOG_FATAL, F("BinData: Failed to split BinData to this size")); + return false; + } + else + return true; + } + else { + SetBit(*out_t, b); + ret_size += part_size; + } + } + } + + *start_bit = 16; + // Check if we have anything left + if (*out_t == 0) + return false; + else + return true; +} + +uint16_t WII5BinData::setBit(uint32_t t, uint8_t b) { + SetBit(t, b); + return t; +} + +// getSizeObj - The individual objects, added together but no header +uint16_t WII5BinData::getSizeObj(uint32_t t) { + uint16_t ret = 0; + for (uint8_t b = 0; b < 16; b++) { + if (BitVal(t, b)) + ret += getSizeOne(b); + } + return ret; +} + +// Create Data is called first - to initialize the Buffer +bool WII5BinData::createData(uint32_t t, void* buf, uint16_t maxSize, uint32_t recordCount) { + memset(buf, 0, maxSize); + WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)buf; + + // Set the Header + local->wii_binary_id = 60001; // Fixed ID for the old system - 60001 + local->bindata_type = t; // Which packet type is this. + local->bindata_packet = 0; // 0 means only? + + // NOTE: + local->deviceId = wii5Config.getDeviceId(); + // Custom recordCount (eg. we are sending something old, else current) + if (recordCount > 0) + local->recordCount = recordCount; + else + local->recordCount = wii5Config.getRecordCount(); + + /* + console.log(LOG_DEBUG, F("*** (1) binary id = %d, binary type = %lu, packet = %d, recordCount = %lu, deviceId = %lu"), + local->wii_binary_id, local->bindata_type, int(local->bindata_packet), local->recordCount, local->deviceId + ); + */ +} + +// setData - literlaly set the data +// - However, it pulls data live from devices, therefore you need next to get old data +// - setBlockMetadata - Use a block to set the Metadata values +// - setBlockResults - Set the block for the results values +bool WII5BinData::setData(uint32_t t, void* buf, uint16_t maxSize) { + char* ptr = (char*)buf; + + if (getSize(t) > maxSize) { + console.log(LOG_FATAL, F("BinData: Standard Data too big for buger provided. Try splitting.")); + return false; + } + + { + WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)ptr; + if ( (local->wii_binary_id != 60001) || (local->bindata_type != t) ) { + // TODO fix the %d here, should be unsigned. + console.log(LOG_DEBUG, F("*** (2) binary id = %d, binary type = %lu, packet = %d, recordCount = %lu, deviceId = %lu"), + local->wii_binary_id, local->bindata_type, int(local->bindata_packet), local->recordCount, local->deviceId + ); + console.log(LOG_FATAL, F("BinData: *** INTERNAL ERROR *** setData called without valid header first")); + } + } + // Header + ptr += sizeof(WII5_BINDATA_HEADER); + + if (BitVal(t, 0)) { + WII5_BINDATA_0* local = (WII5_BINDATA_0*)ptr; + if (wii5Weather_18B20.age < (WII5TIME_12HOUR * 1000)) + local->temperature = wii5Weather_18B20.value; + else + local->temperature = 25500; // TODO Hard coded + if (wii5Battery.age < (WII5TIME_12HOUR * 1000)) + local->voltage = wii5Battery.value; + else + local->voltage = 25500; // TODO Hard coded + + local->last = now(); + local->mode = wii5Controller.getMode(); + + // TODO Check valid ??? + local->gps_lat = wii5Gps.gps->location.lat(); + local->gps_lon = wii5Gps.gps->location.lng(); + + ptr += sizeof(WII5_BINDATA_0); + } + + if (BitVal(t, 1)) { + WII5_BINDATA_1* local = (WII5_BINDATA_1*)ptr; + local->altitude = wii5Gps.gps->altitude.meters(); + local->hdop = wii5Gps.gps->hdop.value(); + local->satellites = wii5Gps.gps->satellites.value(); + local->when = wii5Gps.when; + local->lastRunTime = wii5Gps.lastRunTime; + + ptr += sizeof(WII5_BINDATA_1); + } + + if (BitVal(t, 2)) { + WII5_BINDATA_2* local = (WII5_BINDATA_2*)ptr; + // TODO 2024 - signalQuality ? + ptr += sizeof(WII5_BINDATA_2); + } + if (BitVal(t, 3)) { + WII5_BINDATA_3* local = (WII5_BINDATA_3*)ptr; + local->status = 0; // Special status bit fields to tell us what is going on + local->command = wii5Commands.lastCommandCmd; // Commands as usual, top bit (32) is on for ACK + // TODO local->id = wii5Commands.lastCommandId; // Resposne must contain request id in case we have many + local->id = 0; + local->response = wii5Commands.lastCommandResult; + strncpy(local->message, wii5Commands.lastCommandMessage, sizeof(local->message)); + ptr += sizeof(WII5_BINDATA_3); + } + if (BitVal(t, 4)) { + WII5_BINDATA_4* local = (WII5_BINDATA_4*)ptr; + strncpy(local->message, wii5Commands.lastCommandMessage, sizeof(local->message)); + ptr += sizeof(WII5_BINDATA_4); + } + if (BitVal(t, 5)) { + WII5_BINDATA_5* local = (WII5_BINDATA_5*)ptr; + local->runCount = sh3dNodeConfig.getRunCount(); + local->integerVersion = WII5_SOFTWARE_INTVER; + ptr += sizeof(WII5_BINDATA_5); + } + if (BitVal(t, 6)) { + ptr += sizeof(WII5_BINDATA_6); + } + if (BitVal(t, 7)) { + ptr += sizeof(WII5_BINDATA_7); + } + if (BitVal(t, 8)) { + ptr += sizeof(WII5_BINDATA_8); + } + if (BitVal(t, 9)) { + ptr += sizeof(WII5_BINDATA_8); + } + if (BitVal(t, 10)) { + ptr += sizeof(WII5_BINDATA_10); + } + if (BitVal(t, 11)) { + ptr += sizeof(WII5_BINDATA_11); + } + if (BitVal(t, 12)) { + ptr += sizeof(WII5_BINDATA_12); + } + if (BitVal(t, 13)) { + ptr += sizeof(WII5_BINDATA_13); + } + if (BitVal(t, 14)) { + ptr += sizeof(WII5_BINDATA_14); + } + if (BitVal(t, 15)) { + ptr += sizeof(WII5_BINDATA_15); + } + + return true; +} + +bool WII5BinData::setBlockMetadata(uint32_t t, void* buf, uint16_t maxSize, WII5MetaDataObject* metadata) { + + if (getSize(t) > maxSize) { + console.log(LOG_FATAL, F("BinData: Results Object too big for buger provided. Try splitting.")); + return false; + } + + char* ptr = (char*)buf; + ptr += sizeof(WII5_BINDATA_HEADER); + + if (BitVal(t, 0)) { + WII5_BINDATA_0* local = (WII5_BINDATA_0*)ptr; + local->temperature = metadata->temperatureValue; + local->voltage = metadata->batteryValue; + local->mode = metadata->mode; + local->gps_lat = metadata->gpsLat; + local->gps_lon = metadata->gpsLon; + + ptr += sizeof(WII5_BINDATA_0); + } + + if (BitVal(t, 1)) { + WII5_BINDATA_1* local = (WII5_BINDATA_1*)ptr; + /* + local->altitude = wii5Gps.gps->altitude.meters(); + local->hdop = wii5Gps.gps->hdop.value(); + local->satellites = wii5Gps.gps->satellites.value(); + local->when = wii5Gps.when; + local->lastRunTime = wii5Gps.lastRunTime; + */ + + ptr += sizeof(WII5_BINDATA_1); + } + + if (BitVal(t, 2)) { + ptr += sizeof(WII5_BINDATA_2); + } + if (BitVal(t, 3)) { + WII5_BINDATA_3* local = (WII5_BINDATA_3*)ptr; + ptr += sizeof(WII5_BINDATA_3); + } + if (BitVal(t, 4)) { + ptr += sizeof(WII5_BINDATA_4); + } + if (BitVal(t, 5)) { + ptr += sizeof(WII5_BINDATA_5); + } + if (BitVal(t, 6)) { + ptr += sizeof(WII5_BINDATA_6); + } + if (BitVal(t, 7)) { + ptr += sizeof(WII5_BINDATA_7); + } + if (BitVal(t, 8)) { + ptr += sizeof(WII5_BINDATA_8); + } + if (BitVal(t, 9)) { + ptr += sizeof(WII5_BINDATA_8); + } + if (BitVal(t, 10)) { + ptr += sizeof(WII5_BINDATA_10); + } + if (BitVal(t, 11)) { + ptr += sizeof(WII5_BINDATA_11); + } + if (BitVal(t, 12)) { + ptr += sizeof(WII5_BINDATA_12); + } + if (BitVal(t, 13)) { + ptr += sizeof(WII5_BINDATA_13); + } + if (BitVal(t, 14)) { + ptr += sizeof(WII5_BINDATA_14); + } + if (BitVal(t, 15)) { + ptr += sizeof(WII5_BINDATA_15); + } + +} + +bool WII5BinData::setBlockResults(uint32_t t, void* buf, uint16_t maxSize, WII5Processed* processed) { + char* ptr = (char*)buf; + ptr += sizeof(WII5_BINDATA_HEADER); + + if (BitVal(t, 0)) { ptr += sizeof(WII5_BINDATA_0); } + if (BitVal(t, 1)) { ptr += sizeof(WII5_BINDATA_1); } + if (BitVal(t, 2)) { ptr += sizeof(WII5_BINDATA_2); } + if (BitVal(t, 3)) { ptr += sizeof(WII5_BINDATA_3); } + if (BitVal(t, 4)) { ptr += sizeof(WII5_BINDATA_4); } + if (BitVal(t, 5)) { ptr += sizeof(WII5_BINDATA_5); } + if (BitVal(t, 6)) { ptr += sizeof(WII5_BINDATA_6); } + + // processed1 + if (BitVal(t, 7)) { + WII5_BINDATA_7* local = (WII5_BINDATA_7*)ptr; + /* local->tz_max[4]; local->htm_max[4]; local->hcm_max[4]; local->hz_max[4]; */ + memcpy(local, &processed->processed1, sizeof(float) * 16); // 4 groups of floats + ptr += sizeof(WII5_BINDATA_7); + } + + // processed1 + if (BitVal(t, 8)) { + WII5_BINDATA_8* local = (WII5_BINDATA_8*)ptr; + local->processed1 = processed->processed1; + ptr += sizeof(WII5_BINDATA_8); + } + + // processed2 + if (BitVal(t, 9)) { + WII5_BINDATA_9* local = (WII5_BINDATA_9*)ptr; + local->processed2 = processed->processed2; + ptr += sizeof(WII5_BINDATA_8); + } + + if (BitVal(t, 11)) { + WII5_BINDATA_11* local = (WII5_BINDATA_11*)ptr; + memcpy(&local->direction, &processed->processed2.part2int[8], sizeof(int16_t) * 54); + ptr += sizeof(WII5_BINDATA_11); + } + + if (BitVal(t, 12)) { + WII5_BINDATA_12* local = (WII5_BINDATA_12*)ptr; + ptr += sizeof(WII5_BINDATA_12); + } + + if (BitVal(t, 13)) { + WII5_BINDATA_13* local = (WII5_BINDATA_13*)ptr; + memcpy(&local->moments, &processed->processed2.part2float[0], sizeof(float) * 7); + ptr += sizeof(WII5_BINDATA_13); + } + + if (BitVal(t, 14)) { + WII5_BINDATA_14* local = (WII5_BINDATA_14*)ptr; + memcpy(&local->psd, &processed->processed1.part1float[18], sizeof(float) * 55); + ptr += sizeof(WII5_BINDATA_14); + } +} + +WII5_BINDATA_3* WII5BinData::getCommand(void* buf, uint16_t maxSize) { + uint32_t t = 0; + SetBit(t, 3); + char* ptr = (char*)buf; + { + WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)ptr; + if (local->wii_binary_id != 60001) { + console.log(LOG_WARN, F("BinData - not a valid header")); + return NULL; + } + + // Should upport multiple, but simplistic for now + if (local->bindata_type != t) { + console.log(LOG_WARN, F("BinData - could not find valid command")); + return NULL; + } + + ptr += sizeof(WII5_BINDATA_HEADER); + } + + console.log(LOG_INFO, F("BinData - found valid command")); + return (WII5_BINDATA_3*)ptr; +} + +// dumpData - show the data to the console for debugging +void WII5BinData::dumpData(void* buf, uint16_t maxSize, bool values) { + uint32_t t = 0; + char* ptr = (char*)buf; + { + WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)ptr; + // local->wii_binary_id = 60001; + t = local->bindata_type; + ptr += sizeof(WII5_BINDATA_HEADER); + console.printf(F("# BinData Header found\r\n")); + console.printf(F("# deviceId = %lu\r\n"), local->deviceId); + console.printf(F("# recordCount = %lu\r\n"), local->recordCount); + } + + if (BitVal(t, 0)) { + WII5_BINDATA_0* local = (WII5_BINDATA_0*)ptr; + console.printf(F("# BinData 0 - title=%s\r\n"), strTitle(0, false)); + if (values) { + console.printf(F("# temperature = %d\r\n"), int(local->temperature)); + console.printf(F("# voltage = %d\r\n"), int(local->voltage)); + console.printf(F("# last = %ld\r\n"), long(local->last)); + console.printf(F("# mode = %d\r\n"), int(local->mode)); + console.printf(F("# gps_lat = %ld\r\n"), long(local->gps_lat * GPS_POS_MULT)); + console.printf(F("# gps_lon = %ld\r\n"), long(local->gps_lon * GPS_POS_MULT)); + } + ptr += sizeof(WII5_BINDATA_0); + } + + if (BitVal(t, 1)) { + WII5_BINDATA_1* local = (WII5_BINDATA_1*)ptr; + console.printf(F("# BinData 1 - title=%s\r\n"), strTitle(1, false)); + if (values) { + console.printf(F("# gps_altitude = %ld\r\n"), long(local->altitude)); + console.printf(F("# gps_hdop = %d\r\n"), int(local->hdop)); + console.printf(F("# gps_satellites = %d\r\n"), int(local->satellites)); + console.printf(F("# gps_when = %ld\r\n"), long(local->when)); + console.printf(F("# gps_lastRunTime = %ld\r\n"), long(local->lastRunTime)); + } + ptr += sizeof(WII5_BINDATA_1); + } + + if (BitVal(t, 2)) { + WII5_BINDATA_2* local = (WII5_BINDATA_2*)ptr; + console.printf(F("# BinData 2 - title=%s\r\n"), strTitle(2, false)); + if (values) { + console.printf(F("# lastRunTime = %d\r\n"), int(local->lastRunTime)); + console.printf(F("# signalQuality = %d\r\n"), int(local->signalQuality)); + } + ptr += sizeof(WII5_BINDATA_2); + } + + if (BitVal(t, 3)) { + console.printf(F("# BinData 3 - title=%s\r\n"), strTitle(3, false)); + ptr += sizeof(WII5_BINDATA_3); + } + if (BitVal(t, 4)) { + console.printf(F("# BinData 4 - title=%s\r\n"), strTitle(4, false)); + ptr += sizeof(WII5_BINDATA_4); + } + if (BitVal(t, 5)) { + console.printf(F("# BinData 5 - title=%s\r\n"), strTitle(5, false)); + ptr += sizeof(WII5_BINDATA_5); + } + if (BitVal(t, 6)) { + console.printf(F("# BinData 6 - title=%s\r\n"), strTitle(6, false)); + ptr += sizeof(WII5_BINDATA_6); + } + if (BitVal(t, 7)) { + console.printf(F("# BinData 7 - title=%s\r\n"), strTitle(7, false)); + ptr += sizeof(WII5_BINDATA_7); + } + if (BitVal(t, 8)) { + console.printf(F("# BinData 8 - title=%s\r\n"), strTitle(8, false)); + ptr += sizeof(WII5_BINDATA_8); + } + if (BitVal(t, 9)) { + console.printf(F("# BinData 9 - title=%s\r\n"), strTitle(9, false)); + ptr += sizeof(WII5_BINDATA_8); + } + if (BitVal(t, 10)) { + console.printf(F("# BinData 10 - title=%s\r\n"), strTitle(10, false)); + ptr += sizeof(WII5_BINDATA_10); + } + if (BitVal(t, 11)) { + console.printf(F("# BinData 11 - title=%s\r\n"), strTitle(11, false)); + ptr += sizeof(WII5_BINDATA_11); + } + if (BitVal(t, 12)) { + console.printf(F("# BinData 12 - title=%s\r\n"), strTitle(12, false)); + ptr += sizeof(WII5_BINDATA_12); + } + if (BitVal(t, 13)) { + console.printf(F("# BinData 13 - title=%s\r\n"), strTitle(13, false)); + ptr += sizeof(WII5_BINDATA_13); + } + if (BitVal(t, 14)) { + console.printf(F("# BinData 14 - title=%s\r\n"), strTitle(14, false)); + ptr += sizeof(WII5_BINDATA_14); + } + if (BitVal(t, 15)) { + console.printf(F("# BinData 15 - title=%s\r\n"), strTitle(15, false)); + ptr += sizeof(WII5_BINDATA_15); + } +} + +void WII5BinData::showBlocks(uint32_t t) { + console.printf(F("BinData Type=%lu, Blocks="), t); + for (uint8_t b = 0; b < 16; b++) { + if (BitVal(t, b)) { + console.printf(F("%d, "), int(b)); + } + } + console.printf(F(" size=%d"), getSize(t)); +} + +void WII5BinData::showSplit(uint32_t in_t, uint16_t max_size, char* bufout, uint16_t bufsize) { + uint8_t nextBit = 0; + uint32_t outType = 0; + // TODO: also fill bufout/bufsize with the split summary so callers receive + // structured data, not just console output. + (void)bufout; (void)bufsize; + + console.printf(F("# Orig ")); + showBlocks(in_t); + console.printNewLine(); + + uint8_t count = 0; + while (getSplit(in_t, max_size, &nextBit, &outType)) { + console.printf(F("# New %d "), count); + showBlocks(outType); + console.printNewLine(); + count++; + + if (count > 25) { + console.log(LOG_FATAL, F("BinData: More than 25 splits, not supported")); + return; + } + } +} + +// showSizes - all sizes to console. +void WII5BinData::showSizes(char* bufout, uint16_t bufsize) { + console.printf(F("# BinData Size overhead=%d\r\n"), sizeof(WII5_BINDATA_HEADER)); + // TODO: also fill bufout/bufsize with the size summary so callers receive + // structured data, not just console output. + (void)bufout; (void)bufsize; + for (uint8_t x = 0; x < 16; x++) { + console.printf(F("# BinData Size bit=%d, size=%d, title=%s\r\n"), + int(x), + getSizeOne(x), + strTitle(x, false) + ); + if (getSizeOne(x) > 300) { + console.printf(F("# *** WARNING *** Packet > 300 bytes ***\r\n")); + } + } +} + +// strTitle - get the title for this object +char* WII5BinData::strTitle(uint8_t bit, bool ext) { + switch(bit) { + case 0: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_0_TEXT)); break; + case 1: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_1_TEXT)); break; + case 2: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_2_TEXT)); break; + case 3: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_3_TEXT)); break; + case 4: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_4_TEXT)); break; + case 5: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_5_TEXT)); break; + case 6: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_6_TEXT)); break; + case 7: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_7_TEXT)); break; + case 8: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_8_TEXT)); break; + case 9: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_9_TEXT)); break; + case 10: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_10_TEXT)); break; + case 11: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_11_TEXT)); break; + case 12: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_12_TEXT)); break; + case 13: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_13_TEXT)); break; + case 14: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_14_TEXT)); break; + default: strcpy_P(wii5BufferString, (PGM_P) F("unknown")); break; + }; + return wii5BufferString; +} + +bool WII5BinData::hasType(uint8_t bit, void* buf, uint16_t maxSize) { + // TODO Check bit +} +void* WII5BinData::getData(uint8_t bit, void* buf, uint16_t maxSize) { + // Find location in buffer +} + +WII5BinData wii5BinData; diff --git a/WII5BinData.h b/WII5BinData.h new file mode 100644 index 0000000..c8ff5ca --- /dev/null +++ b/WII5BinData.h @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5BinData.h + * @brief Binary message format for Iridium SBD: layout, packing, and split logic. + */ + +#ifndef WII5BinData_h +#define WII5BinData_h +#include + +// Header Size = 15 +typedef struct { + // 0 + uint16_t wii_binary_id; // Fixed ID for the old system - 60001 + // 2 + uint32_t bindata_type; // Which packet type is this. + // 6 + uint8_t bindata_packet; // Which part of the packet is this - note we don't need total, can claculate from type + + // Essential data to allow packets to be put back together + // 7 + uint32_t deviceId; // Ideally move back to uint32_t + // 11 + uint32_t recordCount; +} WII5_BINDATA_HEADER; + + +#define WII5_BINDATA_0_TEXT "Status,Temp,Volts,GPS" +typedef struct { + // uint8_t deviceId_SHORT; // Ideally move back to uint32_t + uint32_t status; // Special status bit fields to tell us what is going on + time_t last; + int16_t voltage; // 0..16 with 2 decimal places -10000 .. + 10000 + int16_t temperature; // -100 .. +100 with two decimal places 2452 + uint8_t mode; + float gps_lat; + float gps_lon; + // Ideally status - number or reboots/resets and other errors + // How many messages sent, disk space and lots more. +} WII5_BINDATA_0; + +#define WII5_BINDATA_1_TEXT "GPS Extended" +// Detailed GPS data +typedef struct { + uint16_t lastRunTime; // Number of seconds to lock last time + time_t when; // When we last got a time lock + int16_t altitude; // Altitutde in meters + uint8_t satellites; + float hdop; +} WII5_BINDATA_1; + + +#define WII5_BINDATA_2_TEXT "Iridium Extended" +// Detailed Iridium data +typedef struct { + uint16_t lastRunTime; // How long did the last one take + uint8_t signalQuality; + // uint32_t lastId; (the last id sent etc) + // total_sent + // total_received (I think the Iridium knows) + // imei - very large +} WII5_BINDATA_2; + +#define WII5_BINDATA_3_TEXT "Message - Small (<50 bytes)" +// Commands - Tiny version +typedef struct { + uint32_t status; // Special status bit fields to tell us what is going on + uint32_t command; // Commands as usual, top bit (32) is on for ACK + uint8_t id; // Resposne must contain request id in case we have many + uint8_t response; + // TODO 2024 - This is a BREAKING CHANGE for any old buoys - could be fixed on WII3_Server BinData by supporting 30 or 25 text chars + char message[25]; // On ACK/NACK it can contain error data... TODO 2024 Changed from 30 to 25 - where else / what else +} WII5_BINDATA_3; + +#define WII5_BINDATA_4_TEXT "Message - large (< 300 bytes)" +// Commands - full size version +typedef struct { + // uint32_t status; // Special status bit fields to tell us what is going on + // uint32_t command; // Commands as usual, top bit (32) is on for ACK + // uint8_t id; + // uint8_t response; + char message[235]; // On ACK/NACK it can contain error data + // Here we may use this to send a script, by including in this + // message tag - sh/1/3: - ie. write to shell script, Part 1 of 3 + // Rest is concat to the end of the scipt +} WII5_BINDATA_4; + +// TODO Idea - Results/Processed/Run sumary - HMO etc? +#define WII5_BINDATA_5_TEXT "Extra Buoy Information" +typedef struct { + uint32_t runCount; + uint32_t integerVersion; // 16 + // char version[20]; +} WII5_BINDATA_5; + +#define WII5_BINDATA_6_TEXT "Processed - QF" +typedef struct { + float qf_mvar; + float qf_p_accel; + // (gyro not being used) float qf_p_gyro; + // (mag not being used) float qf_p_mag; + // (kistler not being used) float qf_kist", + // (kistler not being used) qf_p_kist", + float qf_imu; + float qf_head; + float a; + float b; +} WII5_BINDATA_6; + +#define WII5_BINDATA_7_TEXT "Processed - _max" +typedef struct { + float tz_max[4]; + float htm_max[4]; + float hcm_max[4]; + float hz_max[4]; +} WII5_BINDATA_7; + +#define WII5_BINDATA_8_TEXT "WII5Processed1" +typedef struct { + WII5Processed1 processed1; +} WII5_BINDATA_8; + +#define WII5_BINDATA_9_TEXT "WII5Processed2" +typedef struct { + WII5Processed2 processed2; +} WII5_BINDATA_9; + +#define WII5_BINDATA_10_TEXT "WII5MetaDataObject" +typedef struct { + WII5MetaDataObject metadata; +} WII5_BINDATA_10; + +// Processed - Direction +#define WII5_BINDATA_11_TEXT "Processed - Direction array" +typedef struct { + int direction[54]; +} WII5_BINDATA_11; + +#define WII5_BINDATA_12_TEXT "Processed - Misc and Quality" +// Processed - Metadatas full (excluding kistler, mag, gyro) +// TODO: trim BinData record set to active fields +typedef struct { + float hmo; + + float theta; + float dp; + float s; + float r; + float nstd; + float f2; + float yaw_std; + float open_water; + float power_diff; + float tp; +} WII5_BINDATA_12; + +// Moments +#define WII5_BINDATA_13_TEXT "Processed - Moment array" +typedef struct { + float moments[7]; +} WII5_BINDATA_13; + +// PSD +#define WII5_BINDATA_14_TEXT "Processed - PSD array" +typedef struct { + float psd[55]; +} WII5_BINDATA_14; + +typedef struct { +} WII5_BINDATA_15; + + + +/** + * @brief Binary message format for Iridium SBD. + * + * Each SBD message starts with a WII5_BINDATA_HEADER (binary id 60001, + * record metadata, a 32-bit `bindata_type` bitmap), then a sequence of + * fixed-size payload structs (WII5_BINDATA_0 .. _15) selected by which + * bits are set in `bindata_type`. The `getSplit()` helper splits an + * over-sized type bitmap across multiple 340-byte SBD frames. + */ +class WII5BinData { + public: + WII5BinData() {} + /** @brief One-time bring-up. */ + void begin(); + + /** @brief Pop the next chunk that fits in `max_size` bytes; advances start_bit. */ + bool getSplit(uint32_t in_t, uint16_t max_size, uint8_t* start_bit, uint32_t* out_t); + /** @brief Size of a single payload struct selected by bit `b`. */ + uint16_t getSizeOne(uint8_t b); + /** @brief Set bit `b` in `t` and return the modified value. */ + uint16_t setBit(uint32_t t, uint8_t b); + /** @brief Total wire size for a type bitmap, including the header. */ + uint16_t getSize(uint32_t t); + /** @brief Total payload size for a type bitmap, excluding the header. */ + uint16_t getSizeObj(uint32_t t); + + /** @brief Initialize a binary buffer with header for type `t`. */ + bool createData(uint32_t t, void* buf, uint16_t maxSize, uint32_t recordCount = 0); + /** @brief Fill the payload structs in `buf` from live device state. */ + bool setData(uint32_t t, void* buf, uint16_t maxSize); + /** @brief Fill the payload structs from a pre-loaded SD metadata block. */ + bool setBlockMetadata(uint32_t t, void* buf, uint16_t maxSize, WII5MetaDataObject* metadata); + /** @brief Fill the payload structs from a pre-loaded SD results (processed) block. */ + bool setBlockResults(uint32_t t, void* buf, uint16_t maxSize, WII5Processed* processed); + + /** @brief Print a human-readable dump of the buffer to the console. */ + void dumpData(void* buf, uint16_t maxSize, bool values); + /** @brief Print the size of every BinData payload type. */ + void showSizes(char* bufout = NULL, uint16_t bufsize = 0); + /** @brief Print the split sequence for a given type bitmap. */ + void showSplit(uint32_t in_t, uint16_t max_size, char* bufout = NULL, uint16_t bufsize = 0); + /** @brief Human-readable title for payload bit `bit`. */ + char* strTitle(uint8_t bit, bool ext); + + /** @brief Does this buffer's header type-bitmap include bit `bit`? */ + bool hasType(uint8_t bit, void* buf, uint16_t maxSize); + /** @brief Pointer to the payload struct for bit `bit` inside `buf`. */ + void* getData(uint8_t bit, void* buf, uint16_t maxSize); + + /** @brief Validate header and return pointer to the embedded command struct. */ + WII5_BINDATA_3* getCommand(void* buf, uint16_t maxSize); + /** @brief Print the list of bits set in a type bitmap. */ + void showBlocks(uint32_t t); + + protected: + bool testDone; +}; + +extern WII5BinData wii5BinData; + +#endif diff --git a/WII5Commands.cpp b/WII5Commands.cpp new file mode 100644 index 0000000..4b334dc --- /dev/null +++ b/WII5Commands.cpp @@ -0,0 +1,1504 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Commands.cpp + * @brief Command protocol handler: parses and dispatches @-prefixed commands. + */ + +/* + +WII5Commands - Main ability to control the wave buoy + +Command Inputs + + * Hardware Buttons + Use Sh3dNodeIO -> processCmd + + * Serial Console + Use console.available -> processConsole + + * Radio + Use Sh3dNodeNetwork.available -> processCmd + + * Iridium + Use TODO (not sure yet, now this works out we have waiting Iridium Messges) + Maybe just wii5Iridium.available and next? + +Naming Convensions here: + + * begin - called once, as usual + * loop, loop* - Get direct acccess to hardware, such as a serial port or radio + * process* - Do something with some data we already have + * parse* - Calculate some data from probably some raw strings + +Flow of Data: Serial + + wii5Commands.loop() + console.loop - Basic read serial port + console.processCmd - Calculate string, and CSV output + console.available - got something + -> wii5Commands.processConsole + Got "@" + -> wii5Commands.processConsoleCsv + + +*/ + +#include +#include +#include +#include +#include +#include + +void WII5Commands::begin() { + disableButtons = 600000; +} + +/* + +injectCommand - literally inject a plain text command and process it + + Injecting a command can look like this: + @WII5,setting,captureoptions,90,16400,40960 + 1234567890123456789012345678901234567890123 + 1 2 3 4 + 44 bytes - with overheads, could be over the 50 + + 4 bytes - command - @WII5,setting,captureoptions + 2 bytes - Minutes = 1440 (2 bytes) maximum + 4 bytes - Binary data + 2 bytes (maybe 4) - rows + 12 bytes + +*/ +bool WII5Commands::injectCommand(char *sendCmd) { + console.log(LOG_INFO, F("Command Inject: %s"), sendCmd); + console.clearCommand(); + // Send prefix @WII5, + console.processCmd('@'); + console.processCmd('W'); + console.processCmd('I'); + console.processCmd('I'); + console.processCmd('5'); + console.processCmd(','); + // Send received command + for (uint16_t k = 0; k < strlen(sendCmd); k++) { + char myChar = sendCmd[k]; + console.processCmd(myChar); + } + console.processCmd('\r'); + console.processCmd('\n'); + // Proess command now + return true; +} + +bool WII5Commands::processBinData() { + // TODO hard coded? + /* + WII5_BINDATA_3* command = wii5BinData.getCommand(wii5BinaryIridium, 340); + if (command) { + console.log(LOG_ERROR, F("processBinData injecting BinData message - %s"), command->message); + injectCommand(command->message); + } + else + */ + if (strncmp_P(wii5BinaryIridium, (PGM_P) F("@WII5,"), 6) == 0) { + console.log(LOG_ERROR, F("processBinData original (wii5) - %s"), (char*)wii5BinaryIridium); + console.log(LOG_ERROR, F("processBinData injecting Text message (wii5) - %s"), (char*)wii5BinaryIridium + 6); + injectCommand((char*)wii5BinaryIridium + 6); + } + // TODO 2024 - Is this used - consider commenting out + else if (strncmp_P(wii5BinaryIridium, (PGM_P) F("@Maths,"), 7) == 0) { + // TODO: Area should have maths + setupLast(WII5DEVICE_UNKNOWN, WII5PORT_CONSOLE, WII5AREA_WII5, WII5COMMAND_NONE); + console.log(LOG_ERROR, F("processBinData original (maths) - %s"), (char*)wii5BinaryIridium); + console.log(LOG_ERROR, F("processBinData injecting Text message (maths) - %s"), (char*)wii5BinaryIridium + 7); + if (wii5Maths.queueCommand((char*)wii5BinaryIridium + 7, strlen(wii5BinaryIridium+7))) { + // Successfully queued message (max 2) + resultLast(WII5RESULTS_SUCCESS, F("MathsQueueSuccess")); + } + else { + // Failed to queue - report + resultLast(WII5RESULTS_INVALID, F("MathsQuueFailed")); + } + return true; + } + else { + console.log(LOG_ERROR, F("processBinData failed - no Command found in packet")); + return false; + } + + // Command injected - time to process it + setupLast(WII5DEVICE_UNKNOWN, WII5PORT_CONSOLE, WII5AREA_WII5, WII5COMMAND_NONE); + if (processConsoleAT()) { + console.log(LOG_INFO, F("Command Inject: SUCCESS")); + // if ( (lastCommandCmd != WII5COMMAND_NONE) && (lastCommandResult != WII5RESULT_UNKNWON) ) { + // lastCommandResult = result; + // strncpy_P(lastCommandMessage, message, sizeof(lastCommandMessage)); + return true; + } + else { + console.log(LOG_INFO, F("Command Inject: FAILED/NOT EXIST")); + return false; + } +} + +bool WII5Commands::processNetwork() { + #ifdef WII5_RADIO_LORA + if (sh3dNodeNetwork.lastValid) { + switch(int(sh3dNodeNetwork.lastPacketType)) { + case SH3D_NODE_Packet_ConsoleReceive_packetType: + console.printf(F("# REMOTE RECEIVE: Level=%d"), sh3dNodeNetwork.localConsoleReceive->level); + console.printf(F(" message=%s"), sh3dNodeNetwork.localConsoleReceive->buf); + console.printNewLine(); + // TODO setup last and runCommand + break; + + default: + break; + }; + } + #endif + return false; +} + +// NOTE: Be careful this doesn't do much with the buttons, just inject into Console or similar +bool WII5Commands::processButtons() { + /* New option for keeping maths on? */ + if (sh3dNodeIO.button2->isClicked()) { + console.log(LOG_INFO, F("Button2: clicked - turning on Maths CPU, hold 1h, return line on")); + // Turn on Maths. Hold maths on 1 hour. Set return line (force Maths to stay on) + wii5Maths.start(WII5MATHSMODE_BUTTON); + wii5Maths.setHold(3600); + wii5Maths.setReturnLine(); + } + if (sh3dNodeIO.button2->isReleased()) { + console.log(LOG_INFO, F("Button2: released - turning off return line")); + wii5Maths.cancelReturnLine(); + } + + if (disableButtons < 30000) { + return false; + } + + // Button still held for 10 minutes - disable it + else if (buttonActive && (buttonHeld > 600000)) { + buttonActive = false; + buttonReleased = false; + } + + // Button released times up + else if (buttonReleased && (buttonReleaseWait > 500)) { + if (buttonHeld < WII5_BUTTON_HOLD_TIME) { + console.log(LOG_DEBUG, F("Button1: was held for < 5 seconds")); + } + else { + console.log(LOG_DEBUG, F("Button1: Released, switching mode")); + + // Set default mode - set in config before. + wii5Controller.setDefaultMode(); + + // Optional - reboot + } + buttonActive = false; + buttonReleased = false; + buttonMode = 0; + } + + // OUT: Two Beeps - tell you we detected it... + else if (sh3dNodeIO.button1->isClicked()) { + console.log(LOG_INFO, F("Button1: clicked")); + if (!buttonActive) { + buttonHeld = 0; + buttonActive = true; + buttonMode = 0; + console.log(LOG_DEBUG, F("Button1: pressed - hold for 10 seconds to get current mode")); + } + buttonReleased = false; + } + + // RELEASE = Actiate what we just set + else if (sh3dNodeIO.button1->isReleased()) { + console.log(LOG_INFO, F("Button1: released")); + // We are in active state - wait + if (buttonActive) { + buttonReleaseWait = 0; + buttonReleased = true; + } + } + + // Still held down, count the time + // TODO 2024 - Simplify this for WII5 2024 + if (buttonActive) { + wii5Controller.shared5On(); + // TODO if buttonActive for more then 5 mins. Disable button ! + + /* + + 0..4999 = Do nothing + > 5000 = Inc counter & show mode + > 10000 = Inc counter & + + */ + if (buttonHeld > (uint32_t)((uint32_t)((uint32_t)buttonMode * (uint32_t)WII5_BUTTON_HOLD_TIME) + (uint32_t)WII5_BUTTON_HOLD_TIME)) { + buttonMode++; + + switch(buttonMode) { + // Report Status + case 1: + wii5Maths.start(WII5MATHSMODE_BUTTON); + wii5Maths.setHold(900); + if (wii5Config.getDefaultMode() == WII5MODE_CAPTURE) { + // Capture = 3 beeps + sh3dNodeIO.led2Set(LED_TRIPPLE_LONG); + } + else { + // Sleep = 2 beeps + sh3dNodeIO.led2Set(LED_DOUBLE_LONG); + } + console.log(LOG_INFO, F("Button1: held - 2 = sleep, 3 = capturue, hold 10 seconds to troggle")); + break; + + // Toggle Mode - then toggle back again if a mistake + case 2: + case 3: + wii5Maths.start(WII5MATHSMODE_BUTTON); + wii5Maths.setHold(900); + // NOTE: Onl default mode for now + if (wii5Config.getDefaultMode() == WII5MODE_CAPTURE) { + console.log(LOG_INFO, F("Button1: Swap - moving from to Sleep mode - please release button, buttonMode=%d"), buttonMode); + wii5Config.setDefaultMode(WII5MODE_SLEEP); + sh3dNodeIO.led2Set(LED_DOUBLE_LONG); + } + else { + console.log(LOG_INFO, F("Button1: Swap - moving from to Capture mode - please release button, buttonMode=%d"), buttonMode); + wii5Config.setDefaultMode(WII5MODE_CAPTURE); + sh3dNodeIO.led2Set(LED_TRIPPLE_LONG); + } + break; + case 4: + console.log(LOG_INFO, F("Button1: Shutdown stage - reseting in 5 seconds")); + wii5Setup.shutdownbeep(); + wii5Maths.setHold(0); + wii5Maths.powerOff(true); + wii5Maths.stop(); + + // Force pins output low + pinMode(SHARED_5VOLT_PIN, OUTPUT); + digitalWrite(SHARED_5VOLT_PIN, LOW); + pinMode(POWER_MATHS_PIN, OUTPUT); + digitalWrite(POWER_MATHS_PIN, LOW); + + wii5Config.resetConfigData(); + + // Wait for Pi shutdown 5 seconds, then reset us + wii5Controller.safeDelay(5000); + + // TODO - how to stop this recurring forever if button fails on. Write to EEPROM + sh3dNodeUtil.reset(); + break; + } + } + /* + else { + console.log(LOG_ERROR, F("buttonMode=%d, HOLD_TIME=%d, Held=%d"), buttonMode, WII5_BUTTON_HOLD_TIME, butonHeld) + if (buttonHeld > ((buttonMode * WII5_BUTTON_HOLD_TIME) + WII5_BUTTON_HOLD_TIME)) { + + } + */ + } + + #if BUTTON_3>0 + if (sh3dNodeIO.button3->isClicked()) { + wii5Maths.gpioClick(); + } + #endif + + return false; +} + +// setupLast - Need this before runCommand - e.g. if from Console vs Iridium +bool WII5Commands::runCommand(WII5_COMMANDS cmd, char** params, uint8_t paramsLen) { + setupLast(WII5DEVICE_UNKNOWN, WII5PORT_CONSOLE, WII5AREA_WII5, cmd); + // TODO returnLast + + switch(cmd) { + case WII5COMMAND_NONE: + console.log(LOG_INFO, F("ERROR: @WII5 command, no match")); + return false; + break; + + case WII5COMMAND_NA: + // Handled by another module... move along. + break; + + case WII5COMMAND_HELP: + console.log(LOG_INFO, F("HELP: replaced with @Help")); + break; + + case WII5COMMAND_ECHO: + // TODO reply with everythng after echo - up to X chars + break; + + case WII5COMMAND_PEOPLE: + // TODO: reply with everything after echo - up to X chars + resultLast(WII5RESULTS_NOREPLY, F("No reply - people")); + resultLast(WII5RESULTS_NOREPLY, F("Scott Penrose ")); + resultLast(WII5RESULTS_NOREPLY, F("WII5 Buoy firmware contributors")); + switch(console.getCsvVal(3)) { + case 0: + break; + } + break; + + // HELLO Received - we need to send an ACK + case WII5COMMAND_HELLO: + // @WII5,hello,SourcePort,SourceDeviceType,SourceDeviceID,SourceSoftwareVersion + // @WII5,hello!,MyPort,MyDeviceType,MyDeviceID,MySoftwareVersion + // @WII5,hello,Console,Maths,100001,1 + // NOTE: Device ID should be same both ends for Maths etc + console.log(LOG_DEBUG, F("Hello - port=%s, device=%s"), + console.getCsvBuffer(2), + console.getCsvBuffer(3) + ); + + wii5Maths.woops_on(); + wii5Controller.setLastHello( + wii5Strings.parsePort(console.getCsvBuffer(2)), + wii5Strings.parseDevice(console.getCsvBuffer(3)) + ); + + wii5Display.atCommandSend(F("hello!"), F("%S,%S,port=%S,device=%S,id=%lu,version=%S,board=%S,commit=%S,freeMemory=%d/%d"), + // Old way + F("console"), + F("wavebuoy"), // WII5_DEVICE_TYPE), + // New way + // 2 - port + F("console"), + // 3 - device + F("wavebuoy"), // WII5_DEVICE_TYPE), + // 4 - id + wii5Config.getDeviceId(), + // 5 - version + F(WII5_SOFTWARE_VERSION), + // 6 - Board + F(WII5_BOARD_NAME), + // 7 - Commit + F(WII5_SOFTWARE_COMMIT), + // TODO too long + // 8 - Free Memory / Max + freeMemory(), WII5_BOARD_MEMORY + /* + // 9 - Uptime + (uint32_t)(millis() / 60000), + // 10 - Current storage Record + wii5Config.getRecordCount(), + // 11 - mode + wii5Strings.strMode(wii5Controller.getMode()) + */ + ); + + // Send DateTime if it is up to date and valid (we should also support RTC) + if (wii5Gps.isTimeValid()) { + console.log(LOG_DEBUG, F("Sending time - valid from GPS")); + wii5Display.atCommandSend(F("time"), F("%04d,%02d,%02d,%02d,%02d,%02d"), + int(year()), int(month()), int(day()), + int(hour()), int(minute()), int(second()) + ); + } + else { + console.log(LOG_DEBUG, F("Not sending time - not valid from GPS")); + } + + // Send statusDump - useful information (short version) + wii5Controller.statusDump(); + break; + + case WII5COMMAND_HELLOACK: + console.log(LOG_DEBUG, F("COMMAND: hello Ack - Not implemented")); + wii5Maths.woops_on(); + wii5Controller.setLastHello( + wii5Strings.parsePort(console.getCsvBuffer(2)), + wii5Strings.parseDevice(console.getCsvBuffer(3)) + ); + + /* + console.printf(F("@WII5,hello!,Console,%S,%lu,%S"), + F(WII5_DEVICE_TYPE), + wii5Config.getDeviceId(), + F(WII5_SOFTWARE_VERSION) + ); + */ + + break; + + + // Sleep 30 seconds, loop 20 seconds - check we are basic WDT + case WII5COMMAND_WAITY: + console.log(LOG_FATAL, F("Sleep 30 seconds, then loop 20 using safe loop")); + wii5Setup.sleepBefore(); + sh3dNodeUtil.sleep(30); + wii5Setup.sleepAfter(); + for (uint8_t n = 0; n < 20; n++) { + console.log(LOG_FATAL, F("Safe Loop %d / 20"), n); + wii5Controller.safeDelay(1000); + } + // NOTE: No break, now do loop + + // Unsafe loop 20 seconds - good for internal WDT testing + case WII5COMMAND_WAITX: + console.log(LOG_FATAL, F("ABOUT To loop (no other loops for 20 seconds)")); + for (uint8_t n = 0; n < 20; n++) { + console.log(LOG_FATAL, F("UNSAFE Loop %d"), n); + delay(1000); + } + console.log(LOG_FATAL, F("End of the 20 second loop")); + break; + + // Unsafe 5 minutes - should cause external WDT reset + case WII5COMMAND_WAITZ: + console.log(LOG_FATAL, F("ABOUT To loop (no other loops for 300 seconds)")); + for (uint16_t n = 0; n < 300; n++) { + console.log(LOG_FATAL, F("UNSAFE Loop %d"), n); + delay(1000); + } + console.log(LOG_FATAL, F("End of the 300 second loop")); + break; + + // Safe 5 minutes - Should not cause external WDT reset + case WII5COMMAND_WAITQ: + console.log(LOG_FATAL, F("ABOUT To loop (safe delay 300 seconds)")); + for (uint16_t n = 0; n < 300; n++) { + console.log(LOG_FATAL, F("Safe Loop %d"), n); + wii5Controller.safeDelay(1000); + } + console.log(LOG_FATAL, F("End of the 300 second loop")); + break; + + // Sleep 5 miniutes - should not cause external WDT reset + case WII5COMMAND_WAITS: + console.log(LOG_FATAL, F("ABOUT To Sleep for 300 seconds to check WDT")); + wii5Setup.sleepBefore(); + sh3dNodeUtil.sleep(300); + wii5Setup.sleepAfter(); + console.log(LOG_FATAL, F("End of the 300 second sleep")); + break; + + case WII5COMMAND_STATUS: + // TODO be nice to return status + // TODO move to dipslay ? + console.log(LOG_DEBUG, F("COMMAND: Status")); + wii5Controller.statusDump(true); + break; + + case WII5COMMAND_LOG_DEBUG: + console.log(LOG_INFO, F("LOG: Level set to DEBUG - test with @WII5,log,test")); + console.setLevel(LOG_ALL); + break; + + case WII5COMMAND_LOG_DEFAULT: + console.log(LOG_INFO, F("LOG: Level set to INFO - test with @WII5,log,test")); + console.setLevel(LOG_INFO); + break; + + case WII5COMMAND_LOG_FATAL: + console.log(LOG_INFO, F("LOG: Level set to FATAL - test with @WII5,log,test")); + console.setLevel(LOG_INFO); + break; + + case WII5COMMAND_LOG_ERROR: + console.log(LOG_INFO, F("LOG: Level set to ERROR - test with @WII5,log,test")); + console.setLevel(LOG_ERROR); + break; + + case WII5COMMAND_LOG_TEST: + console.printf(F("# Log Level Fatal, Error, Warn, Info, Debug")); + console.log(LOG_FATAL, F("Using FATAL millis=%d"), millis()); + console.log(LOG_ERROR, F("Using ERROR millis=%d"), millis()); + console.log(LOG_WARN, F("Using WARN millis=%d"), millis()); + console.log(LOG_INFO, F("Using INFO millis=%d"), millis()); + console.log(LOG_DEBUG, F("Using DEBUG millis=%d"), millis()); + break; + + case WII5COMMAND_STORAGE_STATUS: + resultLast(WII5RESULTS_NOREPLY, F("No reply - SD Status")); + console.log(LOG_INFO, F("Storage: Current = %d"), wii5Controller.getSD()); + break; + + case WII5COMMAND_STORAGE_DEBUG: + resultLast(WII5RESULTS_NOREPLY, F("No reply Storage Debug")); + sdBlock.debug = (console.getCsvVal(3) > 0) ? true : false; + console.log(LOG_INFO, F("Storage: Debug = %d"), int(sdBlock.debug)); + break; + + case WII5COMMAND_STORAGE_SETSTATUS: + if (sdBlock.metadataUpdateStatus( // metadataBlock, mainRecordCount, 1); + console.getCsvVal(3), console.getCsvVal(4), console.getCsvVal(5) + ) + ) { + console.log(LOG_INFO, F("Storage: Status Field updated %lu,%lu,%lu"), console.getCsvVal(3), console.getCsvVal(4), console.getCsvVal(5)); + resultLast(WII5RESULTS_SUCCESS, F("StatusUpdated")); + } + else { + console.log(LOG_INFO, F("Storage: Status Field updated %lu,%lu,%lu"), console.getCsvVal(3), console.getCsvVal(4), console.getCsvVal(5)); + resultLast(WII5RESULTS_ERROR, F("StatusFailed")); + } + break; + + case WII5COMMAND_STORAGE_OFF: + resultLast(WII5RESULTS_SUCCESS, F("SD Off")); + console.log(LOG_INFO, F("SD: off")); + wii5Controller.setSDOff(); + break; + + case WII5COMMAND_STORAGE_SD1: + wii5Controller.setSD1(); + if (sdBlock.cardOpen(wii5Controller.getSD())) { + resultLast(WII5RESULTS_SUCCESS, F("SD 1 Open")); + console.log(LOG_INFO, F("SD: 1 - cardOpen - ON and Success")); + } + else { + resultLast(WII5RESULTS_ERROR, F("SD 1 Open")); + console.log(LOG_INFO, F("SD: 1 - cardOpen - Failure")); + } + break; + + case WII5COMMAND_STORAGE_SD2: + wii5Controller.setSD2(); + if (sdBlock.cardOpen(wii5Controller.getSD())) { + resultLast(WII5RESULTS_SUCCESS, F("SD 2 Open")); + console.log(LOG_INFO, F("SD: 2 - cardOpen - ON and Success")); + } + else { + resultLast(WII5RESULTS_ERROR, F("SD 2 Open")); + console.log(LOG_INFO, F("SD: 2 - cardOpen - Failure")); + } + break; + + case WII5COMMAND_STORAGE_LIST: + resultLast(WII5RESULTS_NOREPLY, F("No reply - StorageList")); + sdBlock.metadataList( + console.getCsvVal(3), + console.getCsvVal(4) > 0 ? console.getCsvVal(4) : 20 + ); + break; + + case WII5COMMAND_STORAGE_VIEW: + case WII5COMMAND_STORAGE_METADATA: + resultLast(WII5RESULTS_NOREPLY, F("No reply - Storage View")); + wii5Display.sdBlockView(console.getCsvVal(3), false, false); + break; + + case WII5COMMAND_STORAGE_RESULTS: + resultLast(WII5RESULTS_NOREPLY, F("No reply - Storage Results")); + wii5Display.sdBlockView(console.getCsvVal(3), true, false); + break; + + case WII5COMMAND_STORAGE_RAW: + resultLast(WII5RESULTS_NOREPLY, F("No reply - Storage Raw")); + wii5Display.sdBlockView(console.getCsvVal(3), true, true); + break; + + case WII5COMMAND_STORAGE_FORMAT: + if (wii5Controller.getSD() != 0) { + console.log(LOG_ERROR, F("SD Card is still active, and format may effect running capture or maths processing")); + } + + if (console.getCsvVal(3) == 1) { + wii5Controller.setSD1(); + } + else if (console.getCsvVal(3) == 2) { + wii5Controller.setSD2(); + } + else { + // FAIL - do not continue + resultLast(WII5RESULTS_INVALID, F("SD Format: SD Invalid")); // TODO get new size + return true; + } + + if (sdBlock.cardOpen(wii5Controller.getSD(), true)) { + console.log(LOG_DEBUG, F("SD: %d - cardFormat - Open"), wii5Controller.getSD()); + if (sdBlock.cardClose()) { + console.log(LOG_DEBUG, F("SD: %d - cardFormat - Complete"), wii5Controller.getSD()); + if (console.getCsvVal(3) == 1) + resultLast(WII5RESULTS_SUCCESS, F("SD format: 1 Complete")); // TODO get new size + else + resultLast(WII5RESULTS_SUCCESS, F("SD format: 2 Complete")); // TODO get new size + } + else { + console.log(LOG_DEBUG, F("SD: %d - cardFormat - Failed format"), wii5Controller.getSD()); + if (console.getCsvVal(3) == 1) + resultLast(WII5RESULTS_ERROR, F("SD Format Failed 1")); // TODO get new size + else + resultLast(WII5RESULTS_ERROR, F("SD Format Failed 2")); // TODO get new size + } + } + break; + + // @WII5,setting,time,1988,01,28,01,02,03 + case WII5COMMAND_SETTING_TIME: + when = now(); + setTime( + int(console.getCsvVal(6)), int(console.getCsvVal(7)), int(console.getCsvVal(8)), + int(console.getCsvVal(5)), int(console.getCsvVal(4)), int(console.getCsvVal(3)) + ); + + // Ignore anything less than 1 second (is there a way to do before setTime) (10 secconds) + // TODO configurable... 10 seconds safe? + // TODO abs - does it work with long + if ( ( (now() - when) > 10 ) || ( when - now() > 10) ) { + console.log(LOG_INFO, F("COMMAND: setTime RECEIVED = %04d-%02d-%02dT%02d:%02d:%02d - updating local and RTC"), + int(console.getCsvVal(3)), int(console.getCsvVal(4)), int(console.getCsvVal(5)), + int(console.getCsvVal(6)), int(console.getCsvVal(7)), int(console.getCsvVal(8)) + ); + #ifdef WII5_RTC + wii5RTC.setRTC(now()); + #endif + } + break; + + case WII5COMMAND_SETTING_LIST: + wii5Config.dump(true, NULL); + break; + + case WII5COMMAND_SETTING_DEVICEID: + // TODO Move to function here for settings or bertter, to Config + console.log(LOG_INFO, F("COMMAND: Setting DeviceID = %lu"), console.getCsvVal(3)); + // Limits - 10000 - 99999 + if (console.getCsvVal(3) == 0) { + console.log(LOG_INFO, F("Support services")); + resultLast(WII5RESULTS_INVALID, F("ID: No ID")); + } + else if ( + (console.getCsvVal(3) < 10000) + || (console.getCsvVal(3) > 99999) + ) { + console.log(LOG_ERROR, F("Unable to set DeviceID to '%lu' should be between 10000 - 99999"), console.getCsvVal(3)); + resultLast(WII5RESULTS_INVALID, F("ID: Bad ID")); + } + else { + wii5Config.setDeviceId(console.getCsvVal(3)); + lastCommandResult = WII5RESULTS_SUCCESS; + snprintf_P( + lastCommandMessage, sizeof(lastCommandMessage), + PSTR("ID: set=%lu"), + wii5Config.getDeviceId() + ); + } + break; + + case WII5COMMAND_SETTING_GPSTIMEOUT: + resultLast(WII5RESULTS_NOREPLY, F("No reply - gpstimeout")); // TODO Maybe? + console.log(LOG_INFO, F("COMMAND: Setting GPSTimeout = %lu"), console.getCsvVal(3)); + if (console.getCsvVal(3) == 0) { + console.log(LOG_INFO, F("COMMAND: Existing GPSTimeout = %lu"), console.getCsvVal(3)); + } + else if ( + (console.getCsvVal(3) < 30) + || (console.getCsvVal(3) > 900) + ) { + console.log(LOG_ERROR, F("COMMAND: Unable to set GPSTimeouta")); + } + else { + wii5Config.setGpsTimeout(console.getCsvVal(3)); + } + break; + + case WII5COMMAND_SETTING_DEFAULTS: + console.log(LOG_INFO, F("COMMAND: Setting config back to defaults")); + if (console.getCsvVal(3) == 3759) { + console.log(LOG_INFO, F("COMMAND: reset counters = %lu"), console.getCsvVal(3)); + wii5Config.resetCounters(); + resultLast(WII5RESULTS_SUCCESS, F("CountersReset")); // TODO Maybe? + } + else { + // Reset Config Defaults... Then allow override + wii5Config.resetConfigData(); + wii5Config.resetStatusData(); + wii5Controller.statusDump(); + resultLast(WII5RESULTS_SUCCESS, F("ConfigDefaults")); // TODO Maybe? + } + break; + + case WII5COMMAND_SEND: + { + // Send any commands + // IMPORTANT - do not reply command here ! + // strncpy(wii5Commands.lastCommandMessage, buf, bufSize < 235 ? bufSize, 235); + memset(wii5Commands.lastCommandMessage, 0, sizeof(wii5Commands.lastCommandMessage)); + char *pos = wii5Commands.lastCommandMessage; + uint16_t used = 0; + for (uint8_t i = 2; i < console.getCsvCount(); i++) { + strncpy(pos, console.getCsvBuffer(i), sizeof(wii5Commands.lastCommandMessage) - used); + pos += strlen(console.getCsvBuffer(i)); + used += strlen(console.getCsvBuffer(i)); + if (used < sizeof(wii5Commands.lastCommandMessage)) { + *pos = ','; + pos++; + used++; + } + } + wii5Commands.lastCommandMessage[sizeof(wii5Commands.lastCommandMessage) - 1] = '\0'; + console.log(LOG_FATAL, F("SEND Text Received: %s"), wii5Commands.lastCommandMessage); + wii5Communications.sendText(); + } + break; + + case WII5COMMAND_SET: + // Special case + // - set,capture,{period},{binary} - And set the default mode + // - set,sleep,{period},{binary},{until} - And set the default mode + { + WII5_MODES newMode; + newMode = wii5Strings.parseMode(console.getCsvBuffer(2)); + if (newMode == WII5MODE_NONE) { + console.log(LOG_ERROR, F("COMMAND: Invalid mode '%s'"), console.getCsvBuffer(4)); + resultLast(WII5RESULTS_INVALID, F("InvalidMode")); + } + else { + console.log(LOG_DEBUG, F("COMMAND: Parsed mode = %d (%s)"), newMode, wii5Strings.strMode(newMode)); + + switch(newMode) { + case WII5MODE_SLEEP: + wii5Config.setSleepPeriod(console.getCsvVal(3)); + wii5Config.setSleepBinaryType(console.getCsvVal(4)); + wii5Config.setDefaultMode(newMode); + lastCommandResult = WII5RESULTS_SUCCESS; + snprintf_P( + lastCommandMessage, sizeof(lastCommandMessage), + PSTR("M=sleep,P=%lu,D=%lu"), + wii5Config.getSleepPeriod(), wii5Config.getSleepBinaryType() + ); + break; + case WII5MODE_CAPTURE: + wii5Config.setCapturePeriod(console.getCsvVal(3)); + wii5Config.setCaptureBinaryType(console.getCsvVal(4)); + wii5Config.setCaptureRecords(5120); + lastCommandResult = WII5RESULTS_SUCCESS; + wii5Config.setDefaultMode(newMode); + snprintf_P( + lastCommandMessage, sizeof(lastCommandMessage), + PSTR("M=capture,P=%lu,D=%lu"), + wii5Config.getCapturePeriod(), wii5Config.getCaptureBinaryType() + ); + break; + case WII5MODE_POSITION: + wii5Config.setPositionPeriod(console.getCsvVal(3)); + wii5Config.setPositionBinaryType(console.getCsvVal(4)); + wii5Config.setDefaultMode(newMode); + lastCommandResult = WII5RESULTS_SUCCESS; + snprintf_P( + lastCommandMessage, sizeof(lastCommandMessage), + PSTR("M=position,P=%lu,D=%lu"), + wii5Config.getPositionPeriod(), wii5Config.getPositionBinaryType() + ); + break; + default: + resultLast(WII5RESULTS_INVALID, F("Not sleep, position or capture")); + break; + }; + console.log(LOG_FATAL, F("Default mode saved, updating current mode to match")); + wii5Controller.setDefaultMode(); + } + } + break; + + case WII5COMMAND_SETTING_MODE: + { + bool defMode = false; + time_t expires = 0; + if (strcmp_P(console.getCsvBuffer(3),(PGM_P) F("default")) == 0) { + console.log(LOG_INFO, F("COMMAND: Setting MODE=Defalut")); + defMode = true; + } + else { + console.log(LOG_INFO, F("COMMAND: Setting MODE=Temporary datetime=%s"), console.getCsvBuffer(3)); + defMode = false; + // TODO Parse valid date time etc + // TODO Do we have a valid time. Woot ! + // TODO Set expires in... (max 4 weeks?) + expires = 123; + } + + WII5_MODES newMode; + if (strcmp_P(console.getCsvBuffer(4),(PGM_P) F("swap")) == 0) { + // NOTE: Onl default mode for now + if (wii5Config.getDefaultMode() == WII5MODE_CAPTURE) { + console.log(LOG_INFO, F("COMMAND: Swap - moving from Capture to Sleep mode")); + newMode = WII5MODE_SLEEP; + } + else { + console.log(LOG_ERROR, F("COMMAND: Swap - moving from to capture mode")); + newMode = WII5MODE_CAPTURE; + } + } + else { + newMode = wii5Strings.parseMode(console.getCsvBuffer(4)); + } + + if (newMode == WII5MODE_NONE) { + console.log(LOG_ERROR, F("COMMAND: Invalid mode '%s'"), console.getCsvBuffer(4)); + resultLast(WII5RESULTS_INVALID, F("InvalidMode")); + } + else { + console.log(LOG_DEBUG, F("COMMAND: Parsed mode = %d (%s)"), newMode, wii5Strings.strMode(newMode)); + + if (defMode) { + console.log(LOG_INFO, F("Default mode set to = %d (%s)"), newMode, wii5Strings.strMode(newMode)); + wii5Config.setDefaultMode(newMode); + resultLast(WII5RESULTS_SUCCESS, F("mode=")); // Add new mode + } + } + + // IF mode=capture, optionally set Period and Binary Type + + // IF mode=position, optionally set Periods and Binary Type + + // IF mode=sleep??? + console.log(LOG_FATAL, F("Default mode saved, updating current mode to match")); + wii5Controller.setDefaultMode(); + wii5Maths.setHold(900); + } + break; + + case WII5COMMAND_SETTING_BATTERYOPTIONS: + resultLast(WII5RESULTS_NOREPLY, F("No reply")); + console.log(LOG_INFO, F("COMMAND: Setting Battery Low=%lu, Mide=%lu"), console.getCsvVal(3), console.getCsvVal(4)); + wii5Config.setBatteryLow(console.getCsvVal(3)); + wii5Config.setBatteryMid(console.getCsvVal(4)); + break; + + case WII5COMMAND_SETTING_DISABLELOWBATTERY: + console.log(LOG_INFO, F("COMMAND: Setting Disable Low Battery = %lu"), console.getCsvVal(3)); + /* + if ( + (console.getCsvVal(3) < 10000) + || (console.getCsvVal(3) > 99999) + ) { + console.log(LOG_ERROR, F("Unable to set DeviceID to '%lu' should be between 10000 - 99999"), console.getCsvVal(3)); + } + else { + } + */ + wii5Config.setDisableLowBattery(console.getCsvVal(3)); + break; + + case WII5COMMAND_SETTING_FLAGS: + // ALL or ONE + // wii5Config.setDisableLowBattery(console.getCsvVal(3)); + break; + + case WII5COMMAND_SETTING_CAPTUREOPTIONS: + console.log(LOG_INFO, F("COMMAND: Setting Capture Options Period=%lu minute, Flags=%lu, Records=%lu"), console.getCsvVal(3), console.getCsvVal(4), console.getCsvVal(5)); + // TODO - do we need limits here too? + if ( + (console.getCsvVal(3) >= 5) // 15 Minutes TODO 15 + && (console.getCsvVal(3) <= 720) // to 12 hours + ) { + wii5Config.setCapturePeriod(console.getCsvVal(3)); + } + // TODO Else error + if (console.getCsvVal(4) >= 0) { + wii5Config.setCaptureBinaryType(console.getCsvVal(4)); + } + if (console.getCsvVal(5) >= 0) { + wii5Config.setCaptureRecords(console.getCsvVal(5)); + } + break; + + case WII5COMMAND_SETTING_POSITIONOPTIONS: + console.log(LOG_INFO, F("COMMAND: Setting Position Options = %lu,%lu"), console.getCsvVal(3), console.getCsvVal(4)); + if ( + (console.getCsvVal(3) >= 3) // 3 Minutes + && (console.getCsvVal(3) <= 720) // to 12 hours + ) { + wii5Config.setPositionPeriod(console.getCsvVal(3)); + } + if (console.getCsvVal(4) >= 0) { + wii5Config.setPositionBinaryType(console.getCsvVal(4)); + } + break; + + case WII5COMMAND_SETTING_SLEEPOPTIONS: + console.log(LOG_INFO, F("COMMAND: Setting Sleep Options = %lu,%lu"), console.getCsvVal(3), console.getCsvVal(4)); + if ( + (console.getCsvVal(3) >= 2) // 2 minutes + && (console.getCsvVal(3) <= 5760) // 4 days + ) { + wii5Config.setSleepPeriod(console.getCsvVal(3)); + } + if (console.getCsvVal(4) >= 0) { + wii5Config.setSleepBinaryType(console.getCsvVal(4)); + } + break; + + case WII5COMMAND_RESET: + if (console.getCsvVal(2) == 666) { + console.log(LOG_FATAL, F("COMMAND: Reset...")); + SerialConsole.flush(); + sh3dNodeUtil.reset(); + } + else { + console.log(LOG_FATAL, F("COMMAND: Reset failed.... did you know the code...")); + } + break; + + #ifdef WII5_GPS + case WII5COMMAND_GPS_START: + console.log(LOG_INFO, F("GPS: Start")); + wii5Gps.on(); + break; + + case WII5COMMAND_GPS_STOP: + console.log(LOG_INFO, F("GPS: Stop")); + wii5Gps.off(); + break; + + case WII5COMMAND_GPS_DUMP: + console.log(LOG_INFO, F("GPS: Dump")); + wii5Gps.dump(); + break; + + case WII5COMMAND_GPS_PASSTHROUGH: + console.log(LOG_INFO, F("GPS: Passthrough set to %d"), console.getCsvVal(3)); + wii5Gps.setPassthrough(console.getCsvVal(3) ? 1 : 0); + break; + case WII5COMMAND_GPS_DEBUG: + console.log(LOG_INFO, F("GPS: Debug set to %d"), console.getCsvVal(3)); + wii5Gps.setDebug(console.getCsvVal(3) ? 1 : 0); + break; + case WII5COMMAND_GPS_TIME: + console.log(LOG_INFO, F("GPS: autoTime")); + wii5Gps.autoTime(); + break; + case WII5COMMAND_GPS_POS: + console.log(LOG_INFO, F("GPS: autoPos")); + wii5Gps.autoPos(); + break; + case WII5COMMAND_GPS_ACCURATE: + console.log(LOG_INFO, F("GPS: autoAccurate")); + wii5Gps.autoAccurate(); + break; + + #endif + + case WII5COMMAND_SPARTON_MODE: + //TODO move logic to Sparton + console.log(LOG_DEBUG, F("COMMAND: SPARTON Mode - Not implemented")); + break; + + case WII5COMMAND_SPARTON_DUMP: + //TODO move logic to Sparton + console.log(LOG_DEBUG, F("COMMAND: SPARTON Dump - Not implemented")); + break; + + case WII5COMMAND_SPARTON_INFO: + console.log(LOG_DEBUG, F("COMMAND: SPARTON Info")); + wii5Sparton.info(); + break; + + case WII5COMMAND_SPARTON_PASSTHROUGH: + console.log(LOG_INFO, F("Sparton: Passthrough set to %d"), console.getCsvVal(3)); + wii5Sparton.setPassthrough(console.getCsvVal(3) ? 1 : 0); + break; + + case WII5COMMAND_SPARTON_DEBUG: + console.log(LOG_INFO, F("Sparton: Debug set to %d"), console.getCsvVal(3)); + wii5Sparton.setDebug(console.getCsvVal(3) ? 1 : 0); + break; + + // TODO @WII5,maths - seems in both directions. That is bad. + // TODO From Maths, should be @Maths not @WII5 or @WII5Maths or something + case WII5COMMAND_MATHS_BEEP: + wii5Maths.woops_on(); + if(strcmp_P(console.getCsvBuffer(3),(PGM_P) F("once")) == 0) { + console.log(LOG_DEBUG, F("COMMAND: Maths Beep - once")); + wii5Controller.shared5On(); + sh3dNodeIO.led2Set(LED_ONCE); + } + else if(strcmp_P(console.getCsvBuffer(3),(PGM_P) F("quiet")) == 0) { + // Special case - check the mode + wii5Controller.shared5On(); + if (! wii5Maths.isMode(WII5MATHSMODE_AUTO)) { + console.log(LOG_DEBUG, F("COMMAND: Maths Beep - ONCE")); + sh3dNodeIO.led2Set(LED_ONCE); + } + else { + console.log(LOG_DEBUG, F("COMMAND: Maths Beep - NORMAL")); + sh3dNodeIO.led2Set(LED_SHORT_LONG); + } + } + else { + console.log(LOG_DEBUG, F("COMMAND: Maths Beep")); + wii5Controller.shared5On(); + sh3dNodeIO.led2Set(LED_SHORT_LONG); + } + break; + + case WII5COMMAND_MATHS_INFO: + resultLast(WII5RESULTS_SUCCESS, F("Info Queued")); + wii5Maths.woops_on(); + console.log(LOG_INFO, F("MATHS: Info")); + wii5Maths.queueCommand("info", 8); + break; + + case WII5COMMAND_MATHS_BOOTED: + console.log(LOG_DEBUG, F("MathsBooted - flag for moving on")); + wii5Maths.woops_on(); + wii5Controller.setLastHello(WII5PORT_CONSOLE, WII5DEVICE_MATHS); + break; + + case WII5COMMAND_MATHS_RETURN: + if (console.getCsvVal(3) == 0) { + console.log(LOG_INFO, F("Maths - RETURN line - OFF")); + wii5Maths.cancelReturnLine(); + } + else if (console.getCsvVal(3) == 1) { + console.log(LOG_INFO, F("Maths - RETURN line - ON")); + wii5Maths.setReturnLine(); + } + else { + console.log(LOG_INFO, F("Maths - RETURN line - Unknown")); + } + break; + + case WII5COMMAND_MATHS_FLIP: + console.log(LOG_FATAL, F("COMMAND: Maths Flip (setting maths to hold 1 hour)")); + wii5Maths.setHold(3600); + wii5ModeCapture.flip(); + wii5Controller.statusDump(); + break; + + case WII5COMMAND_MATHS_RESTART: + if(strcmp_P(console.getCsvBuffer(3),(PGM_P) F("start")) == 0) { + console.log(LOG_INFO, F("MATHS: REBOOT - start")); + } + else if(strcmp_P(console.getCsvBuffer(3),(PGM_P) F("stop")) == 0) { + console.log(LOG_INFO, F("MATHS: REBOOT - stop")); + } + else if(strcmp_P(console.getCsvBuffer(3),(PGM_P) F("update")) == 0) { + console.log(LOG_INFO, F("MATHS: REBOOT - rebooting in: %lu seconds"), console.getCsvVal(4)); + } + else if(strcmp_P(console.getCsvBuffer(3),(PGM_P) F("now")) == 0) { + console.log(LOG_INFO, F("MATHS: REBOOT - rebooting now")); + } + break; + + case WII5COMMAND_MATHS_START: + console.log(LOG_INFO, F("COMMAND: Maths Start")); + // TODO - Other? + wii5Maths.start(WII5MATHSMODE_COMMS); + wii5Controller.statusDump(); + break; + + case WII5COMMAND_MATHS_HALT: + case WII5COMMAND_MATHS_STOP: + console.log(LOG_INFO, F("COMMAND: Maths Stop/Halt")); + wii5Maths.stop(); + wii5Controller.statusDump(); + break; + + case WII5COMMAND_MATHS_HOLD: + wii5Maths.woops_on(); + console.log(LOG_INFO, F("COMMAND: Maths Hold %d seconds"), console.getCsvVal(3)); + wii5Maths.setHold(console.getCsvVal(3)); + wii5Controller.statusDump(); + break; + + case WII5COMMAND_MATHS_UNTIL: + console.log(LOG_INFO, F("COMMAND: Maths Until time"), console.getCsvVal(3)); + wii5Maths.setMathsUntil(console.getCsvVal(3)); + wii5Controller.statusDump(); + break; + + // TODO Need to start maths, make sure it is fullly working then send the command to Maths,do etc. + // TODO how to send responses in that case? - Multiple + case WII5COMMAND_MATHS_DO: + break; + case WII5COMMAND_MATHS_MAKE: + break; + + case WII5COMMAND_MATHS_DONE: + wii5Maths.queueDone(); + break; + + case WII5COMMAND_MATHS_SCRIPT: + if (console.getCsvVal(3) == 1) { + console.log(LOG_INFO, F("Queue: test one,two")); + wii5Maths.queueCommand("test,one", 8); + wii5Maths.queueCommand("test,two", 8); + } + else if (console.getCsvVal(3) == 2) { + console.log(LOG_INFO, F("Queue: test one,two")); + wii5Maths.queueCommand("do,", 8); + wii5Maths.queueCommand("test,two", 8); + } + else if (console.getCsvVal(3) == 3) { + console.log(LOG_INFO, F("Queue: Maths recompile")); + wii5Maths.queueCommand("makeextra,-DDEBUG_BUILD=1", 25); + } + else { + console.log(LOG_INFO, F("Queue: Maths - bogus commands")); + wii5Maths.queueCommand("DoesNone", 8); + } + break; + + case WII5COMMAND_DUMP: + //TODO move logic to Controller or Display !!! + console.log(LOG_INFO, F("COMMAND: Dump ALL - Not implemented")); + break; + + // Network tools + case WII5COMMAND_NETWORK_ECHO: + //TODO move logic to Controller or Network + console.log(LOG_DEBUG, F("Network: sendEcho (CONSOLE)")); + #ifdef WII5_RADIO_LORA + sh3dNodeNetwork.sendEcho(); + #endif + break; + case WII5COMMAND_NETWORK_ECHOACK: + //TODO move logic to Controller or Network + #ifdef WII5_RADIO_LORA + console.log(LOG_DEBUG, F("Network: sendACK (for Echo)")); + sh3dNodeNetwork.sendACK(SH3D_NODE_Packet_Echo_packetType_ACK, SH3D_NODE_Packet_Echo_packetVersion); + #endif + break; + case WII5COMMAND_NETWORK_FAKEECHO: + //TODO move logic to Controller or Network + #ifdef WII5_RADIO_LORA + console.log(LOG_DEBUG, F("Network: Faking an echo")); + // NOTE: Used to create Fake Packets - should be in a Test Library + // TODO move to RadioLoRa + sh3dNodeRadio.lastLength = sizeof(SH3D_NODE_Packet_Echo); + sh3dNodeNetwork.localEcho->header.node.nodeType = 1; + sh3dNodeNetwork.localEcho->header.node.nodeId = 2; + sh3dNodeNetwork.localEcho->packetType = SH3D_NODE_Packet_Echo_packetType; + sh3dNodeNetwork.localEcho->packetVersion = SH3D_NODE_Packet_Echo_packetVersion; + sh3dNodeNetwork.localEcho->key = 9; + sh3dNodeNetwork.localEcho->val = 3; + sh3dNodeRadio.lastValid = true; + #endif + break; + case WII5COMMAND_NETWORK_FAKEECHOACK: + //TODO move logic to Controller or Network + #ifdef WII5_RADIO_LORA + console.log(LOG_DEBUG, F("Network: Faking an echo ack")); + // NOTE: Used to create Fake Packets - should be in a Test Library + // TODO move to RadioLoRa + sh3dNodeRadio.lastLength = sizeof(SH3D_NODE_Packet_ACK); + sh3dNodeNetwork.localACK->header.node.nodeType = 1; + sh3dNodeNetwork.localACK->header.node.nodeId = 2; + sh3dNodeNetwork.localACK->packetType = SH3D_NODE_Packet_Echo_packetType_ACK; + sh3dNodeNetwork.localACK->packetVersion = SH3D_NODE_Packet_Echo_packetVersion; + sh3dNodeRadio.lastValid = true; + #endif + break; + + case WII5COMMAND_CAPTURE_START: + console.log(LOG_DEBUG, F("Capture: ManualStart requested (if not already running)")); + wii5Controller.setMode(WII5MODE_CAPTURE); + wii5ModeCapture.start(); + break; + + case WII5COMMAND_CAPTURE_STOP: + console.log(LOG_DEBUG, F("Capture: ManualStop requested (if running)")); + wii5Controller.setMode(WII5MODE_CAPTURE); + wii5ModeCapture.stop(); + break; + + case WII5COMMAND_POSITION_START: + console.log(LOG_DEBUG, F("Position: ManualStart requested (if not already running)")); + wii5Controller.setMode(WII5MODE_POSITION); + wii5ModePosition.start(); + break; + + case WII5COMMAND_POSITION_STOP: + console.log(LOG_DEBUG, F("Position: ManualStop requested (if running)")); + wii5Controller.setMode(WII5MODE_POSITION); + wii5ModePosition.stop(); + break; + + case WII5COMMAND_POSITION_PASSTHROUGH: + console.log(LOG_DEBUG, F("Position: Passthrough toggle")); + wii5Gps.setPassthrough(console.getCsvVal(3) ? 1 : 0); + wii5Gps.setDebug(console.getCsvVal(3) ? 1 : 0); + wii5Iridium.setPassthrough(console.getCsvVal(3) ? 1 : 0); + wii5Iridium.setDebug(console.getCsvVal(3) ? 1 : 0); + break; + + case WII5COMMAND_MODE_POSITION: + console.log(LOG_DEBUG, F("Set: Mode=Position")); + wii5Controller.setMode(WII5MODE_POSITION); + break; + + case WII5COMMAND_MODE_MANUALTEST: + console.log(LOG_DEBUG, F("Set: Mode=ManualTest")); + wii5Controller.setMode(WII5MODE_MANUALTEST); + break; + + case WII5COMMAND_MODE_SELFTEST: + console.log(LOG_DEBUG, F("Set: Mode=SelfTest")); + if (strcmp_P(console.getCsvBuffer(3),(PGM_P) F("frommaths")) == 0) { + wii5ModeSelfTest.setMode(WII5SM_FROMMATHS); + } + else if (strcmp_P(console.getCsvBuffer(3),(PGM_P) F("nomaths")) == 0) { + wii5ModeSelfTest.setMode(WII5SM_NOMATHS); + } + wii5Controller.setMode(WII5MODE_SELFTEST); + break; + + // TODO This should realy be CURRENt, ie, it could be the temp one + case WII5COMMAND_MODE_DEFAULT: + console.log(LOG_DEBUG, F("Set: Mode=Default")); + wii5Controller.setDefaultMode(); + break; + + case WII5COMMAND_MODE_CAPTURE: + console.log(LOG_DEBUG, F("Set: Capture Mode")); + wii5Controller.setMode(WII5MODE_CAPTURE); + break; + + case WII5COMMAND_MODE_SLEEP: + console.log(LOG_DEBUG, F("Set: Sleep Mode (Note: Maths hold set to 5 minutes)")); + wii5Controller.setMode(WII5MODE_SLEEP); + // We must have set this from serial, lets leave MAths hold 5 mins + wii5Maths.setHold(900); + break; + + case WII5COMMAND_BINDATA_LIST: + // TODO Check if we need reply..., where to store it? New var I think + console.log(LOG_DEBUG, F("BinData: List")); + wii5BinData.showSizes(lastCommandMessage, WII5_RESULT_MESSAGE); + break; + + case WII5COMMAND_BINDATA_SPLIT: + // TODO Check if we need reply..., where to store it? New var I think + console.log(LOG_DEBUG, F("BinData: Split")); + wii5BinData.showSplit(console.getCsvVal(3), (uint16_t)console.getCsvVal(4), lastCommandMessage, WII5_RESULT_MESSAGE); + break; + + case WII5COMMAND_WEATHER_READ: + console.log(LOG_DEBUG, F("Weather: Reading temperature")); + wii5Weather_18B20.temperatureRead(); + break; + + case WII5COMMAND_WEATHER_TEST: + console.log(LOG_DEBUG, F("Weather: Test Loop")); + { + for (uint16_t wtest = 0; wtest < 100; wtest++) { + console.log(LOG_INFO, F("Weather: Test Loop: n=%d"), wtest); + wii5Weather_18B20.temperatureRead(); + } + } + break; + + case WII5COMMAND_IRIDIUM_SEND: + console.log(LOG_DEBUG, F("Iridium: Send Text")); + wii5Iridium.setPassthrough(1); // TODO Should be configurable + // TODO What text should be configurable + wii5Iridium.setText(F("Longer example text")); + wii5Iridium.requestSendText(); + break; + + case WII5COMMAND_COMMUNICATIONS_DISABLED: + console.log(LOG_DEBUG, F("Communication: Disabled")); + wii5Communications.setDisabled(console.getCsvVal(3) ? 1 : 0); + break; + + case WII5COMMAND_COMMUNICATIONS_TEST: + console.log(LOG_DEBUG, F("Communication: Test - once through test mode")); + wii5Communications.setAutoMode(); + wii5Communications.start(); + wii5Communications.setTESTING(); + break; + + case WII5COMMAND_COMMUNICATIONS_SIGNAL: + console.log(LOG_DEBUG, F("Communication: Signal (SD Card change)")); + wii5Communications.signalSD(); + break; + + case WII5COMMAND_COMMUNICATIONS_START: + console.log(LOG_DEBUG, F("Communication: Start")); + wii5Communications.start(); + break; + + case WII5COMMAND_COMMUNICATIONS_STOP: + console.log(LOG_DEBUG, F("Communication: Stop")); + wii5Communications.stop(); + break; + + case WII5COMMAND_BATTERY_START: + console.log(LOG_DEBUG, F("Battery: start")); + wii5Battery.start(); + break; + + default: + console.log(LOG_INFO, F("Command @ not yet implemented = '%s'."), wii5Strings.strCommand(cmd)); + return false; + break; + } + return true; +} + +// doneLast - flag that the entry is now done. +void WII5Commands::doneLast() { + lastCommandAckComplete = true; +} + +// setupLast - set original last entry data +// TODO - Manually need to set: +// lastCommandCmd = cmd; lastCommandParam1 = 0; lastCommandParam2 = 0; lastCommandParam3 = 0; +// lastCommandResult = WII5RESULTS_UNKNOWN; memset(&lastCommandMessage, 0, sizeof(lastCommandMessage)); +void WII5Commands::setupLast(WII5_DEVICES device, WII5_PORTS port, WII5_AREAS area, WII5_COMMANDS cmd) { + if (!lastCommandAckComplete) { + console.log(LOG_DEBUG, F("Warning. Last message did not deliver and ACK, it will have been lost.")); + // TODO Details - log whole of lastCommand + } + + // When and Where + lastCommandAge = 0; + lastCommandT = now(); + lastCommandDevice = device; + lastCommandPort = port; + lastCommandArea = area; + + // Cleanup + lastCommandCmd = cmd; + lastCommandParam1 = 0; + lastCommandParam2 = 0; + lastCommandParam3 = 0; + + // Results ? + lastCommandResult = WII5RESULTS_UNKNOWN; + memset(&lastCommandMessage, 0, sizeof(lastCommandMessage)); + + // ackComplete + lastCommandAckComplete = false; +} + +void WII5Commands::resultLast(WII5_RESULTS result, const __FlashStringHelper *message) { + lastCommandResult = result; + strncpy_P(lastCommandMessage, (const char *)message, sizeof(lastCommandMessage)); +} +void WII5Commands::resultLast(WII5_RESULTS result, char *message) { + lastCommandResult = result; + strncpy(lastCommandMessage, message, sizeof(lastCommandMessage)); +} +void WII5Commands::resultLast(WII5_RESULTS result) { + lastCommandResult = result; +} + +/* +// resultLast(...) +lastCommandResult = X +snprintf_P( + lastCommandMessage, sizeof(lastCommandMessage), + PSTR(""), + getDeviceId(), getRecordCount() +); +*/ + +// Work out that we have a WII5,X where X is the result +bool WII5Commands::processConsoleAT() { + if (!console.available() || (console.getCommand() != '@')) + return false; + + // TODO repeat this for other systems - e.g. parsing network or iridium + if (strcmp_P(console.getCsvBuffer(0),(PGM_P) F("WII5")) == 0) { + return runCommand( + wii5Strings.parseCommand( + console.getCsvBuffer(1), + console.getCsvBuffer(2), + console.getCsvBuffer(3) + ) + ); + } + else if (strcmp_P(console.getCsvBuffer(0),(PGM_P) F("Help")) == 0) { + return wii5Help.processConsoleCsv(); + } + return false; +} + +// Return false if there IS a command and it was not handled +bool WII5Commands::processConsoleStandard() { + if (console.available()) { + switch(console.getCommand()) { + // Help - Low level help before full commands + case 'h': + case 'H': + console.log(LOG_INFO, F("WII5: Simplified command and help system")); + console.log(LOG_FATAL, F(" NOTE: you may need to enable logs @WII5,log,all")); + console.log(LOG_DEBUG, F(" See @Help for full commands")); + console.log(LOG_DEBUG, F(" * - show all pins")); + console.log(LOG_DEBUG, F(" @WII5,maths,start|stop - immediate start or stop")); + console.log(LOG_DEBUG, F(" @WII5,maths,hold,300 - hold for 5 minutes")); + console.log(LOG_DEBUG, F(" @WII5,mode,manualtest - Immediate switch to manual test mode")); + console.log(LOG_DEBUG, F(" @WII5,capture,start|stop - start or stop a capture, also switches mode")); + console.log(LOG_DEBUG, F(" @WII5,mode,sleep - sleep mode.")); + console.log(LOG_DEBUG, F(" Z - force a reset now (must add 666)")); + console.log(LOG_DEBUG, F(" . - set default mode")); + console.log(LOG_DEBUG, F(" ? - Status")); + break; + + // RESET. usual Z666; + case 'Z': + if (console.getVal() == 666) { + sh3dNodeUtil.reset(); + } + break; + + // T: for manualTest Mode + case 'T': + // TODO console.log(LOG_FATAL, F("Old testmode. Inbstead = @WII5,mode,manualtest !!!")); + console.log(LOG_FATAL, F("MANUALTEST")); + wii5Controller.setMode(WII5MODE_MANUALTEST); + break; + + // s: Sleep mode + case 's': + console.log(LOG_ERROR, F("Old sleepmode. Inbstead = @WII5,mode,sleep !!!")); + break; + + // C for CAPTURE Mode + case 'C': + console.log(LOG_ERROR, F("Old capturemode. Inbstead = @WII5,mode,capture, @WII5,capture,start, @WII5,capture,stop !!!")); + break; + + case 'W': + // TODO Danger ! Move this elsewhere and update Documents + SerialConsole.print(F("WARNING - Disabled external WDT reset pin - runCount=")); + SerialConsole.println(sh3dNodeConfig.getRunCount()); + wii5Controller.tempDisableWDT = true; + break; + + case '*': + wii5Display.dumpPins(); + break; + + // Status etc + case '.': + wii5Controller.setDefaultMode(); + break; + + case ',': + wii5Display.printMetadata(); + break; + + case '?': + wii5Controller.statusDump(true); + break; + + default: + return false; + break; + } + } + return true; +} + +WII5Commands wii5Commands; diff --git a/WII5Commands.h b/WII5Commands.h new file mode 100644 index 0000000..e7859a6 --- /dev/null +++ b/WII5Commands.h @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Commands.h + * @brief Command protocol handler: parses and dispatches @-prefixed commands. + */ + +#ifndef WII5Commands_h +#define WII5Commands_h + +#include +#include + +/** + * @brief Text-protocol command parser and dispatcher. + * + * Accepts commands from multiple inputs (local console, Iridium SBD, + * radio, buttons) in two syntaxes: + * - "@AREA,cmd,arg1,arg2,..." (the AT-style protocol) + * - "X;" single-letter "standard" style (used in manual test mode) + * + * Records the most recent command in lastCommand* members so result and + * acknowledge replies can refer back to it. + */ +class WII5Commands : public WII5 { + public: + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_COMMANDS;} + /** @brief One-time bring-up. */ + void begin(); + + /** @brief Feed an AT-style command into the command pipeline as if it came from the console. */ + bool injectCommand(char *sendCmd); + + /** @brief Drain pending "X;" single-letter commands from the console. */ + bool processConsoleStandard(); + + /** @brief Drain pending "@AREA,cmd,..." AT-style commands from the console. */ + bool processConsoleAT(); + + /** @brief Translate physical button events (click/long-hold) into commands. */ + bool processButtons(); + + /** @brief Drain pending packets from the LoRa network (if enabled). */ + bool processNetwork(); + + /** @brief Process any incoming BinData payload (typically from Iridium). */ + bool processBinData(); + + /** @brief Execute a parsed command; returns true on success. */ + bool runCommand(WII5_COMMANDS cmd, char** params = NULL, uint8_t paramsLen = 0); + /** @brief Record the source/area of the command currently being processed. */ + void setupLast(WII5_DEVICES device, WII5_PORTS port, WII5_AREAS area, WII5_COMMANDS cmd = WII5COMMAND_NONE); + /** @brief Finalize lastCommand bookkeeping (call after runCommand). */ + void doneLast(); + /** @brief Set the result + message for the last command (mutable buffer). */ + void resultLast(WII5_RESULTS result, char *message); + /** @brief Set the result + message for the last command (PROGMEM string). */ + void resultLast(WII5_RESULTS result, const __FlashStringHelper *area); + /** @brief Set the result for the last command without altering the message. */ + void resultLast(WII5_RESULTS result); + + /** @brief Print a debug dump of command state to the console. */ + void dump(); + + elapsedMillis disableButtons; + + // ------------------------------------------------------------------------------ + // lastCommand! + + // When? + elapsedMillis lastCommandAge; + time_t lastCommandT; + + // Who sent this? + WII5_PORTS lastCommandPort; + WII5_DEVICES lastCommandDevice; + + // Which area and commands was parsed (e.g. area=WII5) + WII5_AREAS lastCommandArea; + + // Main command - e.g. SETTINGS_DEVICEID + WII5_COMMANDS lastCommandCmd; + uint32_t lastCommandParam1; // Keep just the params in int format - should be enough + uint32_t lastCommandParam2; // Keep just the params in int format - should be enough + uint32_t lastCommandParam3; // Keep just the params in int format - should be enough + + // Result of command - success = yes/no + WII5_RESULTS lastCommandResult; + char lastCommandMessage[WII5_RESULT_MESSAGE]; // Error string + + // Acknowldge reply + bool lastCommandAckComplete; + + protected: + elapsedMillis delayWait; + time_t when; + + // ------------------------------------------------------------------------------ + // Button managmement + elapsedMillis buttonHeld; + elapsedMillis buttonReleaseWait; + bool buttonActive; + bool buttonReleased; + uint8_t buttonMode; + + // ------------------------------------------------------------------------------ +}; + +extern WII5Commands wii5Commands; +#endif diff --git a/WII5Communications.cpp b/WII5Communications.cpp new file mode 100644 index 0000000..1121269 --- /dev/null +++ b/WII5Communications.cpp @@ -0,0 +1,657 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Communications.cpp + * @brief High-level communications dispatcher: orchestrates Iridium send/receive flows. + */ + +/* + +TODO 2024 - This seems a little over complicated, any simplification possible + * List of results to send with current SD card??? + * Status - what to send what not to? + +TODO: Full testing, especially: + . Receiving multiple messages when only sending one + . New sdBlock code for loading and finding records + +Communications - Really in control of Iridium + + However, it may become more, startup of Weather and Voltage. + Possibly even read/write SD cared for data + + When is a message sent: + * By request - e.g. sendBinModeData + * Automatically - e.g. looking for unset results + * By time - if start is called and we get to JUMP, and it has been more than 12 hours. + (this last one covers the case where something has gotten stuck - e.g. Sparton Failure and no results to post) + +Template in another Mode / State machine + + Simple Mode - Just send messages requested, no looking at SD + + Start sending a message (don't call again until stopped) + wii5Communications.setSimpleMode(); + wii5Communications.sendBinModeType(1, 0); + wii5Communications.start(); + + while (!wii5Communications.isRunning()) { + Wait... + + + Complex Mode: + Start the background processing + wii5Communications.setAutoMode(); + + Be very careful of SD changes, power on/off/swap + Ideally, once you start - don't turn it off. + However, this may not always be practical. + Send signalSD when an SD Card is changed. + +*/ + +#include +#include +#include + +void WII5Communications::begin() { + step = WII5COMMUNICATIONS_IDLE; + lastStep = WII5COMMUNICATIONS_END; + wait = 0; + autoMode = false; + metadataScan = false; + testMode = false; + + mainBinType = 0; + metadataBlock = 0; + resultsBlock = 0; + mainRecordCount = 0; + + // Clear the request + requestBinType = 0; + requestMetadataBlock = 0; + requestResultsBlock = 0; + requestRecordCount = 0; + updateStatus = false; + countSucces = 0; + countFailed = 0; + + // Find Request - these should happen before other requests or auto searches + findBinType = 0; + findRecordCount = 0; // When it finds this record it will select this address and the correct Results record + + // TODO 2024 - Disabled - this is unsafe. Can we make it so there is a maimum - look at other loops + disabled = false; +} + +// NOTE: Call start to clear +void WII5Communications::setTESTING() { testMode = true; } + +// TODO 2024 - New code to disable - must timeout !!! +bool WII5Communications::setDisabled(bool in) { + disabled = in; + if (disabled) { + step = WII5COMMUNICATIONS_DISABLED; + console.log(LOG_INFO, F("Communications: disabled on (do nothing)")); + } + else { + step = WII5COMMUNICATIONS_IDLE; + console.log(LOG_INFO, F("Communications: disabled off (move to idle)")); + } + wait = 0; + return disabled; +} +void WII5Communications::setAutoMode() { autoMode = true; } +void WII5Communications::setSimpleMode() { autoMode = false; } +bool WII5Communications::getAutoMode() { return autoMode; } +bool WII5Communications::getSimpleMode() { return !autoMode; } + +void WII5Communications::signalSD() { + // Reset that we definitely want a metadata scan + metadataScan = true; + + // disable existing scans - set them to 0 - as this is a new SD + sdCardId = 0; + sdStartBlock = 0; + sdCurrentBlock = 0; + + // Check the mode, and put it in appropriate place. + start(); +} + +void WII5Communications::findRecord(uint32_t t, uint32_t rc) { + // Set a type and Record Count to find, it will override the previous + findRecordCount = rc; + findBinType = t; +} + +void WII5Communications::sendText(void *buf = NULL, uint16_t bufSize = 0) { + if (buf && bufSize > 0) { + strncpy(wii5Commands.lastCommandMessage, buf, bufSize); // < 235 ? bufSize, 235); + } + uint32_t replyType = wii5BinData.setBit(0, 4); + sendBinModeType(replyType, 0, 0, 0); + setSimpleMode(); + console.log(LOG_INFO, F("Communications: sendText() message=%s"), wii5Commands.lastCommandMessage); + start(); +} + +void WII5Communications::sendError() { + uint32_t replyType = wii5BinData.setBit(0, 3); + wii5Commands.lastCommandResult = WII5RESULTS_ERROR; + sendBinModeType(replyType, 0, 0, 0); + setSimpleMode(); + console.log(LOG_INFO, F("Communications: sendError() message=%s"), wii5Commands.lastCommandMessage); + start(); +} + +void WII5Communications::sendBinModeType(uint32_t t, uint32_t recordCount, uint32_t mdBlock, uint32_t resultsBlock) { + updateStatus = false; + requestWait = 0; + requestBinType = t; + requestRecordCount = recordCount; // Note - this does not look up the count - uses it to check Block + // Leave this at 0 to load currecnt record count + requestMetadataBlock = mdBlock; // Leave this at zero to not load a Block + requestResultsBlock = resultsBlock; // Leave this at zero to not load a Block +} + +/* + +How to use this library. + + setType - The BinData type to be sent + setResultsRecord - Pick and SD Card and record to read. + - Or could have a setResultsBuf + - Not sure how to choose SD card - as it may be locked by maths etc + - Not sure how to keep copy of data - 512 bytes too much + . readRecord, just before BinData.set ? + - How to deal with queues and wrong cards. + +Advanced use + + Instead of just a type with record etc, this library should handle + things like sending raw data. + +Simple Mode + - Like used in Position. Just send message requested. + +Queue Mode + - Find missing entries, not yet sent + +*/ + + +void WII5Communications::start() { + // Note: Test mode is turned to false at each start. So to enable test mode, start then setTESTING + testMode = false; + + sdDone = 0; + sdTried = 0; + if (step == WII5COMMUNICATIONS_IDLE) { + step = WII5COMMUNICATIONS_PREPARE; wait = 0; + } +} + +void WII5Communications::stop() { + // TODO I think we should consider a shutdown, or just use first? + if (step != WII5COMMUNICATIONS_IDLE) { + step = WII5COMMUNICATIONS_END; wait = 0; + } +} + +bool WII5Communications::isRunning() { + return (step != WII5COMMUNICATIONS_IDLE); +} + +void WII5Communications::loop() { + bool first = (lastStep != step); + lastStep = step; + + switch (step) { + case WII5COMMUNICATIONS_DISABLED: + if (first) { + console.log(LOG_DEBUG, F("Communications: step disabled")); + } + break; + + case WII5COMMUNICATIONS_IDLE: + if (first) { + if (disabled) { + step = WII5COMMUNICATIONS_DISABLED; wait = 0; + } + console.log(LOG_DEBUG, F("Communications: step idle - manual waiting for start")); + wii5Iridium.stop(); + countSend = 0; // We use this to count how many in this run + } + // More than 14 hours since last messsage - send out a ping + else if (lastSuccess > ((uint32_t)14*3600000)) { + console.log(LOG_DEBUG, F("Communications: Not sent a successful message in 14 hours - send pinkg")); + sendBinModeType(1,1,0,0); + // TODO Custom text for the error/reason + lastSuccess = 0; // To stop repeating every loop + } + break; + + case WII5COMMUNICATIONS_PREPARE: + step = WII5COMMUNICATIONS_IRIDIUM_START; wait = 0; + break; + + case WII5COMMUNICATIONS_IRIDIUM_START: + if (first) { + console.log(LOG_DEBUG, F("Communications: step IRIDIUM_START (firmware, imei, signalQuality)")); + if (testMode) { + console.log(LOG_FATAL, F("FAKE: skipping iridium request Firmware")); + step = WII5COMMUNICATIONS_JUMP; wait = 0; + } + else { + wii5Iridium.requestFirmware(); + } + binNextBit = 0; // Important to start split + binCount = 0; + } + // Waiting - we are ready to send the text + else if (wii5Iridium.waiting()) { + step = WII5COMMUNICATIONS_JUMP; wait = 0; + } + // TODO Hard coded + else if (wait > 600000) { + console.log(LOG_ERROR, F("Communications: Iridium Timed out after 600 seconds")); + step = WII5COMMUNICATIONS_JUMP; wait = 0; + } + break; + + // Decide what to do + case WII5COMMUNICATIONS_JUMP: + if (first) { + } + + // Already got one moving on + else if (mainBinType > 0) { + console.log(LOG_DEBUG, F("Communications: step JUMP - existing message in mainBinType")); + step = WII5COMMUNICATIONS_IRIDIUM_SEND; wait = 0; + } + + // New request - set it and move on + else if (requestBinType > 0) { + console.log(LOG_DEBUG, F("Communications: step JUMP - new message in requestBiType")); + // One in the background - must clearit, so not used again + mainBinType = requestBinType; + metadataBlock = requestMetadataBlock; + resultsBlock = requestResultsBlock; + mainRecordCount = requestRecordCount; + + countSucces = 0; + countFailed = 0; + + // Clear the request + requestBinType = 0; + requestMetadataBlock = 0; + requestResultsBlock = 0; + requestRecordCount = 0; + step = WII5COMMUNICATIONS_IRIDIUM_SEND; wait = 0; + } + else if (autoMode) { + // TODO - autoMode - decide what to do + // Only when we have nothing else to do + step = WII5COMMUNICATIONS_SD_NEXT; wait = 0; + } + // else if (Old!) { + // TODO Send a minimum packet + // } + else { + step = WII5COMMUNICATIONS_END; wait = 0; + } + + // TODO Decide on loading data from SD + // 1. requestBlock or similar manuarl request (Which SD Card?) + // 2. Auto Mode - find a record not sent and send it + + break; + + // SD Next - find a record + case WII5COMMUNICATIONS_SD_NEXT: + + // Find out the SD Card and Block we started at - if either change, start again + + // TODO - consider openging the SD Card ourselves + + // No card is open.... quit + if (!sdBlock.cardIsOpen()) { + // TODO - consider opening one? - Nah - Capture mode only + console.log(LOG_DEBUG, F("Communications: card is not open - finishing")); + sdCardId = 0; + sdStartBlock = 0; + sdCurrentBlock = 0; + step = WII5COMMUNICATIONS_END; wait = 0; + } + + // Nothing processed - lets get that data from the SD card + else if ( (sdCardId == 0) || (sdStartBlock == 0) ) { + console.log(LOG_DEBUG, F("Communications: new card... getting locations")); + sdCardId = sdBlock.getCardId(); + sdStartBlock = sdBlock.getMetadataNext(); // 1 record past where to read + sdCurrentBlock = sdBlock.getMetadataNext(); // Current record read, starts past because never reads. Sub one first + } + + // Card has changed, blank these and move on + else if ( (sdBlock.getCardId() != sdCardId) || (sdBlock.getMetadataNext() != sdStartBlock)) { + console.log(LOG_DEBUG, F("Communications: card changed ! - trying to read new card")); + console.log(LOG_DEBUG, F("Communications: OldSD=%d, NewCard=%d"), int(sdCardId), int(sdBlock.getCardId())); + console.log(LOG_DEBUG, F("Communications: OldBlock=%lu, NewBlock=%lu"), sdCurrentBlock, sdBlock.getMetadataNext()); + sdCardId = 0; + sdStartBlock = 0; + sdCurrentBlock = 0; + } + + // Is the next block we have to read before the start? + else if ((sdCurrentBlock - 1) < sdBlock.getMetadataFirst()) { + console.log(LOG_DEBUG, F("Communications: Read all metadata records to start")); + // Finsihed readin as far as we can. + sdCardId = 0; + sdStartBlock = 0; + sdCurrentBlock = 0; + step = WII5COMMUNICATIONS_END; wait = 0; + } + + // TODO - check if we have gone back far enough - e.g. 100 records. + // TODO - FIND ! This is where we can do the find. + + // Do 100 max tests + else if (sdTried > 100) { + console.log(LOG_DEBUG, F("Communications: Tried 100 records, Finishing")); + // Finsihed readin as far as we can. + sdCardId = 0; + sdStartBlock = 0; + sdCurrentBlock = 0; + + step = WII5COMMUNICATIONS_END; wait = 0; + } + + // Do 10 transmitts + else if (sdDone > 10) { + console.log(LOG_DEBUG, F("Communications: Done 10 transmitts. Finsihing")); + // Finsihed readin as far as we can. + sdCardId = 0; + sdStartBlock = 0; + sdCurrentBlock = 0; + step = WII5COMMUNICATIONS_END; wait = 0; + + } + + // Read record and use now - cos might loose it + else { + sdTried++; + sdCurrentBlock--; + console.log(LOG_DEBUG, F("Communications: reading block: %lu"), sdCurrentBlock); + if (!sdBlock.read(sdCurrentBlock)) { + console.log(LOG_FATAL, F("Communications: Error reading this block. Skipping to next")); + // TODO what next afer error? + } + else { + WII5MetaDataObject* local_metadata = (WII5MetaDataObject*)(sdBlock.metadata->data); + // TODO Print this out to test ? + if ( + // Status is blank, not sent or tried + (sdBlock.metadata->status == 0) + && (sdBlock.metadata->resultsBlockStart != 0) + && (sdBlock.metadata->recordId != 0) + && (sdBlock.metadata->recordId == local_metadata->recordCount) + ) { + console.log(LOG_DEBUG, F("Communications: Metadata readyy, but is Result?")); + + // Copy the values across, but zero if next record fails - because we loose the data + mainBinType = wii5Config.getCaptureBinaryType(); + metadataBlock = sdCurrentBlock; + resultsBlock = sdBlock.metadata->resultsBlockStart; + mainRecordCount = sdBlock.metadata->recordId; + updateStatus = true; + + bool badResults = false; + + // Read the block + if (!sdBlock.read(resultsBlock)) { + badResults = true; + console.log(LOG_ERROR, F("Communications: Failed to read results for testing")); + } + + // All records should match + else if (sdBlock.block->recordId != mainRecordCount) { + badResults = true; + console.log(LOG_ERROR, F("Communications: Results bock record ID does not match, skipping")); + } + + // This is a way I know the RESULTS block has been written to + else if (sdBlock.block->status != mainRecordCount) { + badResults = true; + console.log(LOG_ERROR, F("Communications: Results status is not the record count - it isn't ready to send")); + } + + if (badResults) { + // Abandon ship + console.log(LOG_FATAL, F("Communications: Error reading this block. Skipping to next")); + mainBinType = 0; + metadataBlock = 0; + resultsBlock = 0; + mainRecordCount = 0; + updateStatus = false; + } + else { + console.log(LOG_FATAL, F("Communications: Block ready to send = %lu"), resultsBlock); + step = WII5COMMUNICATIONS_IRIDIUM_SEND; wait = 0; + } + } + else { + console.log(LOG_DEBUG, F("Communications: Skipping - Status = %lu"), sdBlock.metadata->status); + } + } + } + break; + + // SD Load Block + case WII5COMMUNICATIONS_SD_BLOCK: + step = WII5COMMUNICATIONS_IRIDIUM_SEND; wait = 0; + break; + + // SEND a message + case WII5COMMUNICATIONS_IRIDIUM_SEND: + if (first) { + console.log(LOG_DEBUG, F("Communications: step IRIDIUM_SEND")); + } + // Safety + else if (binCount > 25) { + console.log(LOG_FATAL, F("Communications: More than 25 data splits - cancelling Iridium")); + step = WII5COMMUNICATIONS_END; wait = 0; + } + + // if (wii5BinData.getSplit(wii5Config.getCommunicationsBinaryType(), 300, &binNextBit, &binType)) { + else { + // NOTE: Stil hard coded iridium number + if (wii5BinData.getSplit(mainBinType, 340, &binNextBit, &binType)) { + console.log(LOG_INFO, F("Communications: Sending BinData Splitting #=%d, original type/size=%lu/%d, new type/size=%lu/%d, nextBit=%d"), + binCount, + mainBinType, wii5BinData.getSize(mainBinType), + binType, wii5BinData.getSize(binType), + int(binNextBit) + ); + binCount++; + + // NOTE: setData will not set data from Blocks, call the necessary after steps. + // see BinData for more information. + wii5BinData.createData(binType, wii5BinaryIridium, 340, mainRecordCount); + wii5BinData.setData(binType, wii5BinaryIridium, 340); // TODO Hard coded + + // NOTE: Performance. This should be split up into steps in this state machine, card open, read metadata, etc... This will do for now + if (metadataBlock) { + if (sdBlock.read(metadataBlock)) { + console.log(LOG_INFO, F("Communications: Loaded metadata=%lu and setting Iridium data"), metadataBlock); + // TODO Consider checking metadta block wth mainRecordCount + wii5BinData.setBlockMetadata(binType, wii5BinaryIridium, 340, (WII5MetaDataObject*)sdBlock.metadata->data); + } + else { + console.log(LOG_FATAL, F("Communications: FAILED TO LOAD Load metadata=%lu"), metadataBlock); + } + } + + if (resultsBlock) { + if (sdBlock.read(resultsBlock)) { + console.log(LOG_INFO, F("Communications: Loaded results=%lu"), resultsBlock); + wii5BinData.setBlockResults(binType, wii5BinaryIridium, 340, (WII5Processed*)sdBlock.block->data); + } + else { + console.log(LOG_FATAL, F("Communications: FAILED TO LOAD Load results=%lu"), resultsBlock); + } + } + + wii5Iridium.setBinSize( + wii5BinData.getSize(binType) + ); + + // Send it ! + if (testMode) + console.log(LOG_FATAL, F("FAKE: Not requestSendBinary")); + else { + wii5Iridium.requestSendBinary(); + countSend++; + } + + waitStatus = true; // We are waiting for the status, unlike command replies + step = WII5COMMUNICATIONS_IRIDIUM_SEND_WAIT; wait = 0; + } + + // Clear them and move on + else { + console.log(LOG_DEBUG, F("Communications: Iridium send complete, moving to end")); + if ( + updateStatus + && metadataBlock + && (countSucces > 0) + // && (countFailed < 1) - TODO add this test + ) { + // TODO how about some better meaning to the number? + sdDone++; + console.log(LOG_DEBUG, F("Communications: Updating status block=%lu, record=%lu, status=1"), metadataBlock, mainRecordCount); + sdBlock.metadataUpdateStatus(metadataBlock, mainRecordCount, 1); + + sdBlock.read(metadataBlock); + console.log(LOG_INFO, F("Communications: TODO Updated status and read for block = %lu, record = %lu and status = %lu"), + metadataBlock, mainRecordCount, sdBlock.metadata->status + ); + } + else { + console.log(LOG_INFO, F("Communications: Message sent but status not updated - metadataBlock=%lu,countSucces=%d,update=%d"), metadataBlock, int(countSucces), int(updateStatus)); + } + + updateStatus = false; + binNextBit = 0; // Important to start split + binCount = 0; + mainBinType = 0; + metadataBlock = 0; + resultsBlock = 0; + step = WII5COMMUNICATIONS_JUMP; wait = 0; + } + } + break; + + // Wait for send and check reply + case WII5COMMUNICATIONS_IRIDIUM_SEND_WAIT: + if (first) { + console.log(LOG_DEBUG, F("Communications: step IRIDIUM_SEND_WAIT")); + if (testMode) { + console.log(LOG_FATAL, F("FAKE: Not waiting for Iridium")); + updateStatus = true; + countSucces++; + step = WII5COMMUNICATIONS_JUMP; wait = 0; first = true; + } + } + else if (wii5Iridium.waiting()) { + // Wait for status success/fail for counts. This allos us to ignore command messages + if (waitStatus) { + if (wii5Iridium.lastSend == WII5IRIDIUMSENDRESULT_OK) { + countSucces++; + lastSuccess = 0; // To stop repeating every loop + } + else + countFailed++; + waitStatus = false; + } + + // TODO - check this isn't a race condition. + // Check incoming messages + if (wii5Iridium.lastReceive == WII5IRIDIUMRECEIVERESULT_OK) { + if (wii5Iridium.lastSend == WII5IRIDIUMSENDRESULT_OK) + console.log(LOG_INFO, F("Communications: (Send success) Received Iridium Data length=%d"), wii5Iridium.lastReceiveLen); + else + console.log(LOG_ERROR, F("Communications: (Send failed) Received Iridium Data length=%d"), wii5Iridium.lastReceiveLen); + wii5Commands.processBinData(); + if ( (wii5Commands.lastCommandCmd != WII5COMMAND_NONE) && (wii5Commands.lastCommandResult != WII5RESULTS_UNKNOWN) ) { + console.log(LOG_ERROR, F("Communications: Sending reply command=%d, result=%d"), int(wii5Commands.lastCommandCmd), int(wii5Commands.lastCommandResult)); + // Always send a result, allowing for next message too - 3 or 4 depending on lenght reply + uint32_t replyType = wii5BinData.setBit(0, 3); + wii5BinData.createData(replyType, wii5BinaryIridium, 340, 1); + wii5BinData.setData(replyType, wii5BinaryIridium, 340); // TODO Hard coded + wii5Iridium.setBinSize(wii5BinData.getSize(replyType)); + wii5Iridium.requestSendBinary(); + + // Blank last command + wii5Commands.lastCommandCmd = WII5COMMAND_NONE; + wii5Commands.lastCommandResult = WII5RESULTS_UNKNOWN; + memset(wii5Commands.lastCommandMessage, 0, sizeof(wii5Commands.lastCommandMessage)); + } + else { + // TODO Send error ! + console.log(LOG_ERROR, F("Communications: Unable to reply command=%d, result=%d"), int(wii5Commands.lastCommandCmd), int(wii5Commands.lastCommandResult)); + step = WII5COMMUNICATIONS_JUMP; wait = 0; first = true; + } + // No change of step - as we are sending again + } + else { + if (wii5Iridium.lastSend == WII5IRIDIUMSENDRESULT_OK) + console.log(LOG_INFO, F("Communications: (Send success) nothing received")); + else + console.log(LOG_ERROR, F("Communications: (Send failed) nothing received")); + // Check any further waiting messages - send same message again. + + step = WII5COMMUNICATIONS_JUMP; wait = 0; first = true; + } + } + // TODO Configurables + else if (wait > 300000) { + console.log(LOG_ERROR, F("Communications: Iridium Timed out after 5 minutes")); + step = WII5COMMUNICATIONS_END; wait = 0; + } + + break; + + case WII5COMMUNICATIONS_END: + if (first) { + console.log(LOG_INFO, F("Communications: step END - checking")); + } + else if (metadataScan && (countSend < 1)) { + // Turn it off, so we don't get in here twice on no send. + metadataScan = false; + console.log(LOG_INFO, F("Communications: NO TRANSMISSIONS - Sending a default")); + uint32_t replyType = wii5BinData.setBit(1, 5); + sendBinModeType(replyType, 0, 0, 0); + setSimpleMode(); + step = WII5COMMUNICATIONS_JUMP; wait = 0; + } + else { + metadataScan = false; + console.log(LOG_INFO, F("Communications: Sent %d messages"), countSend); + // Noithing left to do - lets shut it down. + wii5Iridium.stop(); + step = WII5COMMUNICATIONS_IDLE; wait = 0; + } + break; + + default: + console.log(LOG_FATAL, F("COMMUNICATIONS: Default step... step=%d"), step); + step = WII5COMMUNICATIONS_IDLE; wait = 0; + } +} + +WII5Communications wii5Communications; diff --git a/WII5Communications.h b/WII5Communications.h new file mode 100644 index 0000000..9cb2f9b --- /dev/null +++ b/WII5Communications.h @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Communications.h + * @brief High-level communications dispatcher: orchestrates Iridium send/receive flows. + */ + +#ifndef WII5Communications_h +#define WII5Communications_h + +#include +#include + +enum WII5COMMUNICATIONS_STEPS { + WII5COMMUNICATIONS_DISABLED, + WII5COMMUNICATIONS_IDLE, + WII5COMMUNICATIONS_PREPARE, + WII5COMMUNICATIONS_IRIDIUM_START, + WII5COMMUNICATIONS_JUMP, + WII5COMMUNICATIONS_FIND, + WII5COMMUNICATIONS_SD_NEXT, + WII5COMMUNICATIONS_SD_BLOCK, + WII5COMMUNICATIONS_IRIDIUM_SEND, + WII5COMMUNICATIONS_IRIDIUM_SEND_WAIT, + WII5COMMUNICATIONS_END +}; + +/** + * @brief Communications orchestration: manages a state machine across + * Iridium send/receive cycles, including pulling messages from SD blocks + * and splitting them across multiple SBD frames when too large. + * + * Three orthogonal modes: + * - autoMode: scan SD for unsent metadata and push it + * - simpleMode: only send what's explicitly requested + * - testMode: smoke-test the comms path without burning satellite minutes + * + * Plus a `disabled` master switch. + */ +class WII5Communications : public WII5Mode { + public: + WII5Communications() {} + /** @brief Is the comms state machine actively in a send/receive cycle? */ + bool isRunning(); + /** @brief One-time bring-up. */ + void begin(); + /** @brief State-machine tick: drives Iridium and SD interactions. */ + void loop(); + /** @brief Begin a new comms cycle (send pending data). */ + void start(); + /** @brief Abort the current comms cycle and idle. */ + void stop(); + + /** @brief Hint to autoMode that the SD card has fresh data worth scanning. */ + void signalSD(); + + /** @brief Queue a BinData message of type `t`, optionally pulled from given SD blocks. */ + void sendBinModeType(uint32_t t, uint32_t recordCount, uint32_t mdBlock = 0, uint32_t resultsBlock = 0); + /** @brief Send arbitrary buffered text (bypasses the BinData frame). */ + void sendText(void *buf = NULL, uint16_t bufSize = 0); + /** @brief Send the most recent error as an SBD message. */ + void sendError(); + /** @brief Find an SD record matching (binType, recordCount) for retransmission. */ + void findRecord(uint32_t t, uint32_t recordCount); + + /** @brief Enable test mode (no real Iridium traffic). */ + void setTESTING(); + /** @brief Enable autoMode: periodically scan SD and push new data. */ + void setAutoMode(); + /** @brief Enable simpleMode: send only what is explicitly requested. */ + void setSimpleMode(); + /** @brief Is autoMode currently the active mode? */ + bool getAutoMode(); + /** @brief Is simpleMode currently the active mode? */ + bool getSimpleMode(); + /** @brief Master enable/disable; returns previous state. */ + bool setDisabled(bool in); + + protected: + // Mode - These are actually mutually exclusive - disabled | noiridium | auto | test + bool disabled; + bool autoMode; + bool metadataScan; + bool testMode; + + elapsedMillis lastSuccess; + + // Send a message + elapsedMillis requestWait; + uint32_t requestBinType; + uint32_t requestRecordCount; + uint32_t requestMetadataBlock; + uint32_t requestResultsBlock; + + uint8_t sdCardId; + bool updateStatus; + uint32_t sdStartBlock; + uint32_t sdCurrentBlock; + uint8_t countSucces; + uint8_t countFailed; + + uint16_t countSend; + + bool waitStatus; + uint32_t sdDone; + uint32_t sdTried; + uint32_t findBinType; + uint32_t findRecordCount; + + // Loop + bool first; + elapsedMillis displayWait; + elapsedMillis wait; + WII5COMMUNICATIONS_STEPS step; + WII5COMMUNICATIONS_STEPS lastStep; + uint32_t minutes; // since midnight. + + // State of the split to smaller chunks + uint8_t binNextBit; + uint8_t binCount; + uint32_t binType; + + // The block we are working on + uint32_t mainBinType; + uint32_t metadataBlock; + uint32_t resultsBlock; + uint32_t mainRecordCount; +}; + +extern WII5Communications wii5Communications; + +#endif diff --git a/WII5Config.cpp b/WII5Config.cpp new file mode 100644 index 0000000..22189fe --- /dev/null +++ b/WII5Config.cpp @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Config.cpp + * @brief Persistent configuration in EEPROM: device ID, mode defaults, calibrations. + */ + +/* + +WII5Config + +*/ + +#include +#include +#include + +void WII5Config::begin() { + sh3dNodeConfig.begin(); + _deviceId = (uint32_t)(sh3dNodeConfig.getNodeId()); + readConfigData(); + readStatusData(); + #ifdef WII5_DEBUG_SERIAL + SerialDebug.print(F("CONFIG:ID:")); + SerialDebug.print(getDeviceId()); + SerialDebug.println(); + #endif + + console.log(LOG_DEBUG, F("Config: id=%lu"), getDeviceId()); +} + +void WII5Config::resetStatusData() { + // Tag it + statusData.type = WII5_DATA_STATUS_TYPE; + statusData.version = WII5_DATA_STATUS_VERSION; + // statusData.xxxCount = 0; +} + +void WII5Config::resetConfigData() { + // Tag it + configData.type = WII5_DATA_CONFIG_TYPE; + configData.version = WII5_DATA_CONFIG_VERSION; + + // Sensible defaults + configData.gpsTimeout = 300; + configData.flags = 0x0; + configData.disableLowBattery = 0; + + // MODE: Capture + configData.capturePeriod = 20; + configData.captureBinaryType = 769; + configData.captureRecords = 5120; + + // + // 1 = Packet 0 - dates, times, lat, lon, etc. + // 769 = 0,8,9 + // + + // MODE: Position + configData.positionPeriod = 5; + configData.positionBinaryType = 1; // TODO Bogus + + // MODE: Sleep + configData.sleepPeriod = 12 * 60; // 12 hours + configData.sleepBinaryType = 1; // TODO Bogus + configData.sleepUntil = 0; // + + // MODE: Default and Temporary + configData.defaultMode = WII5MODE_CAPTURE; + configData.temporaryMode = WII5MODE_NONE; + configData.temporaryModeExpires = 0; + + // Battery + configData.batteryLow = 900; + configData.batteryMid = 1050; + + // Save it + updateConfigData(); +} + +// WARNING: Don't want to do this without clearning SD Card ! +void WII5Config::resetCounters() { + sh3dNodeConfig.clearRecordCount(); + sh3dNodeConfig.clearRunCount(); +} + +void WII5Config::readStatusData() { + if (sizeof(statusData) > WII5CONFIG_MAXBLOCK) { + console.log(LOG_ERROR, F("Config: Size of Status too big. Size=%d, Maxiumum=%d"), sizeof(statusData), WII5CONFIG_MAXBLOCK); + } + EEPROM.readBlock(WII5CONFIG_OFFSET + WII5CONFIG_STATUSDATA, statusData); + // TODO checking data etc. +} +void WII5Config::updateStatusData() { + EEPROM.updateBlock(WII5CONFIG_OFFSET + WII5CONFIG_STATUSDATA, statusData); +} + +void WII5Config::readConfigData() { + if (sizeof(configData) > WII5CONFIG_MAXBLOCK) { + console.log(LOG_ERROR, F("Config: Size of Config too big. Size=%d, Maxiumum=%d"), sizeof(configData), WII5CONFIG_MAXBLOCK); + } + EEPROM.readBlock(WII5CONFIG_OFFSET + WII5CONFIG_CONFIGDATA, configData); + // TODO checking data etc. +} +void WII5Config::updateConfigData() { + EEPROM.updateBlock(WII5CONFIG_OFFSET + WII5CONFIG_CONFIGDATA, configData); +} + +uint32_t WII5Config::getDeviceId() { + return _deviceId; +} + +void WII5Config::setDeviceId(uint32_t newId) { + sh3dNodeConfig.setNodeId(newId); + _deviceId = newId; +} + +void WII5Config::setDisableLowBattery(time_t exp) { + // TODO Check values + // lowBatteryDisable = exp; +} +time_t WII5Config::getDisableLowBattery() { + return 0; // lowBatteryDisable; +} + +bool WII5Config::_checkMode(WII5_MODES newMode, uint32_t v1, uint32_t v2, uint32_t v3, uint32_t v4) { + // TODO check mode, and v1,2,3 dependeing on mode + // TODO check expiry ? + return true; +} + +void WII5Config::setDefaultMode(WII5_MODES newMode) { + configData.defaultMode = newMode; + updateConfigData(); +} + +WII5_MODES WII5Config::getDefaultMode() { + return configData.defaultMode; +} +void WII5Config::setTemporaryMode(time_t exp, WII5_MODES newMode) { + configData.temporaryMode = newMode; + configData.temporaryModeExpires = exp; + updateConfigData(); +} +WII5_MODES WII5Config::getTemporaryMode() { + return configData.temporaryMode; +} + +uint32_t WII5Config::updateRecordCount() { + return (uint32_t)sh3dNodeConfig.updateRecordCount(); +} +uint32_t WII5Config::getRecordCount() { + return (uint32_t)sh3dNodeConfig.getRecordCount(); +} + +bool WII5Config::getFlag(WII5_FLAGS f) { + return (BitVal(configData.flags, f) == 1); +} +void WII5Config::setFlag(WII5_FLAGS f, bool state) { + if (state) + SetBit(configData.flags, f); + else + ClearBit(configData.flags, f); + updateConfigData(); +} + +uint32_t WII5Config::getGpsTimeout() { + // Minimum 1 minute + if (configData.gpsTimeout < 60) + return 60; + + // Maximum 10 minutes + else if (configData.gpsTimeout > 600) + return 600; + + else + return configData.gpsTimeout; +} + +void WII5Config::setGpsTimeout(uint32_t t) { + configData.gpsTimeout = t; + updateConfigData(); +} + +void WII5Config::setCaptureRecords(uint32_t in) { + configData.captureRecords = in; + updateConfigData(); +} +uint32_t WII5Config::getCaptureRecords() { + if (configData.captureRecords < 100) + return 5120; + else if (configData.captureRecords > 57600) + return 57600; + else + return configData.captureRecords; +} + +void WII5Config::setCapturePeriod(uint32_t in) { + configData.capturePeriod = in; + updateConfigData(); +} +uint32_t WII5Config::getCapturePeriod() { + // Envorce limits 5 - 720 (12 hours) + + // 12 Hours + if (configData.capturePeriod > 720) + return 720; + + // 5 Minutes + else if (configData.capturePeriod < 5) + return 5; + + else + return configData.capturePeriod; +} + +void WII5Config::setCaptureBinaryType(uint32_t in) { + configData.captureBinaryType = in; + updateConfigData(); +} +uint32_t WII5Config::getCaptureBinaryType() { + return configData.captureBinaryType; +} + +void WII5Config::setPositionPeriod(uint32_t in) { + configData.positionPeriod = in; + updateConfigData(); +} +uint32_t WII5Config::getPositionPeriod() { + return configData.positionPeriod; +} + +void WII5Config::setPositionBinaryType(uint32_t in) { + configData.positionBinaryType = in; + updateConfigData(); +} +uint32_t WII5Config::getPositionBinaryType() { + return configData.positionBinaryType; +} + +void WII5Config::setSleepUntil(uint32_t in) { + configData.sleepUntil = in; + updateConfigData(); +} +uint32_t WII5Config::getSleepUntil() { + return configData.sleepBinaryType; +} + +void WII5Config::setSleepBinaryType(uint32_t in) { + configData.sleepBinaryType = in; + updateConfigData(); +} +uint32_t WII5Config::getSleepBinaryType() { + return configData.sleepBinaryType; +} + +void WII5Config::setSleepPeriod(uint32_t in) { + configData.sleepPeriod = in; + updateConfigData(); +} +uint32_t WII5Config::getSleepPeriod() { + // Envorce limits 5 - 720 (12 hours) + + // 6 Hours + if (configData.sleepPeriod > 360) + return 360; + + // 1 minute minimum, becomes 1 hour if nothing + else if (configData.sleepPeriod < 1) + return 1; + + else + return configData.sleepPeriod; +} + +void WII5Config::dump(bool toConsole, Print* toOther) { + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Data,config,deviceid=%lu,recordid=%lu\r\n"), + getDeviceId(), getRecordCount() + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Data,config,defaultMode=%d/%s\r\n"), + int(getDefaultMode()), wii5Strings.strMode(getDefaultMode()) + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + // TODO Temporary mode + + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Data,config,capturePeriod=%lu,captureBinaryType=%lu,captureRecords=%lu\r\n"), + getCapturePeriod(), getCaptureBinaryType(), getCaptureRecords() + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Data,config,positionPeriod=%lu,positionBinaryType=%lu\r\n"), + getPositionPeriod(), getPositionBinaryType() + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Data,config,sleepPeriod=%lu,sleepBinaryType=%lu,sleepUntil=%lu\r\n"), + getSleepPeriod(), getSleepBinaryType(), getSleepUntil() + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Data,config,batteryLow=%d,batteryMid=%d\r\n"), + getBatteryLow(), getBatteryMid() + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + // time_t getDisableLowBattery(); + // uint32_t getRecordCount(); + // bool getFlag(WII5_FLAGS f); + // uint32_t getGpsTimeout(); +} + +uint16_t WII5Config::getBatteryLow() { + if (configData.batteryLow > 1500) + return 1100; + return configData.batteryLow; +} +void WII5Config::setBatteryLow(uint16_t in) { + configData.batteryLow = in; + updateConfigData(); +} +uint16_t WII5Config::getBatteryMid() { + if (configData.batteryMid < getBatteryLow()) + return getBatteryLow() + 100; + if (configData.batteryMid > 1500) + return getBatteryLow() + 100; + return configData.batteryMid; +} +void WII5Config::setBatteryMid(uint16_t in) { + configData.batteryMid = in; + updateConfigData(); +} + +WII5Config wii5Config; diff --git a/WII5Config.h b/WII5Config.h new file mode 100644 index 0000000..e957db0 --- /dev/null +++ b/WII5Config.h @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Config.h + * @brief Persistent configuration in EEPROM: device ID, mode defaults, calibrations. + */ + +#ifndef WII5Config_h +#define WII5Config_h + +#ifdef WII5_EEPROMex +#include +#endif +#include + +/* + +WII5 Configuration we must keeping + +Big Areas: + * Default Mode + * Temorary Mode + * Disable LowBattery + +Identifiers & Counters + + * DeviceId - Unique device ID - see WII Server for names. Normally 5 digits + * RunCount - A counter that incremembers every time the AVR is booted + * RecordCount - A number that is incremented every time a Capture is done + * + +*/ + +// Highly Oversimplified structured EEPROM storage (from past Sh3d managed code) +// Limitations: +// - Assume EEPROMex +// - Assume 384 bytes from where it says +// - Assume 64 bytes per struct (config for example is currently only 32) +// - Gives us 6 areas to write to +// - If we need more advanced, we can make a struct of structs +// TEMP: Manually copied from Sh3d Config +#define WII5CONFIG_MAXBLOCK 64 + +#define WII5CONFIG_OFFSET 128 +// 0 - Config +#define WII5CONFIG_CONFIGDATA 0 +// 1 - Status (TODO Maybe split Status, Counts, Errors) +#define WII5CONFIG_STATUSDATA 64 +// 2 - ? +#define WII5CONFIG_RESERVED_2 128 +// 3 - ? +#define WII5CONFIG_RESERVED_3 192 +// 4 - ? +#define WII5CONFIG_RESERVED_4 256 +// 5 - ? +#define WII5CONFIG_RESERVED_5 320 + + +class WII5Config { + public: + WII5Config() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_CONFIG;} + void begin(); + uint32_t getDeviceId(); // getNodeId + void setDeviceId(uint32_t newId); // setNodeId + + // MODES: - do we have valid ones? + bool _checkMode(WII5_MODES newMode, uint32_t v1, uint32_t v2, uint32_t v3, uint32_t v4); + + void setDefaultMode(WII5_MODES newMode); + WII5_MODES getDefaultMode(); + + void setTemporaryMode(time_t exp, WII5_MODES newMode); + WII5_MODES getTemporaryMode(); + + void setCapturePeriod(uint32_t in); + uint32_t getCapturePeriod(); + + void setCaptureRecords(uint32_t in); + uint32_t getCaptureRecords(); + + void setCaptureBinaryType(uint32_t in); + uint32_t getCaptureBinaryType(); + + void setPositionPeriod(uint32_t in); + uint32_t getPositionPeriod(); + + void setPositionBinaryType(uint32_t in); + uint32_t getPositionBinaryType(); + + // MODE Sleep + void setSleepBinaryType(uint32_t in); + uint32_t getSleepBinaryType(); + void setSleepPeriod(uint32_t in); + uint32_t getSleepPeriod(); + void setSleepUntil(uint32_t in); + uint32_t getSleepUntil(); + + void setDisableLowBattery(time_t exp); + time_t getDisableLowBattery(); + + uint32_t updateRecordCount(); + uint32_t getRecordCount(); + + void resetCounters(); // clearRunCount(), clearRecordCount() + + void readConfigData(); + void updateConfigData(); + void resetConfigData(); + void readStatusData(); + void updateStatusData(); + void resetStatusData(); + + bool getFlag(WII5_FLAGS f); + void setFlag(WII5_FLAGS f, bool state); + + uint32_t getGpsTimeout(); + void setGpsTimeout(uint32_t t); + + void dump(bool toConsole, Print* toOther); + + uint16_t getBatteryLow(); + void setBatteryLow(uint16_t in); + uint16_t getBatteryMid(); + void setBatteryMid(uint16_t in); + + protected: + uint32_t _deviceId; + WII5_Data_Config configData; + // TODO Data_Config - why is status same as config ? + WII5_Data_Config statusData; +}; + +extern WII5Config wii5Config; + +#endif diff --git a/WII5Controller.cpp b/WII5Controller.cpp new file mode 100644 index 0000000..f7f4cce --- /dev/null +++ b/WII5Controller.cpp @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Controller.cpp + * @brief Main controller: mode dispatch, watchdog, and the top-level loop. + */ + +/* + +Controller - Main controller, for loops and processing within WII5. + +Basically the controller is in charge of what is called for setup and loop. + +SPLIT: + + * Setup - helpers and stuff to setup code - doesn't not need to be here + * Mode, setMode, calculateMode etc + * Console and Settings + +HOWTO USE "yield" Here + + NOT READY - Causes infinite loops + + * create a "yield" in the sketch and call + * wii5Controller.yield()l + * And any other important entries, e.g. Network + * And in loop() call yield at the start of loop + +TEMPLATE: + + +LOOPS: + loop: + - This is the actual loop and it just calls the others, depending on mode + - Calls loopWDT + - Based on mode, calls + . + - Call loopSafe - always? + + loopWDT: + - Just update the WDT and spin on + + loopSafe: + - All the commands that must be always run + - Calls + . sh3dNodeIO.loop(); + . wii5Commands.processButtons(); + . console.loop(); + . wii5Commands.processConsoleAT(); + . wii5Commands.processConsoleStandard(); + . wii5Iridium.loop(); + . wii5Communications.loop(); + + loopDefault: + - Old - everything, instead, this is now loopOther + + loopOther: + - Everything else + - Calls + . wii5Sparton.loop(); + . wii5RadioLoRa.loop(); + . wii5Commands.processNetwork(); + . wii5Gps.loop(); + . wii5Battery.loop(); + . wii5Weather_18B20.loop(); + + + + loopNotCapturing: + +Other places loop* is called from + safeDelay (not complete) + +*/ + +#include +#include +#include +#include +#include +#include +#include + +// Debugging etc - move to header file +#define DEBUG_SLEEP + +void WII5Controller::begin() { + uptime = 0; + minuteWait = 0; + wii5ModeSleep.begin(); + wii5ModeCapture.begin(); + wii5ModeLowBattery.begin(); + wii5ModeManualTest.begin(); + wii5ModePosition.begin(); + wii5ModeSelfTest.begin(); + + mode = WII5MODE_BOOT; // Not using setMode here + + currentSD = 0; + + #ifdef WII5_DEBUG_SERIAL + SerialDebug.print(F("CONTROL:BOOTUP:freememory=")); + SerialDebug.print(freeMemory()); + SerialDebug.println(); + #endif + + // SD Card pins - should use Power object + pinMode(POWER_STORAGE_1_PIN, OUTPUT); + pinMode(STORAGE_SD1_MATHS_PIN, OUTPUT); + pinMode(STORAGE_SD2_MATHS_PIN, OUTPUT); + + #ifdef WII5_WDT_INTERNAL + wdt_enable(WDTO_8S); + #endif +} + +// Delay with SafeLoop +void WII5Controller::safeDelay(uint32_t waits) { + delayWait = 0; + while (delayWait < waits) { + loopWDT(); + loopSafe(); + } +} + +void WII5Controller::setLastHello(WII5_PORTS p, WII5_DEVICES d) { + lastHelloReceived = now(); + lastHelloElapsed = 0; + lastHelloPort = p; + lastHelloDevice = d; +} + +bool WII5Controller::checkLastHello(WII5_DEVICES d, uint32_t msOld) { + // Expected device, and time is newer than we needed - TRUE! + if ((d == lastHelloDevice) && (lastHelloElapsed < msOld)) { + return true; + } + return false; +} + +// Main Loop - broken up for easy reading +void WII5Controller::loop() { + loopWDT(); + loopSafe(); + + // The *reasonably* accurate minute count + if (minuteWait > 60000) { + // TODO - use uptime += (minuteWait / 60000) + // minuteWait = (minuteWait % 60000); + // And log if it is over 5 minutes. + uptime++; + minuteWait = minuteWait - 60000; + } + + // TODO timeout + switch (mode) { + case WII5MODE_BOOT: + // Safety: Automatically moves on + console.log(LOG_DEBUG, F("WII5: Boot up process complete")); + // Pretty safe to run + loopOther(); + statusDump(); + setMode(WII5MODE_START); + break; + case WII5MODE_START: + // Safety: Automatically moves on + loopOther(); + setMode(calculateCurrentMode()); + break; + case WII5MODE_DEMO: + // Safety: TODO This one never exits. Needs work. + // modeWait is screwed + loopOther(); + if (modeWait > 60000) { + console.log(LOG_DEBUG, F("Demonstration mode, try T; for test")); + modeWait = 0; + } + if (modeWait > (WII5TIME_1DAY * 1000)) { + // Stuck here... now what. Move to sleep? + mode = WII5MODE_SLEEP; modeWait = 0; + } + break; + case WII5MODE_SLEEP: + // Safety: Sleep mode handles exit + wii5ModeSleep.loop(); + // TODO Maybe? + loopOther(); + break; + case WII5MODE_LOWBATTERY: + // Safety: Battery mode handles exit + wii5ModeLowBattery.loop(); + loopOther(); + break; + case WII5MODE_POSITION: + // Safety: Either temporary timed mode, or changed by sattelite comms + wii5ModePosition.loop(); + loopOther(); + break; + case WII5MODE_CAPTURE: + wii5ModeCapture.loop(); + loopOther(); + break; + case WII5MODE_MANUALTEST: + wii5ModeManualTest.loop(); + if (modeWait > (WII5TIME_1DAY * 1000)) { + // Stuck here... now what. Move to sleep? Check saved mode is sleep or capture + console.log(LOG_FATAL, F("FATAL * FATAL * FATAL * reset is being called from manual test loop")); + console.flush(); + sh3dNodeUtil.reset(); + } + loopOther(); + break; + case WII5MODE_SELFTEST: + wii5ModeSelfTest.loop(); + + // loopSafe plus some + loopOther(); + + if (modeWait > (WII5TIME_1DAY * 1000)) { + // Stuck here... now what. Move to sleep? Check saved mode is sleep or capture + console.log(LOG_FATAL, F("FATAL * FATAL * FATAL * reset is being called from self test loop")); + console.flush(); + sh3dNodeUtil.reset(); + } + break; + default: + console.log(LOG_FATAL, F("Invalide Mode, old=%d, new=%d"), mode, WII5MODE_CAPTURE); + mode = WII5MODE_CAPTURE; + } +} + +// loopSafe - Minimal safe loop, to allow for callback from Iridium etc +void WII5Controller::loopSafe() { + // Buttons, LEDs etc + sh3dNodeIO.loop(); + wii5Commands.processButtons(); + + // Serial port + console.loop(); + wii5Commands.processConsoleAT(); + wii5Commands.processConsoleStandard(); + + // Undesirable: Do not use these in loopSafe + // - Gps - lots of data + // - Sparton - lots of data + // - Iridium - lots of data and potential cost + // - Battery - Changes mode on us + + wii5Iridium.loop(); + wii5Communications.loop(); + + wii5Maths.loop(); +} + +void WII5Controller::loopWDT() { + #ifdef WDT_RESET + // TODO consider compile this out and have specific version + // TODO or don't call loopWDT inside TestMode and have own version + // TODO manual test mode timeout + // TODO Test the buoy - has it been more than 3 days since we sent a message - maybe reboot? + if (tempDisableWDT) return; + if (wdtWait > 5100) { + digitalWrite(WDT_RESET, LOW); + wdtWait = 0; + } + // TODO setting pin all the time for 100 ms + else if (wdtWait > 5000) { + digitalWrite(WDT_RESET, HIGH); + } + #endif + #ifdef WII5_WDT_INTERNAL + wdt_reset(); + #endif +} + +void WII5Controller::loopOther() { + #ifdef WII5_IMU_SPARTON + wii5Sparton.loop(); + #endif + + // LORA or Other Modems + #ifdef WII5_RADIO_LORA + wii5RadioLoRa.loop(); + wii5Commands.processNetwork(); + #endif + + #ifdef WII5_GPS + wii5Gps.loop(); + #endif + + wii5Battery.loop(); + wii5Weather_18B20.loop(); +} + +WII5_MODES WII5Controller::getMode() { + return mode; +} + +bool WII5Controller::setMode(WII5_MODES newMode, bool nodelay) { + // Ignore same mode change - really this is an error + if (newMode == mode) + return false; + + console.log(LOG_INFO, F("Controller: new mode set %d:%s"), newMode, wii5Strings.strMode(newMode)); + #ifdef WII5_DEBUG_SERIAL + SerialDebug.print(F("CONTROL:MODE:mode=")); + SerialDebug.print(newMode); + SerialDebug.print(F(":")); + SerialDebug.print(wii5Strings.strMode(newMode)); + SerialDebug.println(); + #endif + + // TODO Delay ??? + // - Put in a "when ready or timeout" to change mode + + // Set the mode + mode = newMode; + modeWait = 0; + + // Reset all STEPS to start + wii5ModeCapture.reset(); + wii5ModeLowBattery.reset(); + wii5ModePosition.reset(); + wii5ModeManualTest.reset(); + wii5ModeSelfTest.reset(); + wii5ModeSleep.reset(); + + // Update status to tell users of mode change + statusDump(); + + return true; +} + +// TODO move to SPI +void WII5Controller::disableChipSelect() { + #ifdef RADIO_CS + digitalWrite(RADIO_CS, HIGH); + #endif + #ifdef STORAGE_CS + digitalWrite(RADIO_CS, HIGH); + #endif + #ifdef SPIMEMORY_CS + digitalWrite(SPIMEMORY_CS, HIGH); + #endif +} + + +// TODO move to Status Dump +void WII5Controller::statusDump(bool human) { + // Basic status - mode, time, free memory + // TODO 2024 - Add run count - check this still works in node? (yes) + wii5Display.atCommandSend(F("status"), F("record=%lu,uptime=%lu,freeMemory=%d,run=%lu"), + wii5Config.getRecordCount(), + uptime, freeMemory(), + sh3dNodeConfig.getRunCount() + ); + + period = 0; + next = 0; + size = 0; + + // CURRENT mode if we have one. + if (mode != wii5Config.getDefaultMode()) { + next = 0; + // Calculate next depending on noce + if (getMode() == WII5MODE_CAPTURE) { + period = wii5Config.getCapturePeriod(); + size = wii5BinData.getSize(wii5Config.getCaptureBinaryType()); + } + else if (getMode() == WII5MODE_POSITION) { + period = wii5Config.getPositionPeriod(); + size = wii5BinData.getSize(wii5Config.getPositionBinaryType()); + } + else if (getMode() == WII5MODE_SLEEP) { + period = wii5Config.getSleepPeriod(); + size = wii5BinData.getSize(wii5Config.getSleepBinaryType()); + } + next = wii5Display.minutesUntilNext(period); + + // Only show current if not the same as default + wii5Display.atCommandSend(F("status"), F("modeType=current,mode=%s,modeTime=%lu,modePeriod=%lu,modeNext=%lu,binSize=%d,sd=%d"), + wii5Strings.strMode(mode), + (uint32_t)(modeWait / 60000), // Convert to Minutes + period, next, size, currentSD + ); + } + + // DEFAULT Mode + next = 0; + if (wii5Config.getDefaultMode() == WII5MODE_CAPTURE) { + period = wii5Config.getCapturePeriod(); + size = wii5BinData.getSize(wii5Config.getCaptureBinaryType()); + } + else if (wii5Config.getDefaultMode() == WII5MODE_POSITION) { + period = wii5Config.getPositionPeriod(); + size = wii5BinData.getSize(wii5Config.getPositionBinaryType()); + } + else if (wii5Config.getDefaultMode() == WII5MODE_SLEEP) { + period = wii5Config.getSleepPeriod(); + size = wii5BinData.getSize(wii5Config.getSleepBinaryType()); + } + next = wii5Display.minutesUntilNext(period); + wii5Display.atCommandSend(F("status"), F("modeType=default,mode=%s,modeTime=%lu,modePeriod=%lu,modeNext=%lu,binSize=%d,sd=%d"), + wii5Strings.strMode(wii5Config.getDefaultMode()), + (uint32_t)(modeWait / 60000), // Convert to Minutes + period, next, size, currentSD + ); + + wii5Display.atCommandSend(F("status"), F("version=%S,imei=%s"), F(WII5_SOFTWARE_VERSION), wii5Iridium.imei); + + wii5Display.atCommandSend(F("status"), F("voltage=%ld,temperature=%ld,lat=%ld,lon=%ld,alt=%ld"), + wii5Battery.value, wii5Weather_18B20.value, + long(wii5Gps.gps->location.lat() * GPS_POS_MULT), + long(wii5Gps.gps->location.lng() * GPS_POS_MULT), + long(wii5Gps.gps->altitude.meters() * GPS_ALT_MULT) + ); + + // Calculate Maths mode. + wii5Display.atCommandSend(F("status"), F("maths_on=%d,maths_held_remaining=%lu,maths_power_remaining=%lu"), + wii5Maths.isRunning(), wii5Maths.remainingHold() / 60, wii5Maths.remainingUntil() / 60 + ); + + // Tell the Maths CPU to advertise this info + wii5Display.atCommandSend(F("status"), F("update")); + + if (human) { + console.log(LOG_INFO, F("# WII5: Waves In Ice 5 - 2019. Board:%S Version:%S ID=%lu"), + F(WII5_BOARD_NAME), + F(WII5_SOFTWARE_VERSION), + wii5Config.getDeviceId() + ); + } +} + +WII5_MODES WII5Controller::calculateDefaultMode() { + return wii5Config.getDefaultMode(); +} + +// TODO move to Status Dump +WII5_MODES WII5Controller::calculateCurrentMode() { + // TODO Urgent - temporary Modes + // TODO Urgent - find everywhere setDefaultMode is called, probably should be setCurrentMode + // TODO check we have valid date/time + // TODO check temporaryMode + // TODO check defaultMode + // return WII5MODE_CAPTURE; + // return WII5MODE_DEMO; + return wii5Config.getDefaultMode(); +} + +// What was the last default mode - for reboot and post sleep +// TODO - Proabably move to sh3dNodeConfig (EEPROM) data +void WII5Controller::setDefaultMode() { + // Get default mode from EEPROM and disable the Temporary Mode there + setMode(calculateDefaultMode()); +} + +void WII5Controller::setCurrentMode() { + // Get temporary mode from EEPROM, check it is valid, else fall back to Default + setMode(calculateCurrentMode()); +} + +void WII5Controller::shared5On() { + // Turn it on - and feel free to + digitalWrite(SHARED_5VOLT_PIN, HIGH); +} +void WII5Controller::shared5Off() { + // Turn 5 Volts off - ONLY if safe to do so + if (wii5Maths.isRunning() || wii5Iridium.isRunning() || wii5Sparton.isRunning() || (sh3dNodeIO.led2Get() != LED_OFF)) { + // Keep it on ! + digitalWrite(SHARED_5VOLT_PIN, HIGH); + } + else { + // Yep, power off time + digitalWrite(SHARED_5VOLT_PIN, LOW); + } +} + +uint8_t WII5Controller::getSD() { + return currentSD; +} + +void WII5Controller::setSDOff() { + sdBlock.cardForceClose(); + digitalWrite(POWER_STORAGE_1_PIN, LOW); + digitalWrite(STORAGE_SD1_MATHS_PIN, HIGH); + digitalWrite(STORAGE_SD2_MATHS_PIN, LOW); + currentSD = 0; +} + +// TODO 2024 - delay - really ! Probably ok for now - consider safeDelay +void WII5Controller::setSD1() { + digitalWrite(POWER_STORAGE_1_PIN, LOW); + delay(500); + digitalWrite(STORAGE_SD1_MATHS_PIN, HIGH); + digitalWrite(STORAGE_SD2_MATHS_PIN, LOW); + digitalWrite(STORAGE_CS, HIGH); + delay(500); + digitalWrite(POWER_STORAGE_1_PIN, HIGH); + delay(500); + currentSD = 1; +} + +// TODO 2024 - delay - really ! Probably ok for now - consider safeDelay +void WII5Controller::setSD2() { + digitalWrite(POWER_STORAGE_1_PIN, LOW); + delay(500); + digitalWrite(STORAGE_SD1_MATHS_PIN, LOW); + digitalWrite(STORAGE_SD2_MATHS_PIN, HIGH); + digitalWrite(STORAGE_CS, HIGH); + delay(500); + digitalWrite(POWER_STORAGE_1_PIN, HIGH); + delay(500); + currentSD = 2; +} + +WII5Controller wii5Controller; diff --git a/WII5Controller.h b/WII5Controller.h new file mode 100644 index 0000000..536d302 --- /dev/null +++ b/WII5Controller.h @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Controller.h + * @brief Main controller: mode dispatch, watchdog, and the top-level loop. + */ + +#ifndef WII5Controller_h +#define WII5Controller_h + +#include +#include + +/** + * @brief Top-level firmware controller. + * + * Owns the main loop(), watchdog tick, and dispatch to the active mode + * (Capture / Sleep / Position / ManualTest / SelfTest / LowBattery). + * Also tracks SD-card hand-off, the shared 5V rail, and "last hello" state + * used by mode transitions. + */ +class WII5Controller : public WII5 { + public: + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_CONTROLLER;} + + /** @brief One-time bring-up: zero counters, call mode-class begin()s, enable WDT. */ + void begin(); + /** @brief Spin in loopSafe() / loopWDT() for `waits` milliseconds. */ + void safeDelay(uint32_t waits); + /** @brief Current run-time mode. */ + WII5_MODES getMode(); + /** @brief Switch mode; returns false if the transition was rejected. */ + bool setMode(WII5_MODES newMode, bool nodelay = false); + /** @brief Reset to the EEPROM-configured default mode. */ + void setDefaultMode(); + /** @brief Move to whatever calculateCurrentMode() returns right now. */ + void setCurrentMode(); + /** @brief Compute the mode the buoy should be in given clock + battery + temp overrides. */ + WII5_MODES calculateCurrentMode(); + /** @brief Compute the persistent default mode from EEPROM configuration. */ + WII5_MODES calculateDefaultMode(); + + /** @brief Disable both SD-card chip selects. */ + void setSDOff(); + /** @brief Hand SD card 1 to the AVR side. */ + void setSD1(); + /** @brief Hand SD card 2 to the AVR side. */ + void setSD2(); + /** @brief Which SD card is currently selected (0/1/2). */ + uint8_t getSD(); + + /** @brief Enable the shared 5V rail (used by GPS, Iridium, Sparton, etc.). */ + void shared5On(); + /** @brief Disable the shared 5V rail. */ + void shared5Off(); + + /** @brief Record that a peer device replied to a Hello. */ + void setLastHello(WII5_PORTS p, WII5_DEVICES d); + /** @brief Has device `d` been heard from within the last `msOld` ms? */ + bool checkLastHello(WII5_DEVICES d, uint32_t msOld); + + // TODO These should not be contains ! Why dynamic generate? + // - Move them global - use #define instead + WII5ModeSleep *modeSleep; + WII5ModeCapture *modeCapture; + WII5ModeLowBattery *modeLowBattery; + WII5ModeManualTest *modeManualTest; + WII5ModePosition *modePosition; + WII5ModeSelfTest *modeSelfTest; + + /** @brief Main tick: runs WDT, the safe loop, and dispatches the active mode. */ + void loop(); + /** @brief Minimal-cost loop: console, buttons, Iridium, Communications. */ + void loopSafe(); + /** @brief Heavier-cost loop: Sparton, GPS, Battery, Weather, etc. */ + void loopOther(); + /** @brief Pet the watchdog (external pin and/or AVR internal WDT). */ + void loopWDT(); + /** @brief Print a one-line summary of controller state to the console. */ + void dump(); + + /** @brief Print the long-form `@Status` block to the console (human=true for human-readable). */ + void statusDump(bool human = false); + /** @brief Tri-state both SD chip-select lines. */ + void disableChipSelect(); + + time_t lastHelloReceived; + WII5_PORTS lastHelloPort; + WII5_DEVICES lastHelloDevice; + elapsedMillis lastHelloElapsed; + + bool tempDisableWDT; + + // Uptime in minutes - manually updated by minnute counter + uint32_t uptime; + + protected: + uint8_t currentSD; + + // Used in status calculate + uint32_t next; + uint16_t size; + uint32_t period; + + elapsedMillis minuteWait; + + elapsedMillis delayWait; + elapsedMillis wdtWait; + + // Main mode, and a mode for each of the sub modes + elapsedMillis modeWait; + WII5_MODES mode; + WII5_MODES lastMode; + // TODO how to get all last mode data and swtich back after tempo + time_t modeUntil;// TODO ? + + // TODO Data to store + // - Location, Date & Time - inside WII5GPS + // . Captured directly inside module + // . wii5GPS.status - ON, OFF, BUSY, ERROR + // . wii5GPS.error - Error object = time, number, string + // - Iridium Status - Inside WII5Iridium + // . Captured directly inside module + // - Maths Status - Either WII5Maths or WII5Controller + // . Captured via Console and processed in Controller + // - Capture Status - Inside WII5Capture + // - + +}; + +extern WII5Controller wii5Controller; +#endif diff --git a/WII5Data.h b/WII5Data.h new file mode 100644 index 0000000..21e780c --- /dev/null +++ b/WII5Data.h @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Data.h + * @brief Shared data type definitions: enums, status codes, error codes. + */ + +#ifndef WII5Data_h +#define WII5Data_h +/* + +WII5 - Data Structures + +*/ + +#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc +typedef unsigned long time_t; +#endif + +// Manual from Sh3d +#define SH3D_TYPE_COUNTER uint32_t +#define SH3D_TYPE_UNIQUEID uint64_t +#define SH3D_TYPE_PACKET_TYPE uint8_t +#define SH3D_TYPE_PACKET_VERSION uint8_t + + +// Number of seconds in... +#define WII5TIME_1WEEK (uint32_t)7*(uint32_t)24*(uint32_t)3600 +#define WII5TIME_1DAY (uint32_t)24*(uint32_t)3600 +#define WII5TIME_12HOUR (uint32_t)12*(uint32_t)3600 +#define WII5TIME_1HOUR (uint32_t)3600 + +/* + + Basic: Max 50 bytes + * Buoy ID (16 or 32 bit LSB of 64 bit NodeID) + * Date time + * Location Lat, Lon, Age (in minutes, 1 byte) + * Battery Voltage, Age (in miutes) + * Temperature Internal, Age (in minutes) + * Sleep information - ? + Full: Max 100 bytes + * More data? + Error: + * Basic + Error Number and String + + NOTE: Other types, such as Binary processed Maths come from the Maths CPU + +*/ + +enum WII5SELFTEST_DEVICES { + WII5STD_BOARD = 0, + WII5STD_GPS, + WII5STD_EEPROM, + WII5STD_SD1, + WII5STD_SD2, + WII5STD_IRIDIUM, + WII5STD_BATTERY, + WII5STD_MATHS, + WII5STD_RTC, + WII5STD_SPARTON, + WII5STD_18B20, + WII5STD_BUTTON, + WII5STD_LED, + WII5STD_BUZZER, + WII5STD_5VOLT, + WII5STD_DEVICES +}; + +enum WII5SELFTEST_STATUS { + WII5STS_NONE, // Never run + WII5STS_NC, // Not connected, not enabled, or not applicable + WII5STS_SUCCESS, // SUCCCESS - literally nothing to report + WII5STS_HUMAN, // HUMAN - we assume success, but need a human to confirm + WII5STS_SLOW, // SLOW - SUCCESS in all ways, but slower than expected. Good for SD, Maths boot, GPS lock etc + WII5STS_POWER, // Works, but power management may have failed (e.g. serial data still coming in when power off) + WII5STS_TIMEOUT, // Literally timed out. No failure we can actually understand. + WII5STS_OUTOFRANGE, // Out of range - voltage, or GPS or something out of range + WII5STS_UNKNOWN, // Some sort of bad, unknown error +}; + +enum WII5_DEVICES { + WII5DEVICE_UNKNOWN, + WII5DEVICE_WAVEBUOY, // This AVR - myself + WII5DEVICE_MATHS, // Maths CPU on the Console + WII5DEVICE_CONTROLLER, // Dedicated controller - e.g. handheld Wifi Accessed device + WII5DEVICE_CLI, // Simple CLI Serial port controller + WII5DEVICE_SERVER // Via the Server - e.g. remote command sent from Web Server +}; +enum WII5_CONTROLLERS { + WII5CONTROLLER_UNKNOWN, + WII5CONTROLLER_DRIVER, // ie. See Driver below + WII5CONTROLLER_CONFIG, + WII5CONTROLLER_CONTROLLER, + WII5CONTROLLER_COMMANDS, + WII5CONTROLLER_DISPLAY, + WII5CONTROLLER_HELP, + WII5CONTROLLER_SETUP, + WII5CONTROLLER_STRINGS, + WII5CONTROLLER_METADATA, +}; + +enum WII5_DRIVERS { + WII5DRIVER_UNKNOWN, + WII5DRIVER_GPS, + WII5DRIVER_MATHS, + WII5DRIVER_COMMS, + WII5DRIVER_COMMS_IRIDIUM, + WII5DRIVER_MPU9250, + WII5DRIVER_POWER, + WII5DRIVER_SPARTON, + WII5DRIVER_STORAGE, + WII5DRIVER_WEATHER_18B20, + WII5DRIVER_BATTERY, + WII5DRIVER_RADIO, + WII5DRIVER_RTC, + WII5DRIVER_WDT, +}; +enum WII5_PORTS { + WII5PORT_UNKNOWN, + WII5PORT_RADIO, // Came from the radio, probably LoRa + WII5PORT_COMMS, // Came from main comms, e.g. Iridium or similar + WII5PORT_CONSOLE, // Via the console, probably an end user (or Maths) + WII5PORT_MATHS // From the maths CPU - probably a processed command via web interface +}; + +enum WII5_MATHSMODE { + WII5MATHSMODE_UNKNOWN, + WII5MATHSMODE_AUTO, // Run in auto mode, start and stop as normal + WII5MATHSMODE_BUTTON, // Button was pressed, internally uses setHold + WII5MATHSMODE_COMMS, // Communication data came in +}; + +enum WII5_AREAS { + WII5AREA_UNKNOWN, + WII5AREA_WII5, + WII5AREA_HELP, + WII5AREA_SH3D +}; + +enum WII5_COMMANDS { + WII5COMMAND_NONE = 0, // Not yet parsed + WII5COMMAND_NA = 1, // Parsed but no good + WII5COMMAND_HELP = 10, // @WII5,help - see @Help + + WII5COMMAND_LOG_DEBUG = 11, // Show debugging + WII5COMMAND_LOG_DEFAULT = 12,// Normal level of logging + WII5COMMAND_LOG_FATAL = 13, // Fatals only + WII5COMMAND_LOG_ERROR = 14, // Error only + WII5COMMAND_LOG_TEST = 19, // Test out the log + + // Hello and ACK - mostly about chatting to Maths + WII5COMMAND_HELLO = 100, + WII5COMMAND_HELLOACK = 101, + + // Do it ! + WII5COMMAND_RESET = 102, // Reset the AVR now + WII5COMMAND_STORAGE_FORMAT = 103, // Format 1 or more storage devices, all data lost from there + + WII5COMMAND_ECHO = 110, + WII5COMMAND_PEOPLE = 111, + + WII5COMMAND_BATTERY_START = 112, + WII5COMMAND_WEATHER_READ = 113, + WII5COMMAND_SET = 114, + WII5COMMAND_SEND = 115, + WII5COMMAND_WEATHER_TEST = 116, + + // Set modes now - temporary until reboot etc + WII5COMMAND_MODE_SELFTEST = 200, + WII5COMMAND_MODE_MANUALTEST = 201, + WII5COMMAND_MODE_SLEEP = 202, + WII5COMMAND_MODE_DEFAULT = 203, + WII5COMMAND_MODE_POSITION = 204, + WII5COMMAND_MODE_CAPTURE = 205, + + // Status and Dumps + WII5COMMAND_STATUS = 300, // Optional Device Name + WII5COMMAND_DUMP = 301, + WII5COMMAND_WAITX = 302, + WII5COMMAND_WAITY = 303, + WII5COMMAND_WAITZ = 304, + WII5COMMAND_WAITQ = 305, + WII5COMMAND_WAITS = 306, + + // Settings - stored + WII5COMMAND_SETTING_LIST = 400, // List current settings + WII5COMMAND_SETTING_DEVICEID = 401, // Include Device ID to change + WII5COMMAND_SETTING_GPSTIMEOUT = 402, // GPSTimeout + WII5COMMAND_SETTING_DEFAULTS = 403, // optionally add DeviceId + WII5COMMAND_SETTING_RESET = 404, // Reset counters..... Danger + WII5COMMAND_SETTING_MODE = 405, // Saved Mode + WII5COMMAND_SETTING_TIME = 406, + WII5COMMAND_SETTING_DISABLELOWBATTERY = 407, + WII5COMMAND_SETTING_FLAGS = 408, + WII5COMMAND_SETTING_CAPTUREOPTIONS = 409, + WII5COMMAND_SETTING_POSITIONOPTIONS = 410, + WII5COMMAND_SETTING_SLEEPOPTIONS = 411, + WII5COMMAND_SETTING_BATTERYOPTIONS = 412, + + // Device - MIsc + WII5COMMAND_GPS_MODE = 500, + WII5COMMAND_GPS_DUMP = 501, + WII5COMMAND_GPS_PASSTHROUGH = 502, + WII5COMMAND_GPS_DEBUG = 503, + WII5COMMAND_GPS_START = 504, + WII5COMMAND_GPS_STOP = 505, + WII5COMMAND_GPS_TIME = 506, + WII5COMMAND_GPS_POS = 507, + WII5COMMAND_GPS_ACCURATE = 508, + WII5COMMAND_SPARTON_MODE = 510, + WII5COMMAND_SPARTON_DUMP = 511, + WII5COMMAND_SPARTON_DEBUG = 513, + WII5COMMAND_SPARTON_PASSTHROUGH = 514, + WII5COMMAND_SPARTON_INFO = 515, + + // Device - Maths + WII5COMMAND_MATHS_START = 550, // Turn On maths + WII5COMMAND_MATHS_STOP = 551, // Stop maths + WII5COMMAND_MATHS_BEEP = 552, // Beep ! + WII5COMMAND_MATHS_RESTART = 553, // Maths informing that it is restarting + WII5COMMAND_MATHS_SHUTDOWN = 554, // Maths being requested to shutdown + WII5COMMAND_MATHS_HALT = 555, // Maths informing that it is now halting + WII5COMMAND_MATHS_BOOTED = 556, // Maths tells us it is bootet d + WII5COMMAND_MATHS_KEEPALIVE = 557, // Maths informing "keep alive" (aka not shut shutdown) + WII5COMMAND_MATHS_WIFI_LOCAL = 558, // Tell Maths to try Local WIFI + WII5COMMAND_MATHS_WIFI_WAP = 559, // Tell Maths to create a WAP + WII5COMMAND_MATHS_SHELL = 560, // Send a direct shell command (can do anything). + WII5COMMAND_MATHS_HOLD = 561, // Hold vs Keep Alive.... hmmmm + WII5COMMAND_MATHS_FLIP = 562, // We are running all the time. Request a flip of the AVR + + // TODO Create new file, append, run - so we can send multiple packets, to create arbitrary long file + // WII5COMMAND_MATHS_S, // Send a direct shell command (can do anything). + + WII5COMMAND_MATHS_REPROCESS = 563, // Send maths request to reprocess a file + WII5COMMAND_MATHS_BUILD = 564, // Rebuild the maths code with some optional make directives + WII5COMMAND_MATHS_FIRMWARE = 565, // Update the firmware, inputs will be 'dev', 'local', 'latest, 'vn.n' (version) + WII5COMMAND_MATHS_PATCHLOCAL = 566, // Patch the local Firmware code and rebuild + WII5COMMAND_MATHS_UNTIL = 567, // Set the UNTIL - forced power switch on control + WII5COMMAND_MATHS_INFO = 568, // Show some info - what is the Maths CPU doing + WII5COMMAND_MATHS_DO = 569, + WII5COMMAND_MATHS_MAKE = 570, + WII5COMMAND_MATHS_SCRIPT = 571, // Actually - use this to add to QUEUE + WII5COMMAND_MATHS_DONE = 572, // Actually - use this to add to QUEUE + WII5COMMAND_MATHS_RETURN = 573, // RETURN line Turn on or off + + // Device - Storage cards + WII5COMMAND_STORAGE_START = 580, // Mode 1,2,3,4 + WII5COMMAND_STORAGE_STOP = 581, + WII5COMMAND_STORAGE_TESTFILE = 582, // TODO ls, chdir, get (filename) etc + WII5COMMAND_STORAGE_CONSOLEFILE = 583, + WII5COMMAND_STORAGE_SERIALFILE = 584, + + WII5COMMAND_STORAGE_SETSTATUS = 589, + WII5COMMAND_STORAGE_OFF = 590, + WII5COMMAND_STORAGE_SD1 = 591, + WII5COMMAND_STORAGE_SD2 = 592, + WII5COMMAND_STORAGE_LIST = 593, + WII5COMMAND_STORAGE_VIEW = 594, + WII5COMMAND_STORAGE_RESULTS = 595, + WII5COMMAND_STORAGE_DEBUG = 596, + WII5COMMAND_STORAGE_STATUS = 597, + WII5COMMAND_STORAGE_RAW = 598, + WII5COMMAND_STORAGE_METADATA = 599, + + // Device - Network / Radio + WII5COMMAND_NETWORK_ECHO = 600, + WII5COMMAND_NETWORK_ECHOACK = 601, + WII5COMMAND_NETWORK_FAKEECHO = 602, + WII5COMMAND_NETWORK_FAKEECHOACK = 603, + + // Instant commands for modes + WII5COMMAND_CAPTURE_START = 700, + WII5COMMAND_CAPTURE_STOP = 701, + WII5COMMAND_POSITION_START = 702, + WII5COMMAND_POSITION_STOP = 703, + WII5COMMAND_POSITION_PASSTHROUGH = 704, + + WII5COMMAND_BINDATA_LIST = 800, + WII5COMMAND_BINDATA_SPLIT = 801, + + // Iridium (90x) & Communications (91x) + WII5COMMAND_IRIDIUM_SEND = 901, + WII5COMMAND_COMMUNICATIONS_DISABLED = 911, + WII5COMMAND_COMMUNICATIONS_TEST = 912, + WII5COMMAND_COMMUNICATIONS_START = 913, + WII5COMMAND_COMMUNICATIONS_STOP = 914, + WII5COMMAND_COMMUNICATIONS_SIGNAL = 915, +}; + +// Results of a command request +#define WII5_RESULT_MESSAGE 250 +enum WII5_RESULTS { + WII5RESULTS_UNKNOWN = 0, // Not great, no idea what happened + WII5RESULTS_SUCCESS = 1, // Success - everything worked as you expected + WII5RESULTS_INVALID = 2, // Invalid - probably paramers, see message + WII5RESULTS_RETRY = 3, // Retry later + WII5RESULTS_ERROR = 4, // Error, see message + WII5RESULTS_NOREPLY = 5, // This command can't get a reply - e.g. Help +}; + +// Status of a Driver / Device +enum WII5_STATUS { + WII5STATUS_UNKNOWN, + WII5STATUS_OFF, + WII5STATUS_ON, + WII5STATUS_ON_VALID, + WII5STATUS_BUSY, + WII5STATUS_WAITING, + WII5STATUS_ERROR +}; + +// Error for device +// 50 = about 1K +// 10 = about 190 bytes +#define WII5_ERROR_STRING_MAX 5 +enum WII5_ERRORS { + WII5ERROR_UNKNOWN, + WII5ERROR_NONE, + WII5ERROR_RETRY, + WII5ERROR_INIT, + WII5ERROR_MEMORY, + WII5ERROR_TIMEOUT, + WII5ERROR_FATAL +}; +enum WII5_SERIALCMDS { + WII5SERIALCMD_NONE, + WII5SERIALCMD_OK, + WII5SERIALCMD_WAITING, + WII5SERIALCMD_TIMEOUT, + WII5SERIALCMD_ERROR +}; + +enum WII5_MODES { + WII5MODE_NONE, /// Invalid ! + + // Boot up + WII5MODE_BOOT, // First time only - after boot + WII5MODE_START, // First time only - after boot + + WII5MODE_DEMO, // Special mode which kind of does nothing + + // Normal modes + WII5MODE_SLEEP, // Sleeping (long term mode). + WII5MODE_LOWBATTERY, // Super low power mode + WII5MODE_POSITION, // Send Position Mode + WII5MODE_CAPTURE, // Capture - the main mode WII5MODE_MANUAL_TEST, // Special mode where we can turn things on and off. + + // Testing + WII5MODE_MANUALTEST, + WII5MODE_SELFTEST +}; + +// Other Config - keep order, and add to end, and check values in read +#define WII5_DATA_CONFIG_TYPE 5 +#define WII5_DATA_CONFIG_VERSION 2 +typedef struct { + uint8_t type; + uint8_t version; + + // Useful generic options + time_t disableLowBattery; // Disable until? + uint32_t flags; + uint32_t gpsTimeout; + + // TODO Battery calibration and modes? + // TODO Temperature sensor mapping? + + // MODE Capture options + // Periods: + // Minutes + // - 15 (0, 15, 30, 45 ...) + // - 30 (0, 30) + // - 60 (every hour) + // - 120 (once per hour) + // NOTE: Auto sleep when over 30 minutes? (or configurable) + // Number of Records ? + // What to process - ? Can we make this automatic + // What to return + // Could we "return data" only in certain circumstances + uint32_t capturePeriod; + uint32_t captureBinaryType; + uint32_t captureRecords; + + // MODE LowBattery + // None - see disableLowBattery above + + // MODE Position + uint32_t positionPeriod; + uint32_t positionBinaryType; + + // MODE Sleep + uint32_t sleepPeriod; // 0 or Invalid = 12 hours + uint32_t sleepBinaryType; + uint32_t sleepMaths; // Flags and Times for Maths CPU when waking from sleep + + // So... what mode and for how long + WII5_MODES defaultMode; + WII5_MODES temporaryMode; + WII5_MODES temporaryModeExpires; + + uint16_t batteryLow; + uint16_t batteryMid; + uint32_t sleepUntil; // When to auto go back to capture +} WII5_Data_Config; + +// Other Config - keep order, and add to end, and check values in read +#define WII5_DATA_STATUS_TYPE 6 +#define WII5_DATA_STATUS_VERSION 1 + +/* +typedef struct { + uint8_t type; + uint8_t version; + + // Sleep information + uint16_t sleepCountTotal; + uint16_t sleepCountThis; + time_t sleepLast; + + // Capture information + uint32_t captureCountTotal; + uint16_t captureCountThis; + + // Keep error counts and times to allow for temporary disabe etc + uint16_t buttonErrorCountTotal; + uint8_t buttonErrorCountThis; + time_t buttonErrorCountLast; + + uint16_t gpsErrorCountTotal; + uint8_t gpsErrorCountThis; + time_t gpsErrorCountLast; + + uint32_t rtcErrorCountTotal; + uint16_t rtcErrorCountThis; + time_t rtcErrorCountLast; + + uint32_t commsErrorCountTotal; + uint16_t commsErrorCountThis; + time_t commsErrorCountLast; + + uint32_t captureErrorCountTotal; + uint16_t captureErrorCountThis; + time_t captureErrorCountLast; + + uint32_t commsSendTries; + uint32_t commsSendSuccess; + +} WII5_Data_Status; +*/ + +enum WII5_FLAGS { + WII5FLAGS_GPSDISABLE, // GPS causing problems, turn it OFF + WII5FLAGS_RTCDISABLE, // RTC issues, turn it off + WII5FLAGS_RADIOOFF, // Radio issues, turn it off + WII5FLAGS_ENABLELOGS, // Store logs at record Id + WII5FLAGS_DEBUGLOGS, // Higher information + WII5FLAGS_DISABLEBEEP, // Hide them beeps away +}; + +enum WII5GPS_MODES { + WII5GPS_UNKNOWN, // aka Invalid + WII5GPS_OFF, // Turn it off - power and all + WII5GPS_ON, // On, but just collecting data - no serial out except errors + WII5GPS_TIME, // Wait just for first valid time stamp, set, keep, and send special NMEA and set to OFF + WII5GPS_POS, // Wait for position then off + WII5GPS_ACCURATE, // Like POS Once but more accurate data - ie. wait longer etc + WII5GPS_REPEAT // Send position evern "n" minutes (see setTime) (note, self manage on, off, power modes etc) +}; + +enum WII5_SWITCH { + WII5SWITCH_NONE, + WII5SWITCH_ON, + WII5SWITCH_OFF, + WII5SWITCH_TOGGLE +}; + +typedef struct { + uint32_t deviceId; + uint32_t recordCount; + + // 12. Date, Time etc + time_t last; + uint32_t age; // Update as written to disk - (now() - last) + uint32_t uptime; // Uptime in minutes (not sure how this goes wih sleep) +} WII5Command; + +enum WII5IRIDIUM_SEND_RESULT { + WII5IRIDIUMSENDRESULT_NONE, + WII5IRIDIUMSENDRESULT_OK, + WII5IRIDIUMSENDRESULT_FAILED_TIMEOUT, + WII5IRIDIUMSENDRESULT_FAILED_OTHER, +}; + +enum WII5IRIDIUM_RECEIVE_RESULT { + WII5IRIDIUMRECEIVERESULT_NONE, + WII5IRIDIUMRECEIVERESULT_OK, +}; + + +#define DdW time_t +#endif diff --git a/WII5DataShared.h b/WII5DataShared.h new file mode 100644 index 0000000..826dce6 --- /dev/null +++ b/WII5DataShared.h @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5DataShared.h + * @brief Cross-class shared data structures and constants. + */ + +// ********************************************************************** +// *** This file belongs in WII5_SD_Block *** +// ********************************************************************** + +// NOTE: Manually copied from WII5_Buoy - but unlikely to change +// NOTE: Not aligned - must manually extract characters +typedef struct { + uint8_t byteCount; + uint8_t status; // 3 bits error, 5 bits protocol + uint8_t channel; + float pose_x; + float pose_y; + float pose_z; + // float mag_x; + // float mag_y; + // float mag_z; + float accel_x; + float accel_y; + float accel_z; + // float gyro_x; + // float gyro_y; + // float gyro_z; + uint32_t stamp; + uint16_t crc; +} WII5_DATA_SpartonBinary; + +// Max size in Block Store = 488 +// NOTE: Manually copied from WII5_Buoy - Very likely to change !!! +typedef struct { + // 4. This device and Current Record + uint32_t deviceId; + uint32_t recordCount; + + // 12. Date, Time etc + time_t last; + uint32_t age; // Update as written to disk - (now() - last) + uint32_t uptime; // Uptime in minutes (not sure how this goes wih sleep) + + // 4. Software Versions + uint32_t version; // Bytes.... 0,n.n.n = vn.n.n + + // 8. Temperature + int32_t temperatureValue; + uint32_t temperatureAge; + + // 8. Battery + int32_t batteryValue; + uint32_t batteryAge; + + // 24. GPS + float gpsLat; + float gpsLon; + float gpsAlt; + int32_t gpsSat; + int32_t gpsHdop; + uint32_t gpsFixTime; + uint32_t gpsAge; + + // Iridium ? + + // 24. Capture + uint32_t captureWriteMax; + uint32_t captureWriteMin; + uint32_t captureWriteOver; + uint32_t captureTimeError; + uint32_t captureSizeError; + uint32_t captureStartTime; + + // 1. mode - what is current + uint8_t mode; + // TODO more !!! + + uint32_t iridiumFixTime; + uint32_t iridiumSignalQuality; + + // CAPTURE Info - e.g. number of errors etc +} WII5MetaDataObject; + +// ********************************************************************** +// *** This file belongs in WII5_SD_Block *** +// ********************************************************************** +// Processed output data +typedef struct { + // Reordered version + float part1float[73]; // 73*4 = 292 bytes + /* + float hz_max[4]; + float hcm_max[4]; + float htm_max[4]; + float tz_max[4]; + float tp; + float hmo; + float psd[55]; + */ + +} WII5Processed1; +typedef struct { + // Reordered version + float part2float[20]; // 20*4 = 80 + int part2int[62]; // 62*2 = 124 + // Total = 204 + /* + float moments[7]; + float theta; + float dp; + float s; + float r; + float hs_dir; + float a; + float b; + float nstd; + float f2; + float qf_mvar; + int qf_kist; + int qf_imu; + int qf_p_kist; + int qf_p_accel; + int qf_p_gyro; + int qf_p_mag; + int qf_head; + float power_diff; + float yaw_std; + int open_water; + int direction[54]; + */ +} WII5Processed2; + +typedef struct { + WII5Processed1 processed1; + WII5Processed2 processed2; +} WII5Processed; + +// ********************************************************************** +// *** This file belongs in WII5_SD_Block *** +// ********************************************************************** diff --git a/WII5Display.cpp b/WII5Display.cpp new file mode 100644 index 0000000..8064774 --- /dev/null +++ b/WII5Display.cpp @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Display.cpp + * @brief Status display helpers: SD-block view, formatted metadata dumps. + */ + +/* + +WII5Display + +*/ + +#include +#include + +// TODO Move to utils (could also be pinMode by different inputs) +uint8_t bit; +uint8_t port; +volatile uint8_t *reg; +volatile uint8_t *out; +int wii5_getPinMode(uint8_t pin) { + if (pin >= NUM_PINS) return (-1); + // TODO analog? + + bit = digitalPinToBitMask(pin); + port = digitalPinToPort(pin); + reg = portModeRegister(port); + if (*reg & bit) return (OUTPUT); + + out = portOutputRegister(port); + return ((*out & bit) ? INPUT_PULLUP : INPUT); +} + +uint32_t minutes; +uint32_t used; +uint32_t WII5Display::minutesUntilNext(uint32_t period) { + minutes = (hour() * 60) + minute(); + used = minutes % period; + return period - used; +} + + +// SD / Storage Display Helpers +void WII5Display::sdBlockView(uint32_t block_in, bool show_results, bool show_raw) { + // Read block +#ifdef WII5_DEBUG_DISPLAY + Serial.print(F("# BLOCK=")); + Serial.println(block_in); +#endif + if (!sdBlock.read(block_in)) { + console.log(LOG_ERROR, F("Unable to view block - %lu"), block_in); + return; + } + uint32_t dataBlockStart = sdBlock.metadata->dataBlockStart; + uint32_t dataBlocks = sdBlock.metadata->dataBlocks; + uint32_t resultsBlockStart = sdBlock.metadata->resultsBlockStart; + uint32_t resultsBlocks = sdBlock.metadata->resultsBlocks; + console.printf(F("@Block,metadata,address=%lu,deviceId=%lu,recordId=%lu\r\n"), + block_in, sdBlock.metadata->deviceId, sdBlock.metadata->recordId + ); + console.printf(F("@Block,metadata,dataBlockStart=%lu,dataBlocks=%lu"), + dataBlockStart, dataBlocks + ); + console.printf(F("@Block,metadata,resultsBlockStart=%lu,resultsBlocks=%lu"), + resultsBlockStart, resultsBlocks + ); + printMetadataBlock((WII5MetaDataObject*)&sdBlock.metadata->data); + if (show_results) { + console.printf(F("# Loading results block\r\n")); + if (!sdBlock.read(resultsBlockStart)) { + console.log(LOG_ERROR, F("Unable to view resultsBlock - %lu"), resultsBlockStart); + return; + } + + WII5Processed* processed = (WII5Processed*)sdBlock.block->data; + +#ifdef WII5_DEBUG_DISPLAY + void* a = &processed->processed1; + void* b = &processed->processed1.part1float; + void* c = &processed->processed2.part2float; + void* d = &processed->processed2.part2int; + + Serial.println(int(processed)); + Serial.println(int(a)); + Serial.println(int(b)); + Serial.println(int(c)); + Serial.println(int(d)); + + Serial.println(int(sizeof(WII5Processed))); + Serial.println(int(sizeof(WII5Processed1))); + Serial.println(int(sizeof(WII5Processed2))); + + for (uint8_t i = 0; i < 73; i++) { + Serial.println(processed->processed1.part1float[i]); + } + for (uint8_t i = 0; i < 20; i++) { + Serial.println(processed->processed2.part2float[i]); + } + for (uint8_t i = 0; i < 62; i++) { + Serial.println(processed->processed2.part2int[i]); + } +#else + (void)processed; +#endif + console.print("# END out/processed.out"); + console.printNewLine(); + } + + if (show_raw) { + console.log(LOG_ERROR, F("Raw view not yet implemented")); + } +} + +void WII5Display::printMetadataBlock(WII5MetaDataObject* metadata) { + // TODO MAke this use metadtat abvoe but look the same + console.printf(F("@Metadata,start,print block\r\n")); + console.printf(F("@Metadata,deviceId=%ld,recordId=%ld\r\n"), + metadata->deviceId, metadata->recordCount + ); + console.printf(F("@Metadata,time=%ld,uptime=%ld\r\n"), + metadata->last, metadata->uptime + ); + console.printf(F("@Metadata,temperature=%ld,battery=%ld\r\n"), + metadata->temperatureValue, metadata->batteryValue + ); + console.printf(F("@Metadata,lat=%ld,lon=%ld,alt=%ld,sats=%ld,hdop=%ld\r\n"), + long(wii5Gps.gps->location.lat() * GPS_POS_MULT), long(wii5Gps.gps->location.lng() * GPS_POS_MULT), long(wii5Gps.gps->altitude.meters() * GPS_POS_MULT), + metadata->gpsLat, metadata->gpsLon, metadata->gpsHdop + ); + + /* + console.printf(F("@Metadata,capture,timeError=%ld,sizeError=-%ld,writeMin=%ld,writeMax=%ld,writeOver=%ld\r\n"), + wii5Sparton.captureWriteMax, wii5Sparton.captureWriteMin, wii5Sparton.captureWriteOver, + wii5Sparton.statsTimeError, wii5Sparton.serialSizeError + ); + */ + console.printf(F("@Metadata,end,print block\r\n")); +} + +uint8_t a_start; +uint8_t a_end; +uint8_t p; +void WII5Display::dumpPins() { + a_start = analogInputToDigitalPin(0); + a_end = a_start + NUM_ANALOG_INPUTS - 1; + + console.printf(F("# Pins:\r\n")); + for (p = 0; p < NUM_PINS; p++) { + console.printf(F("# D%02d "), int(p)); + console.printf(F("%s "), wii5Strings.strPinMode(wii5_getPinMode(p))); + console.printf(F(" ")); + + // Long term - if analog - analogRead too or instead + if ((p >= a_start) && (p <= a_end)) { + console.printf(F("%04d"), analogRead(p)); + } + else { + console.printf(F("%s"), wii5Strings.strState(digitalRead(p))); + } + console.printf(F(" ")); + + console.printf(F("%s"), wii5Strings.strPinArduinoName(p)); + console.printf(F(" ")); + console.printf(F("%s"), wii5Strings.strPinWII5Name(p)); + + console.printf(F(" | ")); + + // TODO - Arduino name, D3, A4, MOSI, etc + // TODO - WII5 name - e.g. GPS Power Switch + + // 4 per line? + if ( ((p+1) % 3) == 0) { + console.printNewLine(); + } + } + console.printNewLine(); +} + +void WII5Display::atMathsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){ + console.printf(F("@Maths,")); + console.printf(area); + console.printf(F(",")); + va_list argptr; + va_start(argptr, out); + VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr); + va_end(argptr); + console.print(wii5BufferConsolePrint); + // console.printf(F(",%lu/%lu,"), wii5Config.getDeviceId(), wii5Config.getRecordCount()); + // console.printDateTime(), + console.printNewLine(); +} + +void WII5Display::atStatsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){ + console.printf(F("@Stats,")); + console.printf(area); + console.printf(F(",%lu/%lu,"), wii5Config.getDeviceId(), wii5Config.getRecordCount()); + console.printDateTime(), + console.printf(F(",")); + va_list argptr; + va_start(argptr, out); + VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr); + va_end(argptr); + console.print(wii5BufferConsolePrint); + console.printNewLine(); +} + +void WII5Display::atDataSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){ + console.printf(F("@Data,")); + console.printf(area); + console.printf(F(",%lu/%lu,"), wii5Config.getDeviceId(), wii5Config.getRecordCount()); + console.printDateTime(), + console.printf(F(",")); + va_list argptr; + va_start(argptr, out); + VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr); + va_end(argptr); + console.print(wii5BufferConsolePrint); + console.printNewLine(); +} + +// NOTE ! This is the StatusSerial - not the console +void WII5Display::statusSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){ + #ifdef WII5_STATUS_SERIAL + SerialStatus.print(area); + SerialStatus.print(","); + // Low level - assume we know device id - see if we can send it + // SerialStatus.print(wii5Config.getDeviceId()); + // SerialStatus.print("/"); + SerialStatus.print(wii5Config.getRecordCount()); + // SerialStatus.printDateTime(), + SerialStatus.print(F(",")); + va_list argptr; + va_start(argptr, out); + VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr); + va_end(argptr); + SerialStatus.print(wii5BufferConsolePrint); + SerialStatus.println(); + #endif +} + +void WII5Display::atCommandSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){ + console.printf(F("@WII5,")); + console.printf(area); + // TODO @WII5 commands do not normally have device and date details + console.printf(F(",%lu/%lu,"), wii5Config.getDeviceId(), wii5Config.getRecordCount()); + console.printDateTime(), + console.printf(F(",")); + va_list argptr; + va_start(argptr, out); + VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr); + va_end(argptr); + console.print(wii5BufferConsolePrint); + console.printNewLine(); +} + +void WII5Display::atCommsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ){ + console.printf(F("@Comms,")); + console.printf(area); + console.printf(F(",%lu/%lu,"), wii5Config.getDeviceId(), wii5Config.getRecordCount()); + console.printDateTime(), + console.printf(F(",")); + va_list argptr; + va_start(argptr, out); + VSNPRINTF(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, (const char *)out, argptr); + va_end(argptr); + console.print(wii5BufferConsolePrint); + console.printNewLine(); +} + +void WII5Display::printMetadata() { + console.printf(F("@Metadata,deviceId=%ld,recordId=%ld\r\n"), + wii5Config.getDeviceId(), wii5Config.getRecordCount() + ); + console.printf(F("@Metadata,time=%ld,uptime=%ld\r\n"), + now(), wii5Controller.uptime + ); + console.printf(F("@Metadata,temperature=%ld,battery=%ld\r\n"), + wii5Weather_18B20.value, wii5Battery.value + ); + #ifdef WII5_GPS + console.printf(F("@Metadata,lat=%ld,lon=%ld,alt=%ld,sats=%ld,hdop=%ld\r\n"), + long(wii5Gps.gps->location.lat() * GPS_POS_MULT), long(wii5Gps.gps->location.lng() * GPS_POS_MULT), long(wii5Gps.gps->altitude.meters() * GPS_POS_MULT), + wii5Gps.gps->satellites.value(), wii5Gps.gps->hdop.value() + ); + #endif + + console.printf(F("@Metadata,capture,timeError=%ld,sizeError=-%ld,writeMin=%ld,writeMax=%ld,writeOver=%ld\r\n"), + wii5Sparton.captureWriteMax, wii5Sparton.captureWriteMin, wii5Sparton.captureWriteOver, + wii5Sparton.statsTimeError, wii5Sparton.serialSizeError + ); + +} + +void WII5Display::updateMetadata(void* ref = NULL) { + if (ref) { + metadata = (WII5MetaDataObject*)ref; + } + if (metadata) { + memset(metadata, 0, sizeof(WII5MetaDataObject)); + metadata->deviceId = wii5Config.getDeviceId(); + metadata->recordCount = wii5Config.getRecordCount(); + + // TODO last is now? What about start of capture time? + metadata->last = now(); + metadata->age = 0; + metadata->uptime = wii5Controller.uptime; + metadata->temperatureValue = wii5Weather_18B20.value; + metadata->temperatureAge = wii5Weather_18B20.age; + metadata->batteryValue = wii5Battery.value; + metadata->batteryAge = wii5Battery.age; + #ifdef WII5_GPS + // TODO move to float + metadata->gpsLat = wii5Gps.gps->location.lat(); + metadata->gpsLon = wii5Gps.gps->location.lng(); + metadata->gpsAlt = wii5Gps.gps->altitude.meters(); + metadata->gpsSat = wii5Gps.gps->satellites.value(); + metadata->gpsHdop = wii5Gps.gps->hdop.value(); + // fixtime + // age + metadata->gpsAge = wii5Gps.gps->time.age(); + #endif + + metadata->captureWriteMax = wii5Sparton.captureWriteMax; + metadata->captureWriteMin = wii5Sparton.captureWriteMin; + metadata->captureWriteOver = wii5Sparton.captureWriteOver; + metadata->captureTimeError = wii5Sparton.statsTimeError; + metadata->captureSizeError = wii5Sparton.serialSizeError; + metadata->captureStartTime = wii5ModeCapture.startTime; + + metadata->mode = (uint8_t)wii5Controller.getMode(); + } +} + +WII5Display wii5Display; diff --git a/WII5Display.h b/WII5Display.h new file mode 100644 index 0000000..faf7325 --- /dev/null +++ b/WII5Display.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Display.h + * @brief Status display helpers: SD-block view, formatted metadata dumps. + */ + +#ifndef WII5Display_h +#define WII5Display_h + +#include + +class WII5Display { + public: + WII5Display() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DISPLAY;} + void dumpPins(); + + // TODO rename - very confusing + void statusSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ); + + uint32_t minutesUntilNext(uint32_t period); + + // Standard commands + void atMathsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ); + void atDataSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ); + void atStatsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ); + void atCommandSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ); + void atCommsSend(const __FlashStringHelper *area, const __FlashStringHelper *out, ... ); + void updateMetadata(void* ref = NULL); + void printMetadata(); + void printMetadataBlock(WII5MetaDataObject* metadata); + void sdBlockView(uint32_t block, bool view_md, bool view_raw); + + protected: + WII5MetaDataObject* metadata; + +}; + +extern WII5Display wii5Display; + +#endif diff --git a/WII5GPS.cpp b/WII5GPS.cpp new file mode 100644 index 0000000..e2a7fcb --- /dev/null +++ b/WII5GPS.cpp @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5GPS.cpp + * @brief GPS driver: NMEA parsing via TinyGPS++, time/position updates. + */ + +/* + WII5 GPS +*/ + +#include +#include +#include +#include + +// HARDWARE begin - Just once to configure +void WII5GPS::begin() { + enableNmea(); + powerSetPin(POWER_GPS_PIN, POWER_GPS_ON); + powerOff(true); // Forced off at boot + #ifdef WII5_BUFFER_GPS + setBuffer(wii5BufferGPS, WII5_BUFFER_GPS); + #endif + step = WII5GPS_OFF; + stepWait = 0; + running = false; + minutes = DEFAULT_MINUTES; + processMode = WII5SERIALPARSER_LINE; + lastTime = 864000000; // 10 days ago + sinceOnLast = 864000000; +}; + +// True if last GPS waas more than 12 hours ago +bool WII5GPS::old() { + return (lastTime > (WII5TIME_12HOUR * 1000)); +} + +void WII5GPS::off() { + step = WII5GPS_OFF; stepWait = 0; + stop(true); +} + +void WII5GPS::on() { + step = WII5GPS_ON; stepWait = 0; + start(true); + sinceOnLast = 0; +} + +void WII5GPS::autoTime() { + // Set mode to turn off after a Time, then step = OFF + step = WII5GPS_TIME; stepWait = 0; + start(true); +} + +void WII5GPS::autoPos() { + // Set mode to turn off after a position, then step = OFF + step = WII5GPS_POS; stepWait = 0; + start(true); +} + +void WII5GPS::autoAccurate() { + // Set mode to turn off after a more accurate position, then step = OFF + step = WII5GPS_ACCURATE; stepWait = 0; + start(true); +} + +void WII5GPS::autoRepeat() { + // Set mode to control GPS power on/off every N minutes to get a good time and position + // keep step here. + step = WII5GPS_REPEAT; stepWait = 0; + start(true); +} + +// start - and Stop - can be manually called but might get overwritten by modes in loop +void WII5GPS::start(bool force) { + if (!running || !stream || force) { + powerOn(force); + SerialGPS.begin(SerialGPS_Baud); + stream = &SerialGPS; + // waitTime = UPDATE_TIME; // Update as soon as ready + waitTime = 0; // TODO 2024 - Change to 0 and shorter UPDATE TIME + beginSerialManager(); + // NOTE: This requires modified TinyGPS++ now + /* TODO 2024 Removed - how to change? + gps->encodedCharCount = 0; + gps->sentencesWithFixCount = 0; + gps->failedChecksumCount = 0; + gps->passedChecksumCount = 0; + */ + finished = false; + running = true; + lastError = WII5ERROR_UNKNOWN; + runTime = 0; + sinceOnLast = 0; // Kept how long even with errors + } +} + +// stop +void WII5GPS::stop(bool force) { + if (running || force) { + stream = NULL; + SerialGPS.end(); + endSerialManager(); + powerOff(force); + running = false; + } +} + +// set and get minutes +void WII5GPS::setMinutes(uint8_t newMinutes) { + minutes = newMinutes; +} +uint8_t WII5GPS::getMinutes() { + return minutes; +} + +// loop +void WII5GPS::loop() { + if (running) { + WII5SerialManager::loop(); + + if ( + // Wait - usually 5 minutes before updating time again + (waitTime > UPDATE_TIME) + && (gps->date.isValid()) + && (gps->date.age() < 5000) + && (gps->time.isValid()) + && (gps->time.age() < 5000) + && (gps->satellites.isValid()) + && (gps->satellites.value() > 4) // 2024 moved back to 4 ? 2024 - Moved from 4 to 2, aka 5 satellites down to 3 + ) { + /* TODO 2024 + console.log(LOG_INFO, F("GPS: set time before %d-%d-%d %d:%d:%d"), + gps->date.year(), gps->date.month(), gps->date.day(), + gps->time.hour(), gps->time.minute(), gps->time.second() + ); + */ + console.log(LOG_INFO, F("GPS: Before setting time loop")); + when = now(); + setTime( + gps->time.hour(), gps->time.minute(), gps->time.second(), + gps->date.day(), gps->date.month(), gps->date.year() + ); + console.log(LOG_INFO, F("GPS: After setting time loop")); + + // Yay - we have a valid time - lets set this + lastTime = 0; + + // Ignore anything less than 1 second (is there a way to do before setTime) (10 secconds) + // TODO configurable... 10 seconds safe? + // TODO abs - does it work with long + //if ( ( (now() - when) > 10 ) || ( when - now() > 10) ) { + #ifdef WII5_RTC + wii5RTC.setRTC(); + #endif + //} + waitTime = 0; + } + } + + first = (step != stepLast); + stepLast = step; + + switch (step) { + // WII5GPS_OFF - Completely off, powered down, disabled. + case WII5GPS_OFF: + // NOTE: This is called every time, but does nothing if already stoppped. Just safety + if (first) + stop(); + // When to do more ? + // - e..g no DateTime so trigger GPS + break; + + // WII5GPS_QUIET - Turn on GPS and capture data normally, but don't print anything + // (passthrough now separate) + case WII5GPS_ON: + if (first) + start(); + break; + + case WII5GPS_TIME: + if (first) + start(); + // Valid and age is < 10 seconds??? + if ( + (stepWait > 5000) + && gps->date.isValid() + && (gps->date.age() < 10000) + && gps->time.isValid() + && (gps->time.age() < 10000) + && (gps->satellites.isValid()) + && (gps->satellites.value() > 4) // 2024 moved from 4 to 2 + ) { + lastError = WII5ERROR_NONE; + // Callback or other things? + finished = true; + lastRunTime = (uint32_t)(runTime / 1000); + // TODO Log and Display + step = WII5GPS_OFF; stepWait = 0; + } + else if (stepWait > (wii5Config.getGpsTimeout() * 1000)) { + lastError = WII5ERROR_TIMEOUT; + finished = true; + lastRunTime = (uint32_t)(runTime / 1000); + step = WII5GPS_OFF; stepWait = 0; + } + break; + + case WII5GPS_POS: + if (first) + start(); + // Valid and age is < 30 seconds??? + if ( + (stepWait > 5000) + && gps->location.isValid() + && (gps->time.age() < 20000) + && (gps->satellites.isValid()) + && (gps->satellites.value() >= 4) // 2024 Keep 3 for position + ) { + lastError = WII5ERROR_NONE; + // Callback or other things? + finished = true; + lastRunTime = (uint32_t)(runTime / 1000); + // TODO Log and Display + step = WII5GPS_OFF; stepWait = 0; + } + else if (stepWait > (wii5Config.getGpsTimeout() * 1000)) { + lastError = WII5ERROR_TIMEOUT; + finished = true; + lastRunTime = (uint32_t)(runTime / 1000); + step = WII5GPS_OFF; stepWait = 0; + } + break; + + case WII5GPS_ACCURATE: + if (first) + start(); + if ( + // TODO HDOP, VDOP, TDOP - and possibly other average + (stepWait > 5000) + && gps->location.isValid() + && (gps->date.isValid()) + && (gps->date.age() < 5000) + && (gps->time.isValid()) + && (gps->time.age() < 5000) + && (gps->satellites.isValid()) + && (gps->satellites.value() >= 4) // 2024 - moved from >4 to >=4 aka 5 to 4 for accurate + ) { + lastError = WII5ERROR_NONE; + // Callback or other things? + finished = true; + lastRunTime = (uint32_t)(runTime / 1000); + // TODO Log and Display + console.log(LOG_INFO, F("GPS: Accurate Complete")); + console.log(LOG_INFO, F("GPS: Before setting time accurate")); + when = now(); + setTime( + gps->time.hour(), gps->time.minute(), gps->time.second(), + gps->date.day(), gps->date.month(), gps->date.year() + ); + console.log(LOG_INFO, F("GPS: After setting time accurate")); + lastTime = 0; + waitTime = 0; + step = WII5GPS_OFF; stepWait = 0; + } + else if (stepWait > (wii5Config.getGpsTimeout() * 1000)) { + lastError = WII5ERROR_TIMEOUT; + finished = true; + lastRunTime = (uint32_t)(runTime / 1000); + step = WII5GPS_OFF; stepWait = 0; + } + break; + + case WII5GPS_REPEAT: + if (first) + start(); + if ( + (stepWait > 60000) + && gps->location.isValid() + && (gps->time.age() < 20000) + && (gps->satellites.isValid()) + && (gps->satellites.value() >= 4) // 2024 - moved from >4 to >=4 + ) { + stepWait = 0; + // TODO Log and Display + } + break; + + default: + step = WII5GPS_OFF; stepWait = 0; + break; + } + return; +} + +bool WII5GPS::isTimeValid() { + // Less than 12 hours since we had a good lock? + return ( + (lastTime < (WII5TIME_12HOUR * 1000)) + && (year() >= 2019) + // TODO EWWWW YEAH EWWW I KNOW ! + && (year() < 2045) + ); +} + +bool WII5GPS::ready() { + return finished; +} +bool WII5GPS::isError() { + return (lastError != WII5ERROR_NONE); +} + +bool WII5GPS::isRunning() { + return (step != WII5GPS_OFF); +} + +void WII5GPS::dump(bool toConsole, Print* toOther) { + // Error + if (isError()) { + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Metadata,gps,error=timeout\r\n") + ); + } + else { + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Metadata,gps,error=none\r\n") + ); + } + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + // Position + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Metadata,gps,position,%ld,%ld,%ld,%lu\r\n"), + long(gps->location.lat() * GPS_POS_MULT), + long(gps->location.lng() * GPS_POS_MULT), + long(gps->altitude.meters() * GPS_ALT_MULT), + gps->location.age() + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + // Speed and Course + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Metadata,gps,direction,%ld,%ld,%lu\r\n"), + long(gps->course.deg() * 100), + long(gps->speed.kmph() * 100), + gps->speed.age() + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + // Datetime + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Metadata,gps,datetime,%04d-%02d-%02dT%02d:%02d:%02d,%lu,%lu\r\n"), + int(gps->date.year()), int(gps->date.month()), int(gps->date.day()), + int(gps->time.hour()), int(gps->time.minute()), int(gps->time.second()), + gps->date.value(), gps->date.age() + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + // Quality + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Metadata,gps,quality,satellites=%lu,hdop=%lu,age=%lu\r\n"), + gps->satellites.value(), gps->hdop.value(), + gps->date.age() + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + // Stats + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Metadata,gps,stats,locktime=%lu,chars=%lu,sentences=%lu\r\n"), + lastRunTime, gps->charsProcessed(), gps->sentencesWithFix() + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + +} + +WII5GPS wii5Gps; diff --git a/WII5GPS.h b/WII5GPS.h new file mode 100644 index 0000000..f780560 --- /dev/null +++ b/WII5GPS.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5GPS.h + * @brief GPS driver: NMEA parsing via TinyGPS++, time/position updates. + */ + +#ifndef WII5GPS_h +#define WII5GPS_h + +/* +So one second after the module is powered on normally, +you can have the actual RTC time in the $GPRMC NMEA sentence, +even if the fixing tells 'V' like 'void' (invalid). But the time is valid +RTC time, if your battery backup did not run out of +power since you powered the system off. + + */ + + + +#include +#include + +#define DEFAULT_MINUTES 5 +#define UPDATE_TIME 60000 // TODO 2024 1 minute + +/** + * @brief GPS driver: u-blox NMEA via TinyGPS++. + * + * Manages power-on, fix acquisition, and clock-update flows. NMEA sentences + * are parsed off the WII5SerialManager base; the result feeds the global + * `wii5Gps.gps` (TinyGPSPlus) instance for location/altitude/HDOP queries. + */ +class WII5GPS : public WII5Power, public WII5SerialManager { + public: + WII5GPS() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;} + virtual WII5_DRIVERS driverId() {return WII5DRIVER_GPS;} + + /** @brief One-time bring-up; pin config comes from the active board file. */ + void begin(); + /** @brief State-machine tick: drains NMEA, advances mode. */ + void loop(); + + /** @brief Set how long the GPS stays on each acquisition window. */ + void setMinutes(uint8_t newMinutes); + /** @brief Current acquisition window length, in minutes. */ + uint8_t getMinutes(); + + /** @brief Print a status dump; `toOther` optionally mirrors to a second Print. */ + void dump(bool toConsole = true, Print* toOther = NULL); + + /** @brief Power off the GPS module. */ + void off(); + /** @brief Power on the GPS module. */ + void on(); + /** @brief One-shot acquisition tuned for time only. */ + void autoTime(); + /** @brief One-shot acquisition tuned for position. */ + void autoPos(); + /** @brief Long acquisition for high-quality fix. */ + void autoAccurate(); + /** @brief Periodic re-acquisition (continuous mode). */ + void autoRepeat(); + + /** @brief True once a valid fix is available. */ + bool ready(); + /** @brief True if the most recent acquisition failed. */ + bool isError(); + + /** @brief True if the time fix is valid (independent of position). */ + bool isTimeValid(); + /** @brief True if a state-machine cycle is in flight. */ + bool isRunning(); + + uint32_t lastRunTime; + time_t when; + /** @brief True if the last fix is older than the configured timeout. */ + bool old(); + + elapsedMillis sinceOnLast; // Simple - check how long since it has last been on + protected: + virtual void start(bool force = false); + void stop(bool force = false); + + WII5_ERRORS lastError; + + bool finished; // The reqeust - e.g. wait for position + bool running; + WII5GPS_MODES step; + WII5GPS_MODES stepLast; + bool first; + elapsedMillis stepWait; + uint8_t minutes; + + elapsedMillis waitTime; + elapsedMillis runTime; + elapsedMillis lastTime; // Last time it was valid +}; +extern WII5GPS wii5Gps; + +#endif diff --git a/WII5Help.cpp b/WII5Help.cpp new file mode 100644 index 0000000..e4d027a --- /dev/null +++ b/WII5Help.cpp @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Help.cpp + * @brief Help-text generator for the `@Help` console command. + */ + +/* + +WII5Help + +Top Commands + + * Inputs: + @Help + @WII5 + + * Outputs: + @List + @File + +*/ + +#include +#include + +bool WII5Help::processConsoleCsv() { + // Top page + if (console.getCsvCount() < 2) + return displayIndex(); + + if (console.getCsvCount() < 3) { + if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("info")) == 0) + return displayInfo(); + else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("general")) == 0) + return displayGeneral(); + else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("advanced")) == 0) + return displayAdvanced(); + else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("setting")) == 0) + return displaySettings(); + else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("maths")) == 0) + return displayMaths(); + else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("storage")) == 0) + return displayStorage(); + else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("gps")) == 0) + return displayGps(); + else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("iridium")) == 0) + return displayIridium(); + else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("sparton")) == 0) + return displaySparton(); + else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("hardware")) == 0) + return displayHardware(); + else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("data")) == 0) + return displayData(); + else if (strcmp_P(console.getCsvBuffer(1),(PGM_P) F("testing")) == 0) + return displayTesting(); + } + + // 404 - Not found + return display404(); +} + +// TODO Look at help modules Arduino +bool WII5Help::displayIndex() { + console.printf(F("WII5: Help - page = Index\r\n")); + console.printf(F("Commands list:\r\n")); + return displayList(); +} + +// Not found ! +bool WII5Help::display404() { + console.printf(F("NOT FOUND!")); + return false; +} + +// List help pages +bool WII5Help::displayList() { + console.printf(F("# @Help COMMANDS:\r\n")); + console.printf(F("# - @Help - Here\r\n")); + console.printf(F("# - @Help,info - how to get information\r\n")); + console.printf(F("# - @Help,general - display general commands\r\n")); + console.printf(F("# - @Help,testing - how to run manual and self testing modes\r\n")); + console.printf(F("# - @Help,hardware - brief information on hardware\r\n")); + console.printf(F("# - @Help,data - brief information on data formats\r\n")); + console.printf(F("# - @Help,setting\r\n")); + console.printf(F("# - @Help,storage\r\n")); + console.printf(F("# - @Help,gps\r\n")); + console.printf(F("# - @Help,iridium - iridium and communications menu\r\n")); + console.printf(F("# - @Help,sparton\r\n")); + return true; +} +bool WII5Help::displayInfo() { + console.printf(F("# @Help info:\r\n")); + console.printf(F("# * @WII5,hello - TODO 2024\r\n")); + console.printf(F("# * @WII5,people - List people\r\n")); + console.printf(F("# * @WII5,status - TODO 2024\r\n")); + console.printf(F("# * @WII5,dump - TODO 2024\r\n")); + return true; +} +bool WII5Help::displayGeneral() { + console.printf(F("# @Help general:\r\n")); + console.printf(F("# * @WII5,General\r\n")); + console.printf(F("# * @WII5,reset,666 - Reset now - must have 666\r\n")); + console.printf(F("# * @WII5,status - List status of all objects\r\n")); + console.printf(F("# * @WII5,log,default - Log level set to default\r\n")); + console.printf(F("# * @WII5,log,debug - Log level set to debug\r\n")); + console.printf(F("# * @WII5,log,error - Log level set to error\r\n")); + console.printf(F("# * @WII5,log,fatal - Log level set to fatal\r\n")); + console.printf(F("# * @WII5,log,test - Test the log levels\r\n")); + // TODO 2024 change this to date in #define? TODO 2024 better copyright + console.printf(F("# * Copyright PAS Consultants Pty. Ltd. 2024\r\n")); + return true; +} +bool WII5Help::displayAdvanced() { + console.printf(F("# @Help advanced:\r\n")); + console.printf(F("# * @WII5,help\r\n")); + return true; +} +bool WII5Help::displaySettings() { + console.printf(F("# @Help setting:\r\n")); + console.printf(F("# * @WII5,setting,list\r\n")); + console.printf(F("# * @WII5,setting,deviceid,{n} (10000 to 99999)\r\n")); + console.printf(F("# This is written to EEPROM and used for all directories\r\n")); + console.printf(F("# * @WII5,setting,time,yyyy,mm,dd,hh,mm,ss\r\n")); + console.printf(F("# eg: @WII5,setting,time,2017,01,07,11,01,02\r\n")); + console.printf(F("# to set the time from an authoratataive source (e.g. NTPD)")); + console.printf(F("# * @WII5,setting,mode,default|yyyy-mm-ddThh:mm|time_t,v1,v2,v3,v4\r\n")); + console.printf(F("# If you add a date, time it will be saved to temporary mode\r\n")); + console.printf(F("# * @WII5,(TODO)mode,default|time_t future,mode,x,y,z\r\n")); + console.printf(F("# * @WII5,setting,defaults\r\n")); // NOTE, add 3759 to reset counters too + console.printf(F("# * @WII5,setting,gpstimeout,{seconds}\r\n")); + console.printf(F("# * @WII5,setting,captureoptions,{minutes},{binaryType}\r\n")); + console.printf(F("# Type and Records can be 0 or left off, minutes is required.\r\n")); + console.printf(F("# eg: @WII5,setting,captureoptions,15,03,5120\r\n")); + console.printf(F("# Minutes in caption must be between 15 (1/4 hr) and 720 (12 hr)\r\n")); + console.printf(F("# * @WII5,setting,positionoptions,{minutes},{binaryType}\r\n")); + console.printf(F("# eg: @WII5,setting,positionoptions,5,01\r\n")); + console.printf(F("# * @WII5,setting,sleepoptions,{minutes},{binaryType}\r\n")); + console.printf(F("# eg: @WII5,setting,sleepoptions,720,01\r\n")); + console.printf(F("# Minutes in sleep must be between 60 (1hr) and 1440 (24 hr)\r\n")); + console.printf(F("# * @WII5,help\r\n")); + return true; +} + +bool WII5Help::displayMaths() { + console.printf(F("# @Help maths:\r\n")); + console.printf(F("# Controlling the maths CPU\r\n")); + console.printf(F("# * @WII5,maths,status - current status\r\n")); + console.printf(F("# * @WII5,maths,start - startup the CPU\r\n")); + console.printf(F("# * @WII5,maths,stop - stop the CPU\r\n")); + console.printf(F("# * @WII5,maths,restart - Restart cpu (forced)\r\n")); + console.printf(F("# * @WII5,maths,flip - Stop CPU, flip SD cards and restart CPU\n")); + console.printf(F("# * @WII5,maths,beep - Beep this AVR - sent from the Maths CPU\r\n")); + console.printf(F("# * @WII5,maths,hold,{n} - Hold maths on for {n} minutes\r\n")); + return true; +} + +bool WII5Help::displayStorage() { + console.printf(F("# @Help storage:\r\n")); + console.printf(F("# Storage class is used to manuall control and retrieve data\r\n")); + console.printf(F("# from the storage devices.\r\n")); + console.printf(F("# * @WII5,storage,status\r\n")); + console.printf(F("# * @WII5,storage,off\r\n")); + console.printf(F("# * @WII5,storage,sd1\r\n")); + console.printf(F("# * @WII5,storage,sd2\r\n")); + console.printf(F("# * @WII5,storage,list\r\n")); + console.printf(F("# * @WII5,storage,view,{block_id}\r\n")); + console.printf(F("# * @WII5,storage,results,{id}\r\n")); + console.printf(F("# * @WII5,storage,debug,{0|1}\r\n")); + console.printf(F("# * @WII5,storage,format,1\r\n")); + console.printf(F("# * @WII5,storage,format,2\r\n")); + + /* + console.printf(F("# * @WII5,storage,this1maths2\r\n")); + console.printf(F("# * @WII5,storage,this2maths1\r\n")); + console.printf(F("# * @WII5,storage,dir,{new} - Get direcotry or set directory\r\n")); + console.printf(F("# * @WII5,storage,ls - List files\r\n")); + console.printf(F("# * @WII5,storage,records - List record IDs\r\n")); + console.printf(F("# * @WII5,storage,record,{id} - Get {current}, {next} or {n}\r\n")); + console.printf(F("# * @WII5,storage,read,{filename} - start reading {filename}\r\n")); + console.printf(F("# * @WII5,storage,read! - stop reading\r\n")); + */ + return true; +} +bool WII5Help::displayGps() { + console.printf(F("# @Help gps:\r\n")); + console.printf(F("# * @WII5,gps,status\r\n")); + console.printf(F("# * @WII5,gps,dump\r\n")); + console.printf(F("# * @WII5,gps,start\r\n")); + console.printf(F("# * @WII5,gps,stop\r\n")); + console.printf(F("# * @WII5,gps,passthrug,0|1\r\n")); + console.printf(F("# * @WII5,gps,debug,0|1\r\n")); + console.printf(F("# * @WII5,gps,autopos\r\n")); + return true; +} +bool WII5Help::displayIridium() { + console.printf(F("# @Help iridium:\r\n")); + console.printf(F("# * @WII5,communications,disabled,{0|1} - Set communications to be disabled or enabled\r\n")); + console.printf(F("# * @WII5,iridium,status - TODO 2024 most of these are not written, fix\r\n")); + console.printf(F("# * @WII5,iridium,off - force fully off, including power\r\n")); + console.printf(F("# * @WII5,iridium,last - Show again last data, inclding in progress\r\n")); + console.printf(F("# * @WII5,iridium,metadata - Firmware, Time, Signal Quality\r\n")); + console.printf(F("# * @WII5,iridium,sendtext,{rest is text}\r\n")); + console.printf(F("# * @WII5,iridium,sendbinary,{length} - must already be set in binary console\r\n")); + return true; +} +bool WII5Help::displaySparton() { + console.printf(F("# @Help sparton:\r\n")); + console.printf(F("# * @WII5,sparton,off - power off sparton\r\n")); + return true; +} + +bool WII5Help::displayHardware() { + console.printf(F("# @Help hardware:\r\n")); + // TODO 2024 add + return true; +} + +bool WII5Help::displayData() { + console.printf(F("# @Help data:\r\n")); + console.printf(F("# @WII5,bindata,\r\n")); + return true; +} + +bool WII5Help::displayTesting() { + console.printf(F("# @Help testing:\r\n")); + // TODO 2024 add + console.printf(F("# * @WII5,weather,read - read the temperature sensor and display\r\n")); + console.printf(F("# * @WII5,TODO 2024\r\n")); + return true; +} + +WII5Help wii5Help; diff --git a/WII5Help.h b/WII5Help.h new file mode 100644 index 0000000..eb81e89 --- /dev/null +++ b/WII5Help.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Help.h + * @brief Help-text generator for the `@Help` console command. + */ + +#ifndef WII5Help_h +#define WII5Help_h + +#include +#include + +class WII5Help { + public: + WII5Help() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_HELP;} + bool processConsoleCsv(); + + protected: + bool displayIndex(); + bool display404(); + bool displayList(); + bool displayInfo(); + bool displayGeneral(); + bool displayMaths(); + bool displayHardware(); + bool displayData(); + bool displayTesting(); + bool displayAdvanced(); + bool displaySettings(); + bool displaySparton(); + bool displayIridium(); + bool displayGps(); + bool displayStorage(); +}; + +extern WII5Help wii5Help; + +#endif diff --git a/WII5Iridium.cpp b/WII5Iridium.cpp new file mode 100644 index 0000000..4a6857e --- /dev/null +++ b/WII5Iridium.cpp @@ -0,0 +1,851 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Iridium.cpp + * @brief Iridium 9602/9603 SBD modem driver: AT-command state machine. + */ + +/* + +WII5Iridium: + +TODO 2024 - Seems fairly stable, check TODO and review code + +TOOO + Status + Examoe=oles, abny reference= etc. + Foundby=xm ... Daee + +How to structure a message: + + * Adding up size of Structs - if < 340 (Iridium Size) + WII5_IRIDIUM_BIN_MAX + Then just build it up into that binary entry + * If larger.... + NFI + * Receive - assume one - and goes into bin buffer then processed + * Where is it proessed + +Code Source: + + * WII2_Board/WII2_WaveSim/WII2_WaveSim.ino + * NA + * WII2_Board/WII2_WaveBuoy/WII2_WaveBuoy.ino + * IridiumSBD Library + #include + IridiumSBD isbd(SerialIridium); // , onPin); + * WII2_Board/Iridium/ + * NA + + * States/Iridium_Library + * NA + * Tracker/IridumData/IridiumData.ino + * IridiumSBD Library + * WII2_Board/Iridium + * NA + += What do we want to do = + +== Automatic processing == + + * loop() + * Processes any mode it is in, including off + * + +== Manual Helpers == + + * bool off() + * Returns true if able to switch off and turns to off mode. + * Returns false, if busy and unable to change mode + * bool status() + * Start status if you can. Will change to off when complete, see lastStatus + * Read lastStatus + * bool sendSimple(Messaage) + * Send a simple message, text or binary + * Returns true to say it is prepared + * bool sendStart(); - Setup to send messages - only if not already there + * uint8_t sendStatus(); - Return status so you can send a message + * bool sendMessage(Message); - Return true if buffered and trying, false if not sendStart, or sendStatus is busy + * bool sendEnd(); - End send - return true if we can. Will turn off automatically. + +== Examples == + + * setup() + * loop() + iridum.loop(); + + Want to get the updated status + + // If older than 5 minutes + if (lastStatusMillis > 300000) + .status() + +*/ + + +#include +#include +#include +#include + +#ifdef WII5_COMMS_IRIDIUM +void WII5Iridium::begin() { + SerialComms.begin(SerialComms_Baud); + #ifdef WII5_BUFFER_IRIDIUM + setBuffer(wii5BufferIridium, WII5_BUFFER_IRIDIUM); + #else + console.log(LOG_ERROR, F("Iridium: Error, not setting buffer")); + #endif + powerSetPin(POWER_COMMS_PIN, POWER_COMMS_ON); + powerOff(true); // Forced off at boot + step = WII5IRIDIUM_OFF; + running = false; + initialized = false; + imei[0] = '\0'; + sigQualRetry = 0; + sigQualMin = 2; +} + +void WII5Iridium::start(bool force) { + if (debug) + console.log(LOG_DEBUG, F("IRIDIUM: start current=%d"), step); + if (step == WII5IRIDIUM_OFF) { + recordCount = 0; sendCount = 0; + step = WII5IRIDIUM_POWER; stepWait = 0; + } + powerOn(); +} + +void WII5Iridium::stop(bool force) { + if (debug) + console.log(LOG_DEBUG, F("IRIDIUM: stop current=%d"), step); + if (step != WII5IRIDIUM_OFF) { + // Delays - request off, that sort of thing... ! YES ! TODO + step = WII5IRIDIUM_OFF; stepWait = 0; + } + powerOff(force); +} + +bool WII5Iridium::isRunning() { + return running; +} + +// on +void WII5Iridium::powerOn(bool force) { + if (!running || force) { + if (debug) + console.log(LOG_DEBUG, F("IRIDIUM: power ON")); + running = true; + wii5Controller.shared5On(); + WII5Power::powerOn(); + SerialComms.begin(SerialComms_Baud); + stream = &SerialComms; + beginSerialManager(); + } +} + +// off +void WII5Iridium::powerOff(bool force) { + if (running || force) { + if (debug) + console.log(LOG_DEBUG, F("IRIDIUM: power off")); + running = false; + SerialComms.end(); + stream = NULL; + WII5Power::powerOff(); + endSerialManager(); + wii5Controller.shared5Off(); + } +} + + +void WII5Iridium::loop() { + WII5SerialManager::loop(); + + bool first = (step != stepLast); + stepLast = step; + + switch (step) { + // WII5Iridium_OFF - Completely off, powered down, disabled. + case WII5IRIDIUM_OFF: + if (first) { + currentType = WII5IRIDIUM_REQUEST_NONE; + initialized = false; + wii5Display.atCommsSend(F("status"), F("off")); + powerOff(); + + /* + if (strlen(imei[0]) == 0) { + // TODO No IMEI - Turn on Iridium to get + // TODO Don't continue to do this. If done - stop, even if we dont' have one + requestFirmware(); + } + */ + } + break; + + case WII5IRIDIUM_POWER: + initialized = false; + powerOn(); + // Wait half a second then spin on + if (stepWait > 1000) { + step = WII5IRIDIUM_ALIVE; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step power->alive")); + } + break; + + case WII5IRIDIUM_ALIVE: + initialized = false; + sendLine(0); + // This probably needs to be _AT (not yet written) + processMode = WII5SERIALPARSER_OKHUH; + // Boot up config + last = WII5SERIALLAST_NONE; sendCount = 0; + step = WII5IRIDIUM_BOOT; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step boot_stop->boot_wait")); + break; + + // Boot up config + case WII5IRIDIUM_BOOT: + if (first) { + programCount = 0; programTotal = 3; + setTimeout(15000); + } + switch (sendAll()) { + // Still waiting + case WII5SERIALCMD_WAITING: + break; + // Error + case WII5SERIALCMD_TIMEOUT: + console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (sendAll:0-3): %s"), buffer); + case WII5SERIALCMD_ERROR: + console.log(LOG_ERROR, F("SERIAL: ERROR sending (sendAll:0-3): %s"), buffer); + + // TODO Do a retry count here, and possible power cycle + + // Success + case WII5SERIALCMD_OK: + default: + // TODO - Consider starting again with OFF, then step back to READY etc etc + step = WII5IRIDIUM_FIRMWARE; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step program_base->program_start")); + break; + } + break; + + // Firmware + case WII5IRIDIUM_FIRMWARE: + if (first) { + sendCount = 0; + } + else if (stepWait > 500) { + // TODO This is because we have shared buffer. FFS + // TODO also... this could blat half read in data ? + if (sendCount == 0) { + programLine(4); + setTimeout(15000); + } + switch (sendAndWait(WII5SERIALLAST_BOOTVERSION)) { + // Still waiting + case WII5SERIALCMD_WAITING: + break; + + // Move on + case WII5SERIALCMD_TIMEOUT: + console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (programLine:4): %s"), buffer); + case WII5SERIALCMD_ERROR: + console.log(LOG_ERROR, F("SERIAL: ERROR sending (programLine:4): %s"), buffer); + case WII5SERIALCMD_OK: + default: + // TODO How to keep the return? + step = WII5IRIDIUM_IMEI; stepWait = 0; + if (debug) console.log(LOG_DEBUG, F("step firmware->signalquality")); + break; + }; + } + break; + + // IMEI + case WII5IRIDIUM_IMEI: + if (first) { + sendCount = 0; + } + else if (stepWait > 500) { + // TODO This is because we have shared buffer. FFS + if (sendCount == 0) { + programLine(5); + setTimeout(15000); + } + switch (sendAndWait(WII5SERIALLAST_NUMBER)) { + // Still waiting + case WII5SERIALCMD_WAITING: + break; + + // Move on + case WII5SERIALCMD_TIMEOUT: + console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (programLine:5): %s"), buffer); + step = WII5IRIDIUM_SIGNALQUALITY; stepWait = 0; + break; + case WII5SERIALCMD_ERROR: + console.log(LOG_ERROR, F("SERIAL: ERROR sending (programLine:5): %s"), buffer); + step = WII5IRIDIUM_SIGNALQUALITY; stepWait = 0; + break; + case WII5SERIALCMD_OK: + default: + // TODO Woooh.... should be checking length etc. + strcpy(imei, buffer); + wii5Display.atCommsSend(F("info"), F("imei=%s"), imei); + step = WII5IRIDIUM_SIGNALQUALITY; stepWait = 0; + } + } + break; + + // SYSTEMTIME + // case WII5IRIDIUM_SYSTEMTIME: + + case WII5IRIDIUM_SIGNALQUALITY_WAIT: + if (first) { + if (debug) + console.log(LOG_ERROR, F("Irigin: Waiting n seconds before retry signal quality")); + } + // 15 second delay to retry Signal Quality + // TODO for now 5 seconds, alo... should be able check this + // TODO 2024 - Making this 20 seconds + else if (stepWait > 20000) { + if (debug) + console.log(LOG_ERROR, F("Irigin: Waiting complete")); + step = WII5IRIDIUM_SIGNALQUALITY; stepWait = 0; + } + break; + + // Signal Quality + case WII5IRIDIUM_SIGNALQUALITY: + if (first) { + sendCount = 0; + } + else if (stepWait > 500) { + // TODO This is because we have shared buffer. FFS + if (sendCount == 0) { + programLine(7); + setTimeout(30000); + } + switch (sendAndWait(WII5SERIALLAST_ATCSQ)) { + // Still waiting + case WII5SERIALCMD_WAITING: + break; + // Move on + case WII5SERIALCMD_TIMEOUT: + console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (programLine:7): %s"), buffer); + case WII5SERIALCMD_ERROR: + console.log(LOG_ERROR, F("SERIAL: ERROR sending (programLine:7): %s"), buffer); + case WII5SERIALCMD_OK: + default: + // TODO Keep this data lastCSQ + if (lastVal < sigQualMin) { + if (sigQualRetry < IRIDIUM_RETRY_MAX) { + sigQualRetry++; + wii5Display.atCommsSend(F("info"), F("signalquality=%d,retry=%d"), int(lastVal), int(sigQualRetry)); + // Setting sendCount to zero, will trigger sending Line 7 again + // (not necessary now using signal quality wait) sendCount = 0; + step = WII5IRIDIUM_SIGNALQUALITY_WAIT; stepWait = 0; + } + else { + wii5Display.atCommsSend(F("info"), F("signalquality=%d,retry max"), int(lastVal)); + lastSince = 0; + lastSignalQuality = int(lastVal); + step = WII5IRIDIUM_SEND_JUMP; stepWait = 0; + } + } + else { + lastSince = 0; + lastSignalQuality = lastVal; + wii5Display.atCommsSend(F("info"), F("signalquality=%d"), int(lastSignalQuality)); + step = WII5IRIDIUM_SEND_JUMP; stepWait = 0; + } + } + } + else if (stepWait > 120000) { + lastSince = 0; + lastSignalQuality = 0; + wii5Display.atCommsSend(F("info"), F("signalquality=0,timeout")); + step = WII5IRIDIUM_SEND_JUMP; stepWait = 0; + } + break; + + // Wait here for a retry. + case WII5IRIDIUM_RETRY: + if (first) { + if (debug) + console.log(LOG_ERROR, F("Irigin: Waiting n seconds before retry send")); + } + // 15 second delay to retry Signal Quality + else if (stepWait > 20000) { + step = WII5IRIDIUM_SEND_JUMP; stepWait = 0; + } + break; + + // Choose - send Text or Binary - always returns data + case WII5IRIDIUM_SEND_JUMP: + initialized = true; + if (currentType == WII5IRIDIUM_REQUEST_SIGNALQUALITY) { + // TODO - should be done + step = WII5IRIDIUM_WAITING; stepWait = 0; + } + else if (currentType == WII5IRIDIUM_REQUEST_TIME) { + // TODO - should be done + step = WII5IRIDIUM_WAITING; stepWait = 0; + } + else if (currentType == WII5IRIDIUM_REQUEST_FIRMWARE) { + // TODO - should be done + step = WII5IRIDIUM_WAITING; stepWait = 0; + } + else if (currentType == WII5IRIDIUM_REQUEST_SENDTEXT) { + step = WII5IRIDIUM_PREPARE_TEXT; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step send_jump->prepare_text")); + } + else if (currentType == WII5IRIDIUM_REQUEST_SENDBIN) { + step = WII5IRIDIUM_PREPARE_BIN1; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step send_jump->prepare_bin1")); + } + else { + step = WII5IRIDIUM_OFF; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step send_jump->off")); + } + break; + + // Send Text + case WII5IRIDIUM_PREPARE_TEXT: + if (first) { + if (debug) console.log(LOG_DEBUG, F("Iridium: Setting PrepareText Sentence")); + wii5Display.atCommsSend(F("send"), F("prepare=text,length=%d"), int(strlen(wii5BinaryIridium))); + sendCount = 0; + } + else if (stepWait > 500) { + + // + // AT+SBDWT=hello - Text send, maxium length = 120 bytes + // + + if (sendCount == 0) { + // strncpy_P(buffer ,(PGM_P) F("AT+SBDWT=My first nonBlocking Iridium attempt"), bufMax); + // TODO will this be big enough + sprintf_P(buffer, PSTR("AT+SBDWT=%s"), wii5BinaryIridium); + setTimeout(15000); + } + switch (sendAndWait()) { + // Still waiting + case WII5SERIALCMD_WAITING: + break; + + // Move on + case WII5SERIALCMD_TIMEOUT: + console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (AT+SBDWT=X): %s"), buffer); + case WII5SERIALCMD_ERROR: + console.log(LOG_ERROR, F("SERIAL: ERROR sending (AT+SBDWT=X): %s"), buffer); + case WII5SERIALCMD_OK: + default: + if (debug) + console.log(LOG_DEBUG, F("SERIAL: OK setting up (AT+SBDWT=X)")); + step = WII5IRIDIUM_SEND; stepWait = 0; + if (debug) console.log(LOG_DEBUG, F("step prepare_text->send_text")); + break; + }; + } + break; + + case WII5IRIDIUM_RECEIVE: + if (first) { + if (debug) console.log(LOG_DEBUG, F("Iridium: Receiving Data")); + sendCount = 0; + // Use the shared Binary buffer for incoming now + memset(wii5BinaryIridium, 0, 340); + binBuffer = wii5BinaryIridium; + processMode = WII5SERIALPARSER_BINARY1; + binBufferExpect = 0; // Important - must set to 0 + // SEND THIS - AT+SBDRB + strncpy_P(buffer ,(PGM_P) F("AT+SBDRB"), bufMax); + stream->print(buffer); + sendNewLine(); + wii5Display.atCommsSend(F("receive"), F("start")); + } + else if (stepWait > 500) { + if (processMode != WII5SERIALPARSER_BINARY1) { + // OK So we have a message ! + // Put it somewhere + + // Swith process Mode back and move on + // - We can send here, no delay, then switch to command wait + // - Command wait will be looking for reply in Command TODO + // - or Timeout + // - At which point it will send a reply, and receive next command + // - On receiveing command can be go three ways. + // . Didn't understand the command - ignore / log locally + // . Understood - got a reply, and send a replly + // . Undertsood but timeout... send reply timeout + lastReceive = WII5IRIDIUMRECEIVERESULT_OK; + lastReceiveLen = binBufferExpect; + lastReceiveBuff = wii5BinaryIridium[0]; // First 2 are length; + wii5BinaryIridium[lastReceiveLen] = '\0'; + // TODO Should we strip last 2 to zero? + + // TODO - What if the queue says there is more to receive. Maybe just wait + // since we are alwqys sending lots, and receiving little ? + processMode = WII5SERIALPARSER_OKHUH; + wii5Display.atCommsSend(F("receive"), F("complete,size=%d"), lastReceiveLen); + step = WII5IRIDIUM_WAITING; stepWait = 0; + } + } + else if (stepWait > 5000) { + wii5Display.atCommsSend(F("receive"), F("timeout")); + // TODO Timeout... Force back clear etc (how to clear buffers etc + // TODO Show errors + processMode = WII5SERIALPARSER_OKHUH; + step = WII5IRIDIUM_WAITING; stepWait = 0; + } + break; + + case WII5IRIDIUM_SEND: + if (first) { + if (debug) console.log(LOG_DEBUG, F("Iridium: Setting SendToSat Sentence")); + sendCount = 0; + } + else if (stepWait > 500) { + if (sendCount == 0) { + strncpy_P(buffer ,(PGM_P) F("AT+SBDI"), bufMax); + setTimeout(45000); + } + switch (sendAndWait(WII5SERIALLAST_ATSBDI)) { + // Still waiting + case WII5SERIALCMD_WAITING: + break; + + // Move on + case WII5SERIALCMD_TIMEOUT: + console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (AT+SBDI): %s"), buffer); + case WII5SERIALCMD_ERROR: + console.log(LOG_ERROR, F("SERIAL: ERROR sending (AT+SBDI): %s"), buffer); + wii5Display.atCommsSend(F("send"), F("failed,during +SBDI")); + lastSend = WII5IRIDIUMSENDRESULT_FAILED_OTHER; + step = WII5IRIDIUM_WAITING; stepWait = 0; + currentType = WII5IRIDIUM_REQUEST_NONE; + break; + + case WII5SERIALCMD_OK: + default: + // TODO CHeck for return value of +SBDI or +SBDIX incase we need to retry + wii5Display.atCommsSend(F("send"), F("success,mo_status=%lu,mo_MSN=%lu,mt_status=%lu,mt_MSN=%lu,mt_len=%lu,mt_queued=%lu"), + recordVals[0], recordVals[1], + recordVals[2], recordVals[3], recordVals[4], recordVals[5] + ); + lastMOMSN = recordVals[1]; + lastSince = 0; + + switch(recordVals[0]) { + + // Success fully sent or nothing to send + case 0: + case 1: + // LOG SUCCESS !!! + console.log(LOG_INFO, F("Iridium: SEND: Success")); + lastSend = WII5IRIDIUMSENDRESULT_OK; + currentType = WII5IRIDIUM_REQUEST_NONE; + + // Do we have a waiting message, download it off the Iridium + // 2 = Number of messages, 4 = Length of this one + if ( (recordVals[2] > 0) && (recordVals[4] > 0) ) { + wii5Display.atCommsSend(F("receive"), F("mt_len=%lu"), + recordVals[4] + ); + step = WII5IRIDIUM_RECEIVE; stepWait = 0; + if (debug) console.log(LOG_DEBUG, F("step send_text->receive`")); + } + + // No waiting messages. + else { + step = WII5IRIDIUM_WAITING; stepWait = 0; + // TODO Include Iridium message ID etc etc + wii5Display.atCommsSend(F("send"), F("complete")); + // TODO Capture etc + if (debug) console.log(LOG_DEBUG, F("step send_text->waiting`")); + } + break; + + // Other error + case 2: + default: + // Retry + retry++; + if (retry < IRIDIUM_RETRY_MAX) { + // LOG RETRY !!! + wii5Display.atCommsSend(F("send"), F("failed,retry=%d"), int(retry)); + step = WII5IRIDIUM_RETRY; stepWait = 0; + } + else { + // LOG FAILED !!! + wii5Display.atCommsSend(F("send"), F("failed,exit")); + lastSend = WII5IRIDIUMSENDRESULT_FAILED_OTHER; + step = WII5IRIDIUM_WAITING; stepWait = 0; + currentType = WII5IRIDIUM_REQUEST_NONE; + } + break; + }; + + // TODO - check for receive stuff + break; + }; + } + break; + + // Send Binary + case WII5IRIDIUM_PREPARE_BIN1: + // + // +SBDWB= + // + // {binary SBD message} + {2-byte checksum} + // + // The checksum is the least signi cant 2-bytes of the summation of the entire SBD message. The high order byte will be sent rst. For example if the 9602 were to send the word “hello” encoded in ASCII to the DTE the binary stream would be hex 00 05 68 65 6c 6c 6f 02 14. + // + // Response: + // 0 SBD message successfully written to the 9602. + // 1 SBD message write timeout. An insu cient number of bytes were transferred to 9602 during the transfer period of 60 seconds. + // 2 SBD message checksum sent from DTE does not match the checksum calculated by the 9602. + // 3 SBD message size is not correct. The maximum mobile originated SBD message length is 340 bytes. The minimum mobile originated SBD message length is 1 byte. + // + if (first) { + if (debug) console.log(LOG_DEBUG, F("Iridium: Setting PrepareBin Sentence")); + wii5Display.atCommsSend(F("send"), F("prepare=binary,length=%d"), int(binSize)); + sendCount = 0; + } + else if (stepWait > 500) { + + // + // AT+SBDWB=bytes + // + + if (sendCount == 0) { + sprintf_P(buffer, PSTR("AT+SBDWB=%d"), int(binSize)); + setTimeout(15000); + } + switch (sendAndWait(WII5SERIALLAST_READY)) { + case WII5SERIALCMD_WAITING: + break; + case WII5SERIALCMD_TIMEOUT: + console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending (AT+SBDWB=X): %s"), buffer); + case WII5SERIALCMD_ERROR: + console.log(LOG_ERROR, F("SERIAL: ERROR sending (AT+SBDWB=X): %s"), buffer); + case WII5SERIALCMD_OK: + default: + if (debug) + console.log(LOG_INFO, F("SERIAL: OK setting up (AT+SBDWB=X)")); + step = WII5IRIDIUM_PREPARE_BIN2; stepWait = 0; + if (debug) console.log(LOG_DEBUG, F("step prepare_text->prepare_bin2")); + break; + }; + } + break; + + case WII5IRIDIUM_PREPARE_BIN2: + if (first) { + // Logging? + } + else if (stepWait > 500) { + checksum = 0; + if (passthrough) + console.printBinary(wii5BinaryIridium, binSize); + for (loopcount = 0; loopcount < binSize; loopcount++) { + stream->write(wii5BinaryIridium[loopcount]); + checksum += (uint8_t)wii5BinaryIridium[loopcount]; + } + stream->write(checksum >> 8); + stream->write(checksum & 0xFF); + + // TODO how to find Status + // 0 success + // 1 TIMEOUT + // 2 checksum errors + // 3 size errors + + step = WII5IRIDIUM_SEND; stepWait = 0; + } + break; + + // Complete + case WII5IRIDIUM_STOP_PREPARE: + break; + + case WII5IRIDIUM_END: + break; + + // Wait here... just in case Maths wants to do some more. Give them 1 min + case WII5IRIDIUM_WAITING: + // TODO Configure time etc (should we send reminder?) + if (first) { + wii5Display.atCommsSend(F("status"), F("waiting")); + } + if (stepWait > 60000) { + step = WII5IRIDIUM_OFF; stepWait = 0; + currentType = WII5IRIDIUM_REQUEST_NONE; + if (debug) console.log(LOG_DEBUG, F("step waiting->off`")); + } + break; + + default: + console.log(LOG_FATAL, F("IRIDIUM: Default step... step=%d"), step); + step = WII5IRIDIUM_OFF; + break; + } + return; +} + +// Manually set the binary data - note, we have no buffer, so must have external for hhis +void WII5Iridium::setBinary(uint8_t *buf, uint16_t size) { + // Memcopy data size or max iridium size + binSize = size <= WII5_IRIDIUM_BIN_MAX ? size : WII5_IRIDIUM_BIN_MAX; + memcpy(wii5BinaryIridium, buf, binSize); +} + +void WII5Iridium::setBinSize(uint16_t size) { + binSize = size <= WII5_IRIDIUM_BIN_MAX ? size : WII5_IRIDIUM_BIN_MAX; +} + +void WII5Iridium::setText(char *str) { + strncpy(wii5BinaryIridium, *(char *)str, WII5_IRIDIUM_BIN_MAX); // WII5_STORAGE_FILENAME_LEN); +} +// Allow fprintf params like console etc. +void WII5Iridium::setText(const __FlashStringHelper *str) { + strncpy_P(wii5BinaryIridium, (const char *)str, WII5_IRIDIUM_BIN_MAX); +} + +// Get current +WII5IRIDIUM_REQUEST WII5Iridium::currentRequest() { + return currentType; +} + +bool WII5Iridium::requestSendText() { + // If no text, faile - return false, + currentType = WII5IRIDIUM_REQUEST_SENDTEXT; + recordCount = 0; sendCount = 0; stepWait = 0; + lastSend = WII5IRIDIUMSENDRESULT_NONE; + lastReceive = WII5IRIDIUMRECEIVERESULT_NONE; + retry = 0; + sigQualRetry = 0; + if (initialized) + step = WII5IRIDIUM_SEND_JUMP; + else + step = WII5IRIDIUM_POWER; + return true; +} + +bool WII5Iridium::requestSendBinary() { + // If we have binary data.... else return false + currentType = WII5IRIDIUM_REQUEST_SENDBIN; + lastSend = WII5IRIDIUMSENDRESULT_NONE; + lastReceive = WII5IRIDIUMRECEIVERESULT_NONE; + recordCount = 0; sendCount = 0; stepWait = 0; + retry = 0; + sigQualRetry = 0; + if (initialized) + step = WII5IRIDIUM_SEND_JUMP; + else + step = WII5IRIDIUM_POWER; + return true; +} + +bool WII5Iridium::requestFirmware() { + currentType = WII5IRIDIUM_REQUEST_FIRMWARE; + lastSend = WII5IRIDIUMSENDRESULT_NONE; + lastReceive = WII5IRIDIUMRECEIVERESULT_NONE; + recordCount = 0; sendCount = 0; stepWait = 0; + step = WII5IRIDIUM_POWER; + retry = 0; + sigQualRetry = 0; +} + + +// Waiting - Feel free to check the last status and send another +bool WII5Iridium::waiting() { + return ((step == WII5IRIDIUM_OFF) || (step == WII5IRIDIUM_WAITING)); +} + +/* +void WII5Iridium::clearLast() { + +} +*/ + +void WII5Iridium::programLine(uint16_t l) { + // Main Programming + switch (l) { + // BOOT=0..3, + case 0: + strncpy_P(buffer ,(PGM_P) F("ATE0"), bufMax); + break; + case 1: + strncpy_P(buffer ,(PGM_P) F("AT&D0"), bufMax); + break; + case 2: + strncpy_P(buffer ,(PGM_P) F("AT&K0"), bufMax); + break; + case 3: + strncpy_P(buffer ,(PGM_P) F("AT+SBDMTA=0"), bufMax); + break; + + // Firmware + case 4: + strncpy_P(buffer ,(PGM_P) F("AT+CGMR"), bufMax); + break; + + // IMEI + case 5: + strncpy_P(buffer ,(PGM_P) F("AT+CGSN"), bufMax); + break; + + // System Time + case 6: + strncpy_P(buffer ,(PGM_P) F("AT-MSSTM"), bufMax); + break; + + // Signal Quality + case 7: + strncpy_P(buffer ,(PGM_P) F("AT+CSQ"), bufMax); + break; + + case 8: + strncpy_P(buffer ,(PGM_P) F("AT"), bufMax); + break; + + // send Text + + // send Binary + + // HOW TO: Retries + + // HOW TO: Work around firmware issues + + default: + console.log(LOG_FATAL, F("IRIDIUM: request invalid programming string")); + buffer[0] = '\0'; + break; + } + + if (debug) + console.log(LOG_DEBUG, F("IRIDIUM: set command=%d buffer=%s"), l, buffer); +} + +void WII5Iridium::sendNewLine() { + stream->print(F("\r")); +} + +WII5Iridium wii5Iridium; + +#endif diff --git a/WII5Iridium.h b/WII5Iridium.h new file mode 100644 index 0000000..1ada8b8 --- /dev/null +++ b/WII5Iridium.h @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Iridium.h + * @brief Iridium 9602/9603 SBD modem driver: AT-command state machine. + */ + +#ifndef WII5Iridium_h +#define WII5Iridium_h + + +#include +#include +#include +#ifdef WII5_COMMS_IRIDIUM + +/* + How to make request ? + + How to know when complete? + + +void loop() { + wii5Iridium.loop(); + + if (wii5Iridium.lastStatus) { + // Check the Status of the last thing we sent + } + + if (wii5Iridium.lastReceive) { + // TODO + } + +} + + +DATA STRUCTURE TO Send + + * Wrap WII5 Binary Packet in Iridium specific data + * Time it took to send (updated just before send each time) + * Number of retries + * Last Signal quality + + +Useful References: + + https://docs.rockblock.rock7.com/reference#sbdix + + +*/ + + + +enum WII5IRIDIUM_REQUEST { + WII5IRIDIUM_REQUEST_NONE, + WII5IRIDIUM_REQUEST_FIRMWARE, + WII5IRIDIUM_REQUEST_TIME, + WII5IRIDIUM_REQUEST_SIGNALQUALITY, + WII5IRIDIUM_REQUEST_SENDTEXT, + WII5IRIDIUM_REQUEST_SENDBIN, +}; + +enum WII5IRIDIUM_STATUS { + WII5IRIDIUM_STATUS_READY, // Ready to send, no errors + WII5IRIDIUM_STATUS_BUSY, // Busy, still sending, need to wait + WII5IRIDIUM_STATUS_FAILED // Reason for failure? +}; + +// TODO request - need to know what the input is and how to stop... + +enum WII5IRIDIUM_STEPS { + // Just off. + WII5IRIDIUM_OFF, // Turn it off - power and all + WII5IRIDIUM_POWER, // Turn on power and wait 500ms + WII5IRIDIUM_ALIVE, // Send "AT" until get OK or timeout + WII5IRIDIUM_BOOT, // ATE1, AT&D0, AT&K0, AT+SBDMTA=0 + + // Firmware + WII5IRIDIUM_FIRMWARE,// AT+CGMR + + // IMEI + WII5IRIDIUM_IMEI, // AT-MSSTM + + // System Time + WII5IRIDIUM_SYSTERMTIME, // AT-MSSTM + + // Signal Quality + WII5IRIDIUM_SIGNALQUALITY_WAIT, + WII5IRIDIUM_SIGNALQUALITY, // AT+CSQ + + // Choose - send Text or Binary - always returns data + WII5IRIDIUM_RETRY, // Wait here a bit before rety + WII5IRIDIUM_SEND_JUMP, // Are we TEXT or BINARY + + // Send Text + WII5IRIDIUM_PREPARE_TEXT, // AT+SBDWT=RockBlocks1 + WII5IRIDIUM_SEND, // AT+SBDI ... +SBDI: 1, 3, 0, 0, 0, 0 + + // Send Binary + WII5IRIDIUM_PREPARE_BIN1, // AT+SBDWB={n} (size of data) + WII5IRIDIUM_PREPARE_BIN2, // AT+SBDWB={n} (size of data) + + // Receive Message + WII5IRIDIUM_RECEIVE, // AT+SBDRB ... AT+SBDRB (echo back) + WII5IRIDIUM_RECEIVE_BIN, // first 2 bytes = size, then binary, then checksum + + WII5IRIDIUM_WAITING, // Waiting - hurry up and absorb + + // Complete + WII5IRIDIUM_STOP_PREPARE, // END ! Jump to STOP_PREPARE, + WII5IRIDIUM_END // Very end +}; +#define DEFAULT_MINUTES 5 + +#define IRIDIUM_RETRY_MIN 2 // Used for low power mode, how many times to rety minimum +#define IRIDIUM_RETRY_MAX 20 // Maximum number of retries for normal modes + +// FROM IridiumSBD by Rock Blocks +#define ISBD_LIBRARY_REVISION 2 +#define ISBD_DEFAULT_AT_TIMEOUT 30 +#define ISBD_MSSTM_RETRY_INTERVAL 10 +#define ISBD_DEFAULT_SBDIX_INTERVAL 10 +#define ISBD_USB_SBDIX_INTERVAL 30 +#define ISBD_DEFAULT_SENDRECEIVE_TIME 300 +#define ISBD_STARTUP_MAX_TIME 240 +#define ISBD_MAX_MESSAGE_LENGTH 340 +#define ISBD_MSSTM_WORKAROUND_FW_VER 13001 + +#define ISBD_SUCCESS 0 +#define ISBD_ALREADY_AWAKE 1 +#define ISBD_SERIAL_FAILURE 2 +#define ISBD_PROTOCOL_ERROR 3 +#define ISBD_CANCELLED 4 +#define ISBD_NO_MODEM_DETECTED 5 +#define ISBD_SBDIX_FATAL_ERROR 6 +#define ISBD_SENDRECEIVE_TIMEOUT 7 +#define ISBD_RX_OVERFLOW 8 +#define ISBD_REENTRANT 9 +#define ISBD_IS_ASLEEP 10 +#define ISBD_NO_SLEEP_PIN 11 +#define ISBD_NO_NETWORK 12 +#define ISBD_MSG_TOO_LONG 13 + +enum WII5IRIDIUM_RETURNS { + WII5IRIDIUM_RETURN_AT, + WII5IRIDIUM_RETURN_OK, + WII5IRIDIUM_RETURN_READY, + WII5IRIDIUM_RETURN_ERROR, + WII5IRIDIUM_RETURN_SBDI, // +SBDI, +SBDIX, +SBDIXA (moCode, &moMSN, &mtCode, &mtMSN, &mtLen, &mtRemaining) + WII5IRIDIUM_RETURN_CSQ, // +CSQ:{n} + WII5IRIDIUM_RETURN_MSSTM, // -MSSTM + WII5IRIDIUM_RETURN_BINARY // Hard coded the binary +}; + +/** + * @brief Driver for the Iridium 9602/9603 SBD modem. + * + * AT-command state machine: power on, wait for the modem to come alive, + * boot up (ATE1, AT&D0, AT&K0, AT+SBDMTA=0), then drive a sequence of + * +CSQ / +SBDWB / +SBDIX / +SBDRB cycles to send and receive 340-byte + * SBD messages. State is in WII5IRIDIUM_STEPS; see step transitions in + * WII5Iridium.cpp::loop(). + */ +class WII5Iridium : public WII5Power, public WII5SerialManager{ + public: + WII5Iridium() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;} + virtual WII5_DRIVERS driverId() {return WII5DRIVER_COMMS_IRIDIUM;} + + /** @brief One-time bring-up. */ + void begin(); + /** @brief State-machine tick: parses serial, advances steps. */ + void loop(); + + /** @brief Queue a firmware-version query (AT+CGMR). */ + bool requestFirmware(); + /** @brief Queue a binary send (uses buffer set by setBinary/setBinSize). */ + bool requestSendBinary(); + /** @brief Queue a text send (uses buffer set by setText). */ + bool requestSendText(); + /** @brief Currently-queued request type. */ + WII5IRIDIUM_REQUEST currentRequest(); + /** @brief Set the binary buffer + length for the next send. */ + void setBinary(uint8_t* buf, uint16_t size); + /** @brief Set just the size of the binary buffer (caller writes the bytes). */ + void setBinSize(uint16_t size); + /** @brief Set the text payload for the next send (RAM string). */ + void setText(char* str); + /** @brief Set the text payload for the next send (PROGMEM string). */ + void setText(const __FlashStringHelper *str); + + /** @brief Begin a comms cycle (`force` skips initialized check). */ + virtual void start(bool force = false); + /** @brief Abort the current cycle. */ + void stop(bool force = false); + /** @brief Apply power to the Iridium modem. */ + void powerOn(bool force = false); + /** @brief Cut power to the Iridium modem. */ + void powerOff(bool force = false); + /** @brief Are we waiting on the modem to finish a step? */ + bool waiting(); + + /** @brief Is the state machine currently in a non-idle step? */ + bool isRunning(); + + // TODO Storage? why can't we use a 64 bit (8 byte) number? + char imei[18]; // 14 or up to 16 digits? + // uint32_t ; // 14 or up to 16 digits? + + elapsedMillis lastSince; + uint8_t lastSignalQuality; + uint16_t lastMOMSN; + WII5IRIDIUM_SEND_RESULT lastSend; + WII5IRIDIUM_RECEIVE_RESULT lastReceive; + uint16_t lastReceiveLen; + uint8_t* lastReceiveBuff; + + + virtual void sendNewLine(); + + protected: + bool running; + WII5IRIDIUM_STEPS step; + WII5IRIDIUM_STEPS stepLast; + elapsedMillis stepWait; + + void programLine(uint16_t l); + + bool initialized; + WII5IRIDIUM_REQUEST currentType; + uint8_t retry; // How many times did we retry ? + uint8_t sigQualRetry; // How many times did we retry ? + uint8_t sigQualMin; // How many times did we retry ? + uint32_t binSize; // Size of binary data to send + + uint16_t checksum; + uint16_t loopcount; + uint8_t *txData; + + // Firmware (and age) + // Signal Quality (and age) + // DateTime (and age) + // Send Status (and age) (WAITING, COMPLETE, ERROR) + // Send Queue ? (or external) + // Receive Queue (or external) + + + // lastCSQ + // lastIMEI + // lastSystemTime + // lastID??? + // lastStatus?? + + // Last Status = + +}; + +extern WII5Iridium wii5Iridium; + +#endif +#endif diff --git a/WII5Maths.cpp b/WII5Maths.cpp new file mode 100644 index 0000000..e076c4c --- /dev/null +++ b/WII5Maths.cpp @@ -0,0 +1,588 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Maths.cpp + * @brief Maths CPU controller: power, hold, restart, and SD-card hand-off. + */ + +/* + +WII5Maths - Manage the power and serial to the Maths Compyter + +TODO Seems Communications or Capture mode or something overrides hold - fix + +Who is in charge ? + * This applies to Pi vs AVR - they can both reset each other, + who gets to say - nah you are being turned off, like it or + not. + * Also applies to within the Pi. Who decides to keep it turned + on, and if it is turned on, who decides when to turn off + and even more importantly - where is that triggered? + since we don't rely on the daemon. + * And - if no daemon - how do we receive commands + +Scenario 1: Regular, Power on Pi to process SD Card + * Power up, wii5auto runs as per normal + * End of that wii5auto sends a "serial" command + to tell the AVR that it will be shutting down soon. + * End of that timeout, Pi shutsdown, one final serial + before self shutdown/power off (time how long it takes + before power is off) + * Q: + - How to run something, indipendent of the Daemon + (another listener, or proxy through server) + (complicated) + +Scenario 2: Power Up boot without reason + @WII5,maths,booted + @WII5,maths!,task=mainreboot + +Scenario 3: Power Up boot where Pi did the reboot/firmware etc + Pi won't know it to send a booted command, hello etc still + But AVR can send + @WII5,maths,ask <- Ask what you want instead of telling + @WII5,maths!,thanks <- lol + Could just, on getting ask, decide to ignore (Beause in + on mode for more minutes yet) or go straight to shutdown + + +COMMAND Utility: + * Write a command utitily, for senging @ based commands + to the wave buoy. In each case, the cli does not exit, + until it gets a reply (or timeout) + * Utility should do serial direct, but could potentially + use the API to send and recevive commands, falling back + to serial direct, when the daemon is non-responsive. + * Boot scenraio: + - Send a "why am I awake" - @WII5,maths,booted + Reply with @WII5,maths!,task=on,min=15 + + +Boot Sequence Issues: + * AVR boot - even if WDT or on prupse at the end of sleep/Testing + * Turns on the Pi sets it to ask and waits a long time. + * LOT OF TIME and power. + +setUntil - Not currently used, but allows a number to be set way off info +the future - not ideal though, because it could therefore be always on, +and dates change. + +setHold - is simple and perfect reply to shut down. + +*/ + +#include +#include +#include +#include + +void WII5Maths::begin() { + retry = 0; + firstboot = false; + holdFor = 0; + mathsMode = WII5MATHSMODE_UNKNOWN; + mathsUntil = 0; + // NOTE: This is a two pin version - check it exists + #ifdef POWER_MATHS_PIN_OFF + powerSetPin(POWER_MATHS_PIN, POWER_MATHS_ON, POWER_MATHS_PIN_OFF); + #else + #ifdef POWER_MATHS_PIN + powerSetPin(POWER_MATHS_PIN, POWER_MATHS_ON); + #endif + #endif + + // Make sure off at boot to allow capacitor time + // #ifdef POWER_MATHS_PIN + // WII5Power::powerOff(force); + // #endif + + #ifdef MATHS_RETURNLINE + // TODO 2024 - Using this to tell Maths to stay on !!! + pinMode(MATHS_RETURNLINE, OUTPUT); + digitalWrite(MATHS_RETURNLINE, LOW); + #endif + + // NOTE: Do not set the off or state - wait until we work out what mode this is in + // e.g. Maths may be already on and require to stay on + step = WII5MATHS_ASK; + stepLast = WII5MATHS_UNKNOWN; + stepWait = 0; +}; + +void WII5Maths::setReturnLine() { + pinMode(MATHS_RETURNLINE, OUTPUT); + digitalWrite(MATHS_RETURNLINE, HIGH); + returnlineWait = 0; +} + +void WII5Maths::cancelReturnLine() { + pinMode(MATHS_RETURNLINE, OUTPUT); + digitalWrite(MATHS_RETURNLINE, LOW); +} + +void WII5Maths::gpioClick() { + // elapsedGpio = 0; +} + +// Already on - we don't want to boot up.... assume it is on. Lets assume ASK +void WII5Maths::woops_on() { + // Over simplified - only when off. Race condition here from WARN to OFF, but safe + switch(step) { + case WII5MATHS_OFF: + wii5Display.statusSend(F("Maths"), F("woopson")); + console.log(LOG_DEBUG, F("Maths woops_on - got a hello or similar, switch to ready")); + step = WII5MATHS_READY; stepWait = 0; + } +} + +bool WII5Maths::isMode(WII5_MATHSMODE mm) { + return (mathsMode == mm); +} + +void WII5Maths::start(WII5_MATHSMODE mm) { + mathsMode = mm; + console.log(LOG_DEBUG, F("Maths: start, step=%d off status=%d"), step, WII5MATHS_OFF); + switch(step) { + case WII5MATHS_OFF: + case WII5MATHS_WARN: + case WII5MATHS_ASK: + case WII5MATHS_HOLD_ON: + wii5Display.statusSend(F("Maths"), F("ModeSwitch=boot")); + console.log(LOG_DEBUG, F("Maths: Changing to BOOT, step=%d off status=%d"), step, WII5MATHS_OFF); + step = WII5MATHS_BOOT; stepWait = 0; + retry = 0; + } +} +void WII5Maths::stop(bool force) { + if (step != WII5MATHS_OFF) { + // Delays - request off, that sort of thing... ! YES + console.log(LOG_INFO, F("Maths: stop called, hold was=%lu, now=0"), remainingHold()); + holdFor = 0; + step = WII5MATHS_WARN; stepWait = 0; + } +} + +bool WII5Maths::isOff() { + return (step == WII5MATHS_OFF); +} +bool WII5Maths::isRunning() { + return (step != WII5MATHS_OFF); +} + +void WII5Maths::setDebug(bool in) { + debug = in; +} +bool WII5Maths::getDebug() { + return debug; +} + +// NOTE: Turn on/off shared - even when not necessary, when Maths is on +void WII5Maths::powerOn(bool force) { + // Turn on shared here always, but at boot, turn on shared and delay for capacitor charge + wii5Controller.shared5On(); + WII5Power::powerOn(force); +} + +// Block powerOff maybe +void WII5Maths::powerOff(bool force) { + // Leaves it i the mode, e.g. OFF, but set powerOn + if ((mathsUntil == 0) || (mathsUntil < now())) { + wii5Display.statusSend(F("Maths"), F("power=off,DISABLED")); + mathsUntil = 0; + + wii5Display.atMathsSend(F("shutdown"), F("actual,power off")); + #ifdef POWER_MATHS_PIN + WII5Power::powerOff(force); + #endif + wii5Controller.shared5Off(); + } +} + +// Set hold time and switch to HOLD mode +// - allowLower is false by default, so it won't change unless bigger +void WII5Maths::setHold(uint32_t in, bool allowLower) { + // TODO - check Maths CPU is on. Reset hold count. Set how long for, and tell it to HOLD + if ((in == 0) || allowLower || (remainingHold() < in)) { + holdWait = 0; + holdFor = in; // Seconds, not milliseconds + } + + // if state is maths off - fix it + switch(step) { + case WII5MATHS_OFF: + case WII5MATHS_WARN: + case WII5MATHS_ASK: + step = WII5MATHS_HOLD_ON; stepWait = 0; + break; + } + // Always make sure power is on. + powerOn(true); +} + +void WII5Maths::cancelHold() { + if (holdFor > 0) { + console.log(LOG_INFO, F("Maths: cancelHold called, hold was=%lu, now=0"), remainingHold()); + holdFor = 0; + powerOff(); + step = WII5MATHS_OFF; stepWait = 0; + } +} + +// SUPER simple send Queue +void WII5Maths::sendQueue() { + // TODO how to know that it has been done... lets just try three times. + if (queueCount > 0) { + console.log(LOG_FATAL, F("MathsQeuue: SENDING %s. Retry=%d"), queueMessage[queueCount - 1], queueRetry); + console.printf(F("@Maths,%s\n"), queueMessage[queueCount - 1]); + queueRetry++; + + if (queueRetry > 3) { + queueDone(); + } + } +} + +// Mark first as done +void WII5Maths::queueDone() { + queueRetry = 0; + if (queueCount > 1) { + // Copy the message 1 to 0 + strncpy(queueMessage[0], queueMessage[1], MATHS_QUEUE_BUFFER); + queueCount = 1; + console.log(LOG_FATAL, F("MathsQueue: Second queued messaged moved to first")); + } + else { + queueCount = 0; + console.log(LOG_FATAL, F("MathsQueue: All messages done")); + } +} + +bool WII5Maths::queueCommand(char *val, uint8_t valSize) { + // Not accessed queue in over 15 hours - lets clear it + if (queueLast > 54000000) { + if (queueCount != 0) { + console.log(LOG_INFO, F("NOTE: Maths queue was cleared - not used in 15 hours")); + queueCount = 0; + } + } + queueLast = 0; + if (queueCount == 0) + queueRetry = 0; + if (queueCount >= MATHS_QUEUE_MAX) { + console.log(LOG_INFO, F("Maximum queued messages still waiting")); + return false; + } + + strncpy(queueMessage[queueCount], val, valSize < MATHS_QUEUE_BUFFER ? valSize : MATHS_QUEUE_BUFFER); + queueMessage[queueCount][MATHS_QUEUE_BUFFER - 1] = '\0'; + console.log(LOG_INFO, F("MathsQueue: Added new request = @Maths,%s"), queueMessage[queueCount]); + queueCount++; + + // Turn on and hold to do the above + setHold(900); + + return true; +} + +// Manual Hold Processing +// - Check if Maths is still on - if off, close off hold and exit +// - Manually check if we are still within hold period. +// - If inside - just wait longer +// - If outside - shut down Maths +// . We assume this is only called during non normal maths loop modes. +bool WII5Maths::processHold() { + // First time booted, we ASK the maths cpu - this should not last more than a minute + if (step == WII5MATHS_ASK) { + return true; + } + + // Not actually running + if (holdFor == 0) { + return false; + } + + // TODO console check for Maths held longer, or power OFF + if (holdWait > (1000 * holdFor)) { + cancelHold(); + return false; + } + + return true; +} + +uint32_t WII5Maths::remainingHold() { + if (holdFor > MATHS_MAXHOLD) holdFor = MATHS_MAXHOLD; + // NOTE: Safety, if checking remaining then break out if error + if (holdWait > (MATHS_MAXHOLD * 1000)) { + console.log(LOG_INFO, F("Maths: error holdWait too long = %lu, holdFor=%lu"), holdWait, holdFor); + holdFor = 0; + holdWait = 0; + return 0; + } + return holdFor > 0 ? (holdFor - (holdWait / 1000)) : 0; +} + +uint32_t WII5Maths::remainingUntil() { + return mathsUntil > now() ? (mathsUntil - now()) : 0; +} + +void WII5Maths::loop() { + // Every hour cancel return line + if ((returnlineWait/1000) > MATHS_RETURNHOLD) { + cancelReturnLine(); + returnlineWait = 0; + } + + bool first = (stepLast != step); + stepLast = step; + + switch (step) { + // ASK: This mode will ask the Maths CPU what state to be in + // - Timeout/noanswer will move on to OFF + // exit = hello response or timeout + case WII5MATHS_ASK: + // Setup as if in HOLD mode, but DO NOT turn on the power (this allows reboots without Pi) + // TODO Mayube we want it + holdWait = 0; + if (holdFor < 900) + holdFor = 900; + step = WII5MATHS_HOLD_ON; stepWait = 0; + break; + + // WARN: - Show Maths that it is about to be shut down then wait... + case WII5MATHS_WARN: + if (first) { + if (remainingHold() > 0) { + step = WII5MATHS_HOLD_ON; stepWait = 0; + } + else { + wii5Display.statusSend(F("Maths"), F("mode=warn,power off,10s")); + wii5Display.atMathsSend(F("shutdown"), F("now,15 seconds")); + stepWait = 0; + } + } + // TODO - consider sending updates every 5 seconds + else if (stepWait > 10000) { + // Direct to OFF - beacuse this is WARN + step = WII5MATHS_OFF; stepWait = 0; + } + break; + + // OFF: Sends a message going off but then powers off ! + // exit = NA - Stays here forever, until outside source changes step + case WII5MATHS_OFF: + if (first) { + wii5Display.atMathsSend(F("shutdown"), F("now,power off")); + wii5Display.statusSend(F("Maths"), F("mode=off")); + console.log(LOG_DEBUG, F("MathsLoop. step=OFF first=%d"), first); + powerOff(true); + mathsMode = WII5MATHSMODE_UNKNOWN; + } + + // TODO Normally maths is turned on as needed, but here we can have + // maybe a situation where it hasn't been on for too long? And consider it? + + break; + + // REBOOT: Powers off the device, waits 10 seconds then back to BOOT + // exit = 10 secoonds back to BOOT + case WII5MATHS_REBOOT: + if (first) { + powerOff(true); + wii5Display.atMathsSend(F("reboot"), F("now")); + wii5Display.statusSend(F("Maths"), F("reboot")); + console.log(LOG_DEBUG, F("MathsLoop. step=REBOOT first=%d"), first); + } + // Reboot - turn off for 10 seconds then back to BOOT + if (stepWait > 10000) { + console.log(LOG_DEBUG, F("MATHS: step reboot -> boot")); + step = WII5MATHS_BOOT; stepWait = 0; + } + break; + + // BOOT: Turn the power back on, and jump to BOOT_WAIT + case WII5MATHS_BOOT: + if (first) { + console.log(LOG_INFO, F("MathsLoop. step=BOOT first=%d"), first); + wii5Display.statusSend(F("Maths"), F("boot")); + wii5Controller.shared5On(); + // setStatus(WII5STATUS_WAITING); + } + // TODO 2024 - Look at if this is required or else immediate? + // TODO 200ms for capacitor charge? TODO 10 seconds for now for testing + // else if (stepWait > 10000) + else { + #ifdef POWER_MATHS_PIN + powerOn(); + #endif + console.log(LOG_DEBUG, F("MATHS: step boot -> wait")); + step = WII5MATHS_BOOT_WAIT; stepWait = 0; + } + break; + + // BOOT_WAIT - We twiddlge our thumbs, waiting for the CPu to respond to us + // exit - Hello or similar command from the Maths, or Timeout + // NOTE: On timeout, will probably try reboot, but this is + // where we may get into far more complex code checking for + // limited retries etc + case WII5MATHS_BOOT_WAIT: + if (first) { + console.log(LOG_INFO, F("MathsLoop. step=BOOT_WAIT first=%d"), first); + wii5Display.statusSend(F("Maths"), F("wait")); + displayWait = 30000; + } + // Do we have a recent hello from the Maths + else if ( + wii5Controller.checkLastHello(WII5DEVICE_MATHS, powerElapsed()) + ) { + // SUCCESS - Got a hello from a Maths CPU since we have turned power pin on + console.log(LOG_DEBUG, F("MATHS: step wait -> ready")); + step = WII5MATHS_READY; stepWait = 0; + } + else if (displayWait > 30000) { + // Send Mode + if (mathsMode == WII5MATHSMODE_BUTTON) { + wii5Display.atMathsSend(F("mode"), F("button,no hello")); + } + else if (mathsMode == WII5MATHSMODE_COMMS) { + wii5Display.atMathsSend(F("mode"), F("comms,no hello")); + } + else if (remainingHold() > 0) { + wii5Display.atMathsSend(F("mode"), F("hold,no hello")); + } + else if (mathsMode == WII5MATHSMODE_AUTO) { + wii5Display.atMathsSend(F("mode"), F("auto,no hello")); + } + else { + wii5Display.atMathsSend(F("mode"), F("unknown,no hello")); + } + + sendTime(); + displayWait = 0; + } + else if (stepWait > 120000) { + retry++; + if (retry > 2) { + console.log(LOG_ERROR, F("MATHS: Timeout waiting for boot, switching off (multiple retries)")); + step = WII5MATHS_OFF; stepWait = 0; + } + else { + console.log(LOG_ERROR, F("MATHS: Timeout retry=%d"), retry); + step = WII5MATHS_REBOOT; stepWait = 0; + } + } + break; + + // READY: and waiting patiently for you to tell it what to do. + // - Maths may shut itself down, or send hold commands + case WII5MATHS_READY: + if (first) { + console.log(LOG_DEBUG, F("MathsLoop. step=READY first=%d"), first); + wii5Display.statusSend(F("Maths"), F("ready")); + displayWait = 15000; + } + else if (displayWait > 15000) { + if (mathsMode == WII5MATHSMODE_BUTTON) { + wii5Display.atMathsSend(F("mode"), F("button,waiting")); + } + else if (mathsMode == WII5MATHSMODE_COMMS) { + wii5Display.atMathsSend(F("mode"), F("comms,waiting")); + } + else if (remainingHold() > 0) { + wii5Display.atMathsSend(F("mode"), F("hold,waiting")); + } + else if (mathsMode == WII5MATHSMODE_AUTO) { + wii5Display.atMathsSend(F("mode"), F("auto,waiting")); + } + else { + wii5Display.atMathsSend(F("mode"), F("unknown,waiting")); + } + sendQueue(); + sendTime(); + displayWait = 0; + } + // Wait 15 minutes - this is our worst case waiting - something gone wrong but looks right + else if (stepWait > (900000)) { + console.log(LOG_DEBUG, F("MATHS: step ready -> shutdown_wait")); + step = WII5MATHS_WARN; stepWait = 0; + } + break; + + // HOLD_ON: Keep Maths CPU ON - there needs to be a maximum timeout, and maybe switch to ASK + // e.g. wait 1 hour, then ASK then 1 hour? + case WII5MATHS_HOLD_ON: + if (first) { + wii5Display.atMathsSend(F("mode"), F("hold,remaining=%lu,seconds"), remainingHold()); + console.log(LOG_DEBUG, F("MathsLoop. step=HOLD_ON first=%d, holding for %lu seconds"), first, holdFor); + wii5Display.statusSend(F("maths"), F("holding,%lu,seconds"), remainingHold()); + displayWait = 0; + } + else if (holdWait > (1000 * holdFor)) { + // Have we told them we are shutting down? Maybe that is in the OFF + holdFor = 0; + wii5Display.statusSend(F("maths"), F("holding,end")); + wii5Display.atMathsSend(F("mode"), F("auto")); + step = WII5MATHS_WARN; stepWait = 0; + } + else if (displayWait > 15000) { + wii5Display.atMathsSend(F("mode"), F("hold,remaining=%lu"), remainingHold()); + wii5Display.statusSend(F("maths"), F("holding,%lu,seconds"), remainingHold()); + sendQueue(); + sendTime(); + displayWait = 0; + } + // if (stepWait > (60 * 60 * 1000)) { + // TODO Force OFF !!! + // } + break; + + case WII5MATHS_FINISH: + if (first) + console.log(LOG_INFO, F("MathsLoop. step=FINISH first=%d"), first); + console.log(LOG_DEBUG, F("MATHS: step finish -> off")); + step = WII5MATHS_WARN; stepWait = 0; + break; + + default: + console.log(LOG_FATAL, F("MathsLoop: Default step... step=%d"), step); + step = WII5MATHS_WARN; stepWait = 0; + break; + // TODO Error? + } + return; +} + +void WII5Maths::sendTime() { + if (timeWait < 60000) { + return; + } + timeWait = 0; + if (wii5Gps.isTimeValid()) { + console.log(LOG_DEBUG, F("Sending time - valid from GPS")); + wii5Display.atMathsSend(F("time"), F("%04d,%02d,%02d,%02d,%02d,%02d"), + int(year()), int(month()), int(day()), + int(hour()), int(minute()), int(second()) + ); + } + else { + console.log(LOG_DEBUG, F("Not sending time - not valid from GPS")); + } +} +// Set time to be held on, and force ON if not already +void WII5Maths::setMathsUntil(time_t t) { + if (t > now()) { + mathsUntil = t; + } + else { + mathsUntil = 0; + } +} +time_t WII5Maths::getMathsUntil() { + return mathsUntil; +} + +WII5Maths wii5Maths; diff --git a/WII5Maths.h b/WII5Maths.h new file mode 100644 index 0000000..121d4a6 --- /dev/null +++ b/WII5Maths.h @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Maths.h + * @brief Maths CPU controller: power, hold, restart, and SD-card hand-off. + */ + +#ifndef WII5Maths_h +#define WII5Maths_h + +#include +#include +#include +#include +#include + +enum WII5MATHS_STEPS { + WII5MATHS_UNKNOWN, + WII5MATHS_WARN, + WII5MATHS_OFF, + WII5MATHS_ASK, + WII5MATHS_REBOOT, + WII5MATHS_BOOT, + WII5MATHS_BOOT_WAIT, + WII5MATHS_READY, + WII5MATHS_HOLD_ON, + WII5MATHS_FINISH +}; + +#define MATHS_QUEUE_MAX 2 +#define MATHS_QUEUE_BUFFER 150 + +class WII5Maths : public WII5Power { + public: + WII5Maths() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;} + virtual WII5_DRIVERS driverId() {return WII5DRIVER_MATHS;} + + void setDebug(bool in); + bool getDebug(); + + void begin(); + void loop(); + + void start(WII5_MATHSMODE mm); + void stop(bool force = false); + + void powerOff(bool force = false); + void powerOn(bool force = false); + + // Stats on running etc + time_t lastStartTime; + uint32_t lastRunTime; + + void setMathsUntil(time_t t); + time_t getMathsUntil(); + + bool isRunning(); + bool isOff(); + + void gpioClick(); + + void setHold(uint32_t in, bool allowLower = false); + void woops_on(); + void cancelHold(); + bool processHold(); + void setReturnLine(); + void cancelReturnLine(); + + uint32_t remainingHold(); + uint32_t remainingUntil(); + + bool isMode(WII5_MATHSMODE mm); + + void sendTime(); + void sendQueue(); + bool queueCommand(char *val, uint8_t valSize); + void queueDone(); + + protected: + uint8_t queueCount; + char queueMessage[MATHS_QUEUE_MAX][MATHS_QUEUE_BUFFER]; + elapsedMillis queueLast; + uint8_t queueRetry; + + bool running; + bool debug; + WII5_MATHSMODE mathsMode; + + uint8_t retry; + + bool firstboot; // First booted up - wait, not touching power lines + + time_t mathsUntil; + + // No longer used + // elapsedMillis elapsedHello; + // elapsedMillis elapsedRequest; + // elapsedMillis elapsedGpio; + + elapsedMillis returnlineWait; + elapsedMillis holdWait; + uint32_t holdFor; + + WII5MATHS_STEPS step; + WII5MATHS_STEPS stepLast; + elapsedMillis stepWait; + elapsedMillis displayWait; + elapsedMillis timeWait; + +}; + +extern WII5Maths wii5Maths; + +#endif diff --git a/WII5Mode.cpp b/WII5Mode.cpp new file mode 100644 index 0000000..0e532f4 --- /dev/null +++ b/WII5Mode.cpp @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Mode.cpp + * @brief Base class for run-time operating modes (Capture, Sleep, Position, etc.). + */ + +/* + +NOTE: This is a base class for all modes and not really used + +*/ + +#include +#include +#include + +void WII5Mode::begin() { +} + +void WII5Mode::loop() { +} + +//void WII5Mode::metadataPrint(File* fh) { +//} \ No newline at end of file diff --git a/WII5Mode.h b/WII5Mode.h new file mode 100644 index 0000000..fe152e4 --- /dev/null +++ b/WII5Mode.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Mode.h + * @brief Base class for run-time operating modes (Capture, Sleep, Position, etc.). + */ + +// NOTE: This is a base class for all modes and not really used + +#ifndef WII5Mode_h +#define WII5Mode_h + +#include +#include +#include +#include + +/** + * @brief Base class for run-time operating modes. + * + * Each subclass owns a small state machine that is ticked by + * WII5Controller::loop() while that mode is active. Concrete modes + * include WII5ModeCapture, WII5ModeSleep, WII5ModePosition, + * WII5ModeManualTest, WII5ModeSelfTest, and WII5ModeLowBattery. + */ +class WII5Mode { + public: + /** @brief One-time bring-up. */ + virtual void begin(); + /** @brief State-machine tick while this mode is active. */ + virtual void loop(); + + protected: + elapsedMillis wait; +}; + +#endif diff --git a/WII5ModeCapture.cpp b/WII5ModeCapture.cpp new file mode 100644 index 0000000..7f3bff7 --- /dev/null +++ b/WII5ModeCapture.cpp @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModeCapture.cpp + * @brief Capture mode: IMU/wave-motion data capture and SD logging. + */ + +/* + +MDOECapture - the main event + + * Watch for our perfect time to start + * Prepare SD card, Sparton etc for running + * Trigger start / stop sparton + * Store data to disk + +*/ + +#include +#include +#include + + +void WII5ModeCapture::reset() { + step = WII5CAPTURE_OFF; wait = 0; stepCount = 0; + stepLast = WII5CAPTURE_FINISH; + captureType = WII5CAPTURETYPE_UNKNOWN; +} + +void WII5ModeCapture::begin() { + debug = true; + step = WII5CAPTURE_OFF; wait = 0; stepCount = 0; + captureType = WII5CAPTURETYPE_UNKNOWN; + enableAtFile = false; + enableGPS = true; + enableMaths = true; + enableComms = true; + enableLiveSwap = true; // Playing with this being safe for 2024 +} + +void WII5ModeCapture::start(bool force) { + // If OFF or WAITING for OFF - Skipt + if ( (step == WII5CAPTURE_OFF) || (step == WII5CAPTURE_WAIT5_OFF) || force ) { + console.log(LOG_INFO, F("Capture: MANUAL START - Now")); + captureType = WII5CAPTURETYPE_MANUAL; + captureStart = now(); + step = WII5CAPTURE_START; wait = 0; retry = 0; stepCount = 0; + } + else { + // Switch to CANCEL Capture with restart now + console.log(LOG_INFO, F("Capture: MANUAL START - Cancelling existing and restarting")); + captureType = WII5CAPTURETYPE_MANUAL; + captureStart = now(); + step = WII5CAPTURE_CANCEL_RESTART; wait = 0; retry = 0; stepCount = 0; + } +} + +void WII5ModeCapture::stop(bool force) { + if ((step != WII5CAPTURE_OFF) || force) { + // Switch to CANCEL Capture with OFF (aka Wait) + step = WII5CAPTURE_CANCEL_OFF; wait = 0; retry = 0; stepCount = 0; + } + else { + // Manually just double check everything is off. (done above in the CANCEL_OFF stage) + if (enableGPS) + wii5Gps.off(); + wii5Sparton.stop(); + } + captureType = WII5CAPTURETYPE_UNKNOWN; + wait = 0; retry = 0; stepCount = 0; +} + +elapsedMillis test1; +void WII5ModeCapture::loop() { + // Calculate seconds until next capture has to be started + + bool first = (stepLast != step); + stepLast = step; + + switch (step) { + case WII5CAPTURE_OFF: + // Total minutes since midnight + minutes = (hour() * 60) + minute(); + // Automatically set to a max of 12 hours + period = wii5Config.getCapturePeriod(); + remaining = period - (minutes % period); + + if (first) { + console.log(LOG_INFO, F("Capture: OFF, waiting")); + sh3dNodeIO.led1Set( LED_SLOW ); + } + // else if ((wii5Gps.sinceOnLast > (WII5TIME_1HOUR * 1000)) && (!wii5Gps.isTimeValid())) + // TODO 2024 Changed from 1 hour to 5 minutes - This should change to do a full auto every run + // Note: that & isTimeValid would often be true... + else if (wii5Gps.sinceOnLast > 300000) { + console.log(LOG_INFO, F("Capture: Internal clock / time is not valid, turn on GPS")); + step = WII5CAPTURE_TIME; wait = 0; + } + + // NOTE: we have only 60 seconds + else if ( (minutes % period) == 0) { + console.log(LOG_INFO, F("Capture: Starting minutes match period min=%lu, period=%lu"), minutes, wii5Config.getCapturePeriod()); + // TODO atData !!! + step = WII5CAPTURE_START; wait = 0; retry = 0; stepCount = 0; + captureType = WII5CAPTURETYPE_TIME; + captureStart = now(); + } + + // 20 or more mimutes + /* + else if (remaining > 30) { + // Hmmm... lets shut down everything, get the power lower + // TODO check Maths and Communications is off. + if (!wii5Communications.isRunning()) + wii5Communications.stop(); + if (wii5Controller.getSD() != 0) + wii5Controller.setSDOff(); + } + */ + + // 3 or more minutes + else if (remaining > 3) { + // Run temperature if more than 5 minutes old + if (wii5Weather_18B20.age > 300000) { + wii5Weather_18B20.temperatureRead(); + wii5Battery.start(); + } + } + + // TODO + // - Iridium: loop can do its own start/stop processing data + // - Weather & Battery already do their own + // - + + // TODO + // If WAIT is > X minutes, sleep, or just low power mode? + // GPS off, Iridium Off, Maths off, Sparton off, SD off + // Power Consumption = X + // AVR off as well + // Power Consumption = X + break; + + case WII5CAPTURE_START: + // Output Manual vs Time start + switch(captureType) { + case WII5CAPTURETYPE_MANUAL: + wii5Display.statusSend(F("CaptureMode"), F("start,manual")); + wii5Display.atDataSend( + F("CaptureCommand"), + F("START,Manual") + ); + break; + case WII5CAPTURETYPE_TIME: + wii5Display.statusSend(F("CaptureMode"), F("start,timeperiod")); + wii5Display.atDataSend( + F("CaptureCommand"), + F("START,Time,period=%lu,minutes=%lu"), + wii5Config.getCapturePeriod(), + minutes + ); + break; + }; + + startTime = now(); + step = WII5CAPTURE_PREPARE; wait = 0; retry = 0; stepCount = 0; + break; + + case WII5CAPTURE_TIME: + if (first) { + // wii5Gps.autoTime(); + wii5Gps.autoAccurate(); // TODO 2024 - Moved to full auto accurate instead + } + else if (wii5Gps.ready()) { + step = WII5CAPTURE_OFF; wait = 0; + } + else if (wait > 300000) { + step = WII5CAPTURE_OFF; wait = 0; + } + break; + + case WII5CAPTURE_PREPARE: + // Experiment - start communications DURING a run + wii5Communications.setAutoMode(); + + // Some emergency off, just in case - also handy as it does a reboot + wii5Sparton.stop(); + wii5Sparton.off(); + + step = WII5CAPTURE_PREPARE_ID; wait = 0; retry = 0; stepCount = 0; + break; + + case WII5CAPTURE_PREPARE_ID: + wii5Config.updateRecordCount(); + step = WII5CAPTURE_PREPARE_SD; wait = 0; retry = 0; stepCount = 0; + break; + + case WII5CAPTURE_PREPARE_SD: + // TODO - consider power cylce and delays, and consequence on Maths + // TODO - Maybe only if card not open? + if (wii5Controller.getSD() == 1) { + wii5Controller.setSD1(); + } + else { + wii5Controller.setSD2(); + } + + // TODO retry X times with power retry every second? + // TODO 2024 ! ZOMG ! safeDelay ? or not care? (was 1500) + delay(1000); // TODO Yes I know ! + + // TODO - If card not open? + if (sdBlock.cardOpen(wii5Controller.getSD())) { + wii5Display.statusSend(F("CaptureMode"), F("sdcard,current=%d"), wii5Controller.getSD()); + wii5Display.atDataSend( + F("CaptureCommand"), + F("SDCard,current,%d"), + wii5Controller.getSD() + ); + + // open card - else - bugger ! + if (sdBlock.dataOpen(wii5Config.getRecordCount())) { + wii5Display.atDataSend( + F("CaptureCommand"), + F("SDCard,current,%d,recordCount=%lu"), + wii5Controller.getSD(), wii5Config.getRecordCount() + ); + #ifdef WII5_GPS + if (enableGPS) { + wii5Gps.autoAccurate(); + } + #endif + step = WII5CAPTURE_PREPARE_COMMS; wait = 0; retry = 0; stepCount = 0; + } + else { + sh3dNodeIO.led1Set( LED_FAST ); + console.log(LOG_FATAL, F("CAPTURE: Failed dataOpen cardNum=%d"), wii5Controller.getSD()); + snprintf_P( + wii5Commands.lastCommandMessage, sizeof(wii5Commands.lastCommandMessage), + PSTR("@Err,sd,dataopen,%d"), + wii5Controller.getSD() + ); + wii5Commands.lastCommandCmd = 0; + wii5Communications.sendError(); + // Flip SD for next time + if (wii5Controller.getSD() == 1) + wii5Controller.setSD2(); + else + wii5Controller.setSD1(); + console.log(LOG_FATAL, F("CAPTURE: Cards swapped cardNum=%d"), wii5Controller.getSD()); + step = WII5CAPTURE_WAIT5_OFF; wait = 0; retry = 0; stepCount = 0; + } + } + else { + sh3dNodeIO.led1Set( LED_FAST ); + console.log(LOG_FATAL, F("CAPTURE: Failed cardOpen cardNum=%d"), wii5Controller.getSD()); + snprintf_P( + wii5Commands.lastCommandMessage, sizeof(wii5Commands.lastCommandMessage), + PSTR("@Err,sd,cardopen,%d"), + wii5Controller.getSD() + ); + wii5Commands.lastCommandCmd = 0; + wii5Communications.sendError(); + // Flip SD for next time + if (wii5Controller.getSD() == 1) + wii5Controller.setSD2(); + else + wii5Controller.setSD1(); + console.log(LOG_FATAL, F("CAPTURE: Cards swapped cardNum=%d"), wii5Controller.getSD()); + step = WII5CAPTURE_WAIT5_OFF; wait = 0; retry = 0; stepCount = 0; + } + break; + + case WII5CAPTURE_PREPARE_COMMS: + // For now - skipping comms - leaving until the end + step = WII5CAPTURE_PREPARE_SPARTON; wait = 0; retry = 0; stepCount = 0; + break; + + // Prepare the sparton to go + case WII5CAPTURE_PREPARE_SPARTON: + #ifdef WII5_IMU_SPARTON + wii5Display.statusSend(F("CaptureMode"), F("sparton,start")); + + // TODO Configuration + wii5Sparton.setBinary(1); + wii5Sparton.setHz(8); + wii5Sparton.setRecords(wii5Config.getCaptureRecords()); + wii5Sparton.setCapture(1); + + // Start + // TODO 2024 - Do we need to wait? - or just start? When is commands sent to Sparton? + wii5Sparton.start(); + #endif + step = WII5CAPTURE_INPROGRESS; wait = 0; retry = 0; stepCount = 0; + break; + + // Capture is running - report progress? (might be done in IMU) + case WII5CAPTURE_INPROGRESS: + if (first) { + sh3dNodeIO.led1Set( LED_TRIPPLE ); + } + else if (wii5Sparton.captureFinished()) { + step = WII5CAPTURE_SHUTDOWN; stepCount = 0; wait = 0; + } + // 30 minutes + else if (wait > 1800000) { + console.log(LOG_FATAL, F("Capture: Failed - timeout after 30 minutes")); + // TODO 2024 - sendError? + sh3dNodeIO.led1Set( LED_FAST ); + step = WII5CAPTURE_CANCEL_OFF; wait = 0; retry = 0; stepCount = 0; + } + break; + + // Shutdown the capture devices, IMU etc (should be internal IMIU) + case WII5CAPTURE_SHUTDOWN: + if (stepCount == 1) { + // Shutdown sparton? + } + step = WII5CAPTURE_SEND_PREPARE; stepCount = 0; wait = 0; + break; + + case WII5CAPTURE_SEND_PREPARE: + step = WII5CAPTURE_PROCESS; stepCount = 0; wait = 0; + break; + + case WII5CAPTURE_PROCESS: + // TODO This here should be switching SD Cards if possible + // - Stop SD + // - Switch SD + // - Start Maths + // - This allows code to be processed right away. + + // TODO - if Maths is off + // TODO - Switch SD Cards + // TODO - store new one in wii5Controller.getSD() for next time - ie. shoulduse that even if hours away + + step = WII5CAPTURE_FINISH; stepCount = 0; wait = 0; + break; + + // Three ways to cancel + case WII5CAPTURE_FLIP: + case WII5CAPTURE_CANCEL_RESTART: + case WII5CAPTURE_CANCEL_OFF: + case WII5CAPTURE_MANUAL_OFF: + if (first) { + console.log(LOG_FATAL, F("Capture: Manual Cancel - Turning off the devices.")); + #ifdef WII5_GPS + if (enableGPS) + wii5Gps.off(); + #endif + wii5Sparton.stop(); + + if (step == WII5CAPTURE_FLIP) { + console.log(LOG_INFO, F("CaptureMode: FLIP manually requested on SD Cards")); + + sdBlock.cardForceClose(); + wii5Controller.setSDOff(); + delay(500); // TODO Yes I know.... gah. TODO 2024 safeDelay - really 3 seconds TODO 2024 was 3000 + if (wii5Controller.getSD() == 1) { + wii5Controller.setSD2(); + wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,2")); + wii5Display.atDataSend( + F("MathsProcess"), + F("SD=2") + ); + } + else { + wii5Controller.setSD1(); + wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,1")); + wii5Display.atDataSend( + F("MathsProcess"), + F("SD=1") + ); + } + } + + } + // Maths ? + if (wait > 2000) { + if (step == WII5CAPTURE_CANCEL_RESTART) { + step = WII5CAPTURE_START; wait = 0; retry = 0; stepCount = 0; + } + else { + step = WII5CAPTURE_WAIT5_OFF; wait = 0; retry = 0; stepCount = 0; + } + } + break; + + case WII5CAPTURE_WAIT5_OFF: + if (stepCount == 1) + console.log(LOG_DEBUG, F("ModeCapture: Waiting for 90 seconds after fatal error before retrying")); + if (wait > 90000) { + step = WII5CAPTURE_OFF; stepCount = 0; wait = 0; + } + break; + + + // Capture is finished - what do we do? + // - Need to Sleep. e.g. more than 1 hour to wait. + // - Twiddlge thumbs waiting for Maths CPU to process and send Iridium + // . Reduce power of all other devices + // . Monitor Maths - don't let it run forever + // . Monitor Iridium - don't let it run forever + case WII5CAPTURE_FINISH: + wii5Display.statusSend(F("CaptureMode"), F("sparton,end")); + // Just sit here + wii5Display.atDataSend( + F("CaptureCommand"), + F("END") + ); + + // Close DATA + sdBlock.dataClose(false); + + // Create empty single entry for results. + sdBlock.dataOpen(wii5Config.getRecordCount()); + sdBlock.dataPrepare(); + sdBlock.dataWrite(); + sdBlock.dataClose(true); + + // Trigger temperatureRead - safe place at ene of a read + wii5Weather_18B20.temperatureRead(); + + #ifdef WII5_GPS + if (wii5Gps.isError()) { + // TODO report errors + console.log(LOG_DEBUG, F("GPS: failure I think")); + } + else if (wii5Gps.ready()) { + console.log(LOG_DEBUG, F("GPS: Success I think")); + } + else { + console.log(LOG_DEBUG, F("GPS: UNKNOWN I think")); + } + wii5Gps.off(); + #endif + + // TODO Check for errors + sdBlock.metadataPrepare(); + if (sizeof(WII5MetaDataObject) <= sizeof(sdBlock.metadata->data)) { + console.log(LOG_DEBUG, F("Writing SDBlock Metadata - WII5 Size=%d, SDBlock Size=%d"), + int(sizeof(WII5MetaDataObject)), + int(sizeof(sdBlock.metadata->data)) + ); + wii5Display.updateMetadata(sdBlock.metadata->data); + } + else { + console.log(LOG_FATAL, F("SDBlock and MetaData are not compatible sizes - WII5 Size=%d, SDBlock Size=%d"), + int(sizeof(WII5MetaDataObject)), + int(sizeof(sdBlock.metadata->data)) + ); + } + sdBlock.metadataWrite(); + console.log(LOG_DEBUG, F("SDBlock metadata write = %lu"), sdBlock.metadataLast); + + // Always close card + sdBlock.cardClose(); + + if (debug) { + delay(1000); + sdBlock.cardOpen(wii5Controller.getSD()); + sdBlock.metadataRead(0); + uint32_t dataBlockStart = sdBlock.metadata->dataBlockStart; + uint32_t dataBlocks = sdBlock.metadata->dataBlocks; + uint32_t resultsBlockStart = sdBlock.metadata->resultsBlockStart; + uint32_t resultsBlocks = sdBlock.metadata->resultsBlocks; + console.printf(F("@Block,metadata,address=%lu,deviceId=%lu,recordId=%lu\r\n"), + sdBlock.metadataLast, sdBlock.metadata->deviceId, sdBlock.metadata->recordId + ); + console.printf(F("@Block,metadata,dataBlockStart=%lu,dataBlocks=%lu\r\n"), + dataBlockStart, dataBlocks + ); + console.printf(F("@Block,metadata,resultsBlockStart=%lu,resultsBlocks=%lu\r\n"), + resultsBlockStart, resultsBlocks + ); + } + + // SAFETY + // - Check maths is off, others leave this one live + if (wii5Maths.isOff() || enableLiveSwap) { + // TODO need more delays... + // TODO - consider printing that we did Live Swap + if (wii5Controller.getSD() == 1) { + wii5Controller.setSD2(); + wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,2")); + wii5Display.atDataSend( + F("MathsProcess"), + F("SD=2") + ); + } + else { + wii5Controller.setSD1(); + wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,1")); + wii5Display.atDataSend( + F("MathsProcess"), + F("SD=1") + ); + } + + if (!sdBlock.cardOpen(wii5Controller.getSD())) { + sh3dNodeIO.led1Set( LED_FAST ); + console.log(LOG_ERROR, F("Error opening SD Card")); + } + // Signal we have swapped SD + wii5Communications.signalSD(); + } + else { + wii5Display.statusSend(F("CaptureMode"), F("sdcard,noswap")); + wii5Display.atDataSend( + F("MathsProcess"), + F("noswap") + ); + } + + // TODO Calculate waiting time. + // If Maths finished, and Iridium finished. + // And we have to wait longer than X (30 minutes?) + // Deep Sleep for 90% of that time. + + if(captureType == WII5CAPTURETYPE_MANUAL) { + console.log(LOG_INFO, F("Manual run executed, switching back to Default Mode")); + wii5Controller.setDefaultMode(); + } + + if (enableMaths) + wii5Maths.start(WII5MATHSMODE_AUTO); + wii5Communications.start(); // In case we didn't get SD, still tell it to do the job + step = WII5CAPTURE_WAIT_COMMS_MATHS; wait = 0; retry = 0; stepCount = 0; + break; + + case WII5CAPTURE_WAIT_COMMS_MATHS: + if (first) { + console.log(LOG_DEBUG, F("Waiting for Maths and Communicatinos to Finish")); + } + // If maths and Comms is off, jumpt to OFF + else if ( !wii5Communications.isRunning() && wii5Maths.isOff()) { + console.log(LOG_INFO, F("CAPTURE: Maths and Comms finsihed")); + step = WII5CAPTURE_OFF; wait = 0; retry = 0; stepCount = 0; + } + // Wait 3 minuts here quietly + else if (wait > 180000) { + console.log(LOG_INFO, F("CAPTURE: 3 minutes and Maths and Communications not finished. Mvoing on but leaving them on.")); + step = WII5CAPTURE_OFF; wait = 0; retry = 0; stepCount = 0; + } + break; + + default: + console.log(LOG_FATAL, F("CAPTURE: Default step... step=%d"), step); + step = WII5CAPTURE_OFF; stepCount = 0; wait = 0; + break; + } + + stepCount++; + stepTotal++; +} + +void WII5ModeCapture::flip() { + step = WII5CAPTURE_FLIP; wait = 0; stepCount = 0; +} + +WII5ModeCapture wii5ModeCapture; diff --git a/WII5ModeCapture.h b/WII5ModeCapture.h new file mode 100644 index 0000000..9658e72 --- /dev/null +++ b/WII5ModeCapture.h @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModeCapture.h + * @brief Capture mode: IMU/wave-motion data capture and SD logging. + */ + +#ifndef WII5ModeCapture_h +#define WII5ModeCapture_h + +#include +#include +#include + +enum WII5CAPTURE_TYPE { + WII5CAPTURETYPE_UNKNOWN, + WII5CAPTURETYPE_MANUAL, + WII5CAPTURETYPE_TIME, +}; + +enum WII5CAPTURE_STEPS { + WII5CAPTURE_START, + WII5CAPTURE_OFF, + WII5CAPTURE_TIME, + WII5CAPTURE_PREPARE, + WII5CAPTURE_PREPARE_SD, + WII5CAPTURE_PREPARE_COMMS, + WII5CAPTURE_PREPARE_ID, + WII5CAPTURE_PREPARE_SPARTON, + WII5CAPTURE_WAIT, + WII5CAPTURE_INPROGRESS, + WII5CAPTURE_SHUTDOWN, + WII5CAPTURE_PROCESS, + WII5CAPTURE_SEND_PREPARE, + WII5CAPTURE_SEND_METADATA, + WII5CAPTURE_SEND_RAW, + WII5CAPTURE_CANCEL_RESTART, + WII5CAPTURE_CANCEL_OFF, + WII5CAPTURE_MANUAL_OFF, + WII5CAPTURE_WAIT5_OFF, + WII5CAPTURE_FLIP, + WII5CAPTURE_WAIT_COMMS_MATHS, + WII5CAPTURE_FINISH +}; + +/** + * @brief Capture mode: time-driven IMU data capture and SD logging. + * + * Drives the WII5CAPTURE_STEPS state machine: prepare SD + comms + IMU, + * wait for the start time, run the capture, write metadata and + * processed-results blocks, optionally flip SD cards, then idle until + * the next capture window. + */ +class WII5ModeCapture : public WII5Mode { + public: + WII5ModeCapture() {} + /** @brief Reset to WII5CAPTURE_START. */ + void reset(); + /** @brief One-time bring-up. */ + void begin(); + /** @brief State-machine tick. */ + void loop(); + + /** @brief Begin a manual capture. */ + void start(bool force = false); + /** @brief Abort the current capture. */ + void stop(bool force = false); + + bool enableAtFile; + bool enableGPS; + bool enableMaths; + bool enableComms; + bool enableLiveSwap; + + /** @brief Switch the active SD card to the alternate one. */ + void flip(); + uint32_t startTime; + + protected: + elapsedMillis wait; + time_t captureStart; + WII5CAPTURE_TYPE captureType; + WII5CAPTURE_STEPS step; + WII5CAPTURE_STEPS stepLast; + uint32_t stepCount; + uint32_t stepTotal; + uint8_t retry; + + uint32_t minutes; // since midnight. + uint32_t period; + uint32_t remaining; + + bool debug; + +}; +extern WII5ModeCapture wii5ModeCapture; + +#endif diff --git a/WII5ModeLowBattery.cpp b/WII5ModeLowBattery.cpp new file mode 100644 index 0000000..fe92c40 --- /dev/null +++ b/WII5ModeLowBattery.cpp @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModeLowBattery.cpp + * @brief Low-battery mode: degraded operation when supply is depleted. + */ + +/* + +LowBattery mode - a simple repeatign mode that will capture position data and send it. + +*/ + +#include +#include +#include + +void WII5ModeLowBattery::reset() { + step = WII5LOWBATTERY_START; + wait = 0; +} + +void WII5ModeLowBattery::begin() { +} + +void WII5ModeLowBattery::start() { + step = WII5LOWBATTERY_GPS_START; wait = 0; +} + +void WII5ModeLowBattery::stop() { + step = WII5LOWBATTERY_END; wait = 0; +} + +void WII5ModeLowBattery::loop() { + bool first = (lastStep != step); + lastStep = step; + + switch (step) { + case WII5LOWBATTERY_START: + if (first) { + // TODO This seems bad, perhaps only if next is 10 minutes? + // First time in, turn off devices we don't need. + // Can we turn off Maths. + sh3dNodeIO.led1Set( LED_TRIPPLE_GAP ); + wii5Controller.setSDOff(); + wii5Gps.off(); + wii5Iridium.stop(); + wii5Sparton.stop(); + wii5Controller.shared5Off(); + + console.log(LOG_INFO, F("LowBattery: Waiting for %lu minutes"), + wii5Display.minutesUntilNext(40) + ); + } + else if (wait > (WII5TIME_1DAY * 1000)) { + console.log(LOG_ERROR, F("LowBattery: Timeout - over 24 hours, moving on to do a location and data send")); + step = WII5LOWBATTERY_GPS_START; wait = 0; + } + else { + // TODO - reduce this complicated task ? Lots of maths, being run at full speed + minutes = (hour() * 60) + minute(); + if ( (minutes % 40) == 0) { + console.log(LOG_INFO, F("LowBattery: Starting minutes match period min=%lu, period=%lu"), minutes, 40); + step = WII5LOWBATTERY_GPS_START; wait = 0; + sh3dNodeIO.led1Set( LED_TRIPPLE_GAP ); + } + } + break; + + case WII5LOWBATTERY_GPS_START: + // Ask the GPS for accurate data + console.log(LOG_DEBUG, F("LowBattery: Starting GPS, Battery, Temperature")); + #ifdef WII5_GPS + wii5Gps.autoAccurate(); + #endif + wii5Battery.start(); + wii5Weather_18B20.temperatureRead(); + step = WII5LOWBATTERY_GPS_WAIT; wait = 0; + break; + + case WII5LOWBATTERY_GPS_WAIT: + if (first) { + displayWait = 0; + } + else if (wii5Gps.ready()) { + if (wii5Gps.isError()) { + console.log(LOG_ERROR, F("LowBattery: GPS Finished with ERROR")); + } + wii5Gps.off(); + step = WII5LOWBATTERY_IRIDIUM_WAIT; wait = 0; + } + else if (wait > 120000) { + console.log(LOG_ERROR, F("LowBattery: GPS Timed out after 300 seconds")); + step = WII5LOWBATTERY_IRIDIUM_WAIT; wait = 0; + } + break; + + case WII5LOWBATTERY_IRIDIUM_WAIT: + if (first) { + console.log(LOG_INFO, F("LowBattery: Communications start")); + wii5Communications.setSimpleMode(); + // Increment updateRecordCount for each save to SD OR send to Iridium + wii5Communications.sendBinModeType(1, wii5Config.getRecordCount()); + wii5Communications.start(); + } + else if (!wii5Communications.isRunning()) { + console.log(LOG_INFO, F("LowBattery: Communications end")); + step = WII5LOWBATTERY_END; wait = 0; + } + // TODO 10 minutes ok + else if (wait > 600000) { + console.log(LOG_FATAL, F("LowBattery: Communications timeout")); + step = WII5LOWBATTERY_END; wait = 0; + } + break; + + case WII5LOWBATTERY_END: + if (first) { + console.log(LOG_DEBUG, F("LowBattery: End")); + } + wii5Communications.stop(); + wii5Gps.off(); + + // Stop everything + wii5Controller.setSDOff(); + wii5Sparton.stop(); + wii5Controller.shared5Off(); + + step = WII5LOWBATTERY_START; wait = 0; + break; + + default: + console.log(LOG_FATAL, F("LOWBATTERY: Default step... step=%d"), step); + step = WII5LOWBATTERY_START; + } +} + +WII5ModeLowBattery wii5ModeLowBattery; diff --git a/WII5ModeLowBattery.h b/WII5ModeLowBattery.h new file mode 100644 index 0000000..c89a18f --- /dev/null +++ b/WII5ModeLowBattery.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModeLowBattery.h + * @brief Declares WII5ModePosition (position mode). + * + * @note The WII5ModePosition.h / WII5ModeLowBattery.h header pair are + * swapped relative to their filenames in this codebase: this file + * (WII5ModeLowBattery.h) declares class WII5ModePosition, and + * WII5ModePosition.h declares class WII5ModeLowBattery. The corresponding + * .cpp files match their filenames. TODO: untangle and rename. + */ + +#ifndef WII5ModePosition_h +#define WII5ModePosition_h + +#include +#include +#include + +enum WII5POSITION_STEPS { + WII5POSITION_START, + WII5POSITION_STROBE, + WII5POSITION_TIME, + WII5POSITION_GPS_START, + WII5POSITION_GPS_WAIT, + WII5POSITION_IRIDIUM_WAIT, + WII5POSITION_END +}; + +/** + * @brief Position mode: periodic GPS + Iridium telemetry only. + * + * Lower-power than full Capture mode: turns on GPS, gets a fix, sends + * a position via Iridium SBD, then idles until the next position window. + * No IMU capture, no SD writes. + */ +class WII5ModePosition : public WII5Mode { + public: + WII5ModePosition() {} + /** @brief Reset to WII5POSITION_START. */ + void reset(); + /** @brief One-time bring-up. */ + void begin(); + /** @brief State-machine tick. */ + void loop(); + /** @brief Begin a position acquisition cycle. */ + void start(); + /** @brief Abort the current cycle. */ + void stop(); + + protected: + bool first; + elapsedMillis displayWait; + elapsedMillis wait; + WII5POSITION_STEPS step; + WII5POSITION_STEPS lastStep; + uint32_t minutes; // since midnight. +}; + +extern WII5ModePosition wii5ModePosition; + +#endif diff --git a/WII5ModeManualTest.cpp b/WII5ModeManualTest.cpp new file mode 100644 index 0000000..9622082 --- /dev/null +++ b/WII5ModeManualTest.cpp @@ -0,0 +1,1757 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModeManualTest.cpp + * @brief Manual test mode: operator-driven hardware exercise via the console. + */ + +/* + +Manual Test Mode - Dangerous ! + +*/ + +#include +#include +#include +#include "RTClib.h" + +uint16_t sdPrev = 0; +WII5_DATA_SpartonBinary* spartonData; +uint32_t binDataType = 0; +bool firstLoop; + +void WII5ModeManualTest::reset() { + sdPrev = 0; + pulsePin = 0; + firstLoop = true; +} + +void WII5ModeManualTest::begin() { + waitGPS = false; +} + +elapsedMicros captureWriteTime; +uint32_t captureWriteTemp; +uint32_t captureWriteMax = 0; +uint32_t captureWriteMin = 1000000; +uint32_t captureWriteOver = 0; + +void WII5ModeManualTest::loop() { + if (firstLoop) { + console.setLevel(LOG_ALL); + console.log(LOG_DEBUG,F("Switching to test mode has also enabled full logging. See @Help,log to change or reboot")); + firstLoop = false; + } + if(enableBattery) + wii5Battery.loop(); + + #ifdef WII5_IMU_SPARTON + if(enableSparton) + wii5Sparton.loop(); + #endif + + #ifdef WII5_GPS + if(enableGps) + wii5Gps.loop(); + if (waitGPS) { + if (wii5Gps.ready()) { + waitGPS = false; + console.log(LOG_DEBUG, F("GPS COMPLETED")); + } + } + #endif + + /* + if ( (pulsePin > 0) && (pulseWait > 100) ) { + digitalWrite(pulsePin, !digitalRead(pulsePin)); + pulseWait = 0; + } + */ + + // Extended test commands - only available in test mode + if (console.available()) { + uint32_t val = console.getVal(); + int16_t pin = -1; + Print* f; + if (int(val) < NUM_PINS) + pin = int(val); + switch(console.getCommand()) { + // TODO 2024 - Need to put this in help + case 'V': + console.log(LOG_FATAL, F("ABOUT To loop (no other loops for 20 seconds)")); + for (uint8_t n = 0; n < 20; n++) { + console.log(LOG_FATAL, F("Loop %d"), n); + delay(1000); + } + console.log(LOG_FATAL, F("End of the 20 second loop")); + case 'h': + case 'H': + + // -------------------- + console.log(LOG_DEBUG, + F("*** Extra debug help ***")); + + // -------------------- + console.log(LOG_DEBUG, + F("Sleep/Power:")); + console.log(LOG_DEBUG, + // TODO move S to be self test + F(" S{n} - (S0; for more help) sleep now for {n} seconds - no mode change")); + + // -------------------- + console.log(LOG_DEBUG, + F("PINS:")); + console.log(LOG_DEBUG, + F(" * - Dump all pins. ?{n} - Current mode one pin.")); + console.log(LOG_DEBUG, + F(" o|i{n} - Set to output | input, O{n} - output and pulse 100ms only in here")); + console.log(LOG_DEBUG, + F(" X|x{n} - Set to HIGH | low (add 100 to pin to make output too)")); + + // -------------------- + console.log(LOG_DEBUG, + F("Config:")); + console.log(LOG_DEBUG, + F(" @WII5,setting,list - to see a list of settings")); + console.log(LOG_DEBUG, + F(" @Help,setting - for further help")); + + // -------------------- + console.log(LOG_DEBUG, + F("Storage:")); + console.log(LOG_DEBUG, + F(" m{n} - set Storage Mode 0,1,2,3,4")); + console.log(LOG_DEBUG, + F(" 0=Off, 1=SD1 (power/buffers), 2=SD2 (power/buffers), 11=SD1+Init, 12=SD2+Init")); + console.log(LOG_DEBUG, + F(" 9=metaData list (last 100)")); + + // console.log(LOG_DEBUG, + // F(" M{n} - test Storage 0=Add to file,1=List,2=nextRecord")); + + // -------------------- + console.log(LOG_DEBUG, + F("Serial/Console:")); + console.log(LOG_DEBUG, + F(" c - Test console output")); + console.log(LOG_DEBUG, + F(" c100 - Send @WII5,maths,beep to Console from a string.")); + + // -------------------- + console.log(LOG_DEBUG, + F("GPS:")); + console.log(LOG_DEBUG, + F(" g{n} - gps,0 Off,1 Start, 2 autopos. 50 dump.")); + console.log(LOG_DEBUG, + F(" - 100 passthrough off, 101 passthrough on, ")); + console.log(LOG_DEBUG, + F(" - 110 debug off, 111 debug on")); + + // -------------------- + #ifdef WII5_LORA_RADIO + console.log(LOG_DEBUG, + F("Radio:")); + console.log(LOG_DEBUG, + F(" r{n} - radio. 0=off, 3=send echo, 100 passthrough off, 101 passthrough on, 110 debug off, 111 debug on")); + #else + console.log(LOG_DEBUG, F("Radio: (not enabled)")); + #endif + + // -------------------- + console.log(LOG_DEBUG, + F("Comms/Iridium:")); + console.log(LOG_DEBUG, + F(" q{n} - iridium, 0 off, 1 start. 2 sendText. 3 longer text. 4 binary")); + console.log(LOG_DEBUG, + F(" 66=REPL, 80=Request Firmware")); + console.log(LOG_DEBUG, + F(" 100 passthrough off, 101 passthrough on, 110 debug off, 111 debug on")); + console.log(LOG_DEBUG, + F(" 500+ - testing split")); + + // -------------------- + console.log(LOG_DEBUG, + F("Capture/IMU:")); + console.log(LOG_DEBUG, + F(" p{n} - 0 off, 1 start")); + console.log(LOG_DEBUG, + F(" - 100 passthrough off, 101 passthrough on, 110 debug off, 111 debug on, ")); + console.log(LOG_DEBUG, + F(" - 120 capture off, 121 capture on, 130 8Hz, 131 64Hz, 140 Not binary, 141 Binary")); + console.log(LOG_DEBUG, + F(" 66 = REPL, 67 = info debug, 68 = 0.5 Hz, passthrough, text")); + console.log(LOG_DEBUG, + F(" 8hz 200=100 recs 201=1000 recs, 202 5120 recs, +10 with Capture")); + console.log(LOG_DEBUG, + F(" 64hz 300=100 recs 301=5000 recs, 302 40960 recs, +10 with Capture")); + console.log(LOG_DEBUG, + F(" > 1000 = records - 1000")); + + // -------------------- + console.log(LOG_DEBUG, + F("Weather:")); + console.log(LOG_DEBUG, + F(" w1 - weather - read current temperature")); + + // -------------------- + console.log(LOG_DEBUG, + F("Battery:")); + console.log(LOG_DEBUG, + F(" v - 0=Off, 1=On")); + + // -------------------- + console.log(LOG_DEBUG, + F("Maths")); + console.log(LOG_DEBUG, + F(" M{n} - 0 stop, 1 start")); + + // -------------------- + console.log(LOG_DEBUG, + F("Weather")); + console.log(LOG_DEBUG, + F(" w{n} - 1 temperature read")); + + // -------------------- + console.log(LOG_DEBUG, + F("LED")); + console.log(LOG_DEBUG, + F(" l{xy} - LED x to type Y (0 off, 1 on)")); + console.log(LOG_DEBUG, + F(" 100=shutdown, 101=boot, 102=capture, 103=sleep")); + + // -------------------- + console.log(LOG_DEBUG, + F("RTC")); + console.log(LOG_DEBUG, + F(" R{n} - 66 test code, >10,000 ")); + + // -------------------- + console.log(LOG_DEBUG, + F("BinData")); + console.log(LOG_DEBUG, + F(" B - n > 1000000 = n-1000000 for the Type ID")); + console.log(LOG_DEBUG, + F(" B1 size, B2 create, B3 display")); + + // -------------------- + console.log(LOG_DEBUG, + F("Mode")); + console.log(LOG_DEBUG, + F(" . - Back to default mode (could be odd state, consider reboot)")); + console.log(LOG_DEBUG, + F(" ? status, %% sizes")); + break; + + case 'Q': + + if (1) { + binDataType = wii5BinData.setBit(0, 0); + binDataType = wii5BinData.setBit(binDataType, 8); + binDataType = wii5BinData.setBit(binDataType, 9); + + wii5Communications.setSimpleMode(); + wii5Communications.start(); + wii5Communications.sendBinModeType(binDataType, 16845569, 1300720, 3104221); + } + + // n100; Start simple mode + if (0) { + // 6..14 + binDataType = wii5BinData.setBit(0, 6); + binDataType = wii5BinData.setBit(binDataType, 7); + binDataType = wii5BinData.setBit(binDataType, 8); + binDataType = wii5BinData.setBit(binDataType, 9); + binDataType = wii5BinData.setBit(binDataType, 10); + binDataType = wii5BinData.setBit(binDataType, 11); + binDataType = wii5BinData.setBit(binDataType, 12); + binDataType = wii5BinData.setBit(binDataType, 13); + binDataType = wii5BinData.setBit(binDataType, 14); + + console.log(LOG_DEBUG, F("So how big is this - %d"), wii5BinData.getSize(binDataType)); + + uint8_t nextBit = 0; + uint32_t outType = 0; + uint32_t block_in = 1300720; + if (!sdBlock.read(block_in)) { + console.log(LOG_ERROR, F("Unable to view block - %lu"), block_in); + return; + } + uint32_t resultsBlockStart = sdBlock.metadata->resultsBlockStart; + if (!sdBlock.read(resultsBlockStart)) { + console.log(LOG_ERROR, F("Unable to view resultsBlock - %lu"), resultsBlockStart); + return; + } + WII5Processed* processed = (WII5Processed*)sdBlock.block->data; + + uint8_t count = 0; + while (wii5BinData.getSplit(binDataType, 340, &nextBit, &outType)) { + console.log(LOG_DEBUG, F("BinData Splitting #=%d, original type=%lu, new type=%lu, size=%d, nextBit=%d"), + count, binDataType, outType, wii5BinData.getSize(outType), int(nextBit) + ); + console.printf(F("# New %d"), count); + wii5BinData.showBlocks(outType); + console.printNewLine(); + + wii5BinData.createData(outType, wii5BinaryIridium, 340, 777); + wii5BinData.setData(outType, wii5BinaryIridium, 340); // TODO Hard coded + // wii5BinData.setBlockMetadata(outType, wii5BinaryIridium, 340, (WII5MetaDataObject*)sdBlock.metadata->data); + wii5BinData.setBlockResults(outType, wii5BinaryIridium, 340, (WII5Processed*)sdBlock.block->data); + + console.printf(F(" ... Block (faked) sent")); + + count++; + } + console.log(LOG_ERROR, F("Well.... block seems to have worked")); + } + break; + + + + // BinDAta + case 'B': + switch(val) { + case 0: + console.log(LOG_DEBUG, F("BinData: Check help h;")); + wii5BinData.showSizes(); + break; + + case 1: + console.log(LOG_DEBUG, F("BinData: type=%lu, size=%d"), binDataType, wii5BinData.getSize(binDataType)); + break; + + case 2: + console.log(LOG_DEBUG, F("BinData: Creating")); + wii5BinData.createData(binDataType, wii5BinaryIridium, 340, 0); + wii5BinData.setData(binDataType, wii5BinaryIridium, 340); // TODO Hard coded + break; + + case 3: + console.log(LOG_DEBUG, F("BinData: type=%lu, size=%d"), binDataType, wii5BinData.getSize(binDataType)); + wii5BinData.dumpData(wii5BinaryIridium, 340, false); // TODO Hard coded + break; + case 4: + console.log(LOG_DEBUG, F("BinData: type=%lu, size=%d"), binDataType, wii5BinData.getSize(binDataType)); + wii5BinData.dumpData(wii5BinaryIridium, 340, true); // TODO Hard coded + break; + + case 100: + binDataType = wii5BinData.setBit(0, 0); + console.log(LOG_DEBUG, F("BinData: set 0 type=%lu size=%d"), binDataType, wii5BinData.getSize(binDataType)); + break; + case 101: + binDataType = wii5BinData.setBit(0, 1); + console.log(LOG_DEBUG, F("BinData: set 1 type=%lu size=%d"), binDataType, wii5BinData.getSize(binDataType)); + break; + case 102: + binDataType = wii5BinData.setBit(0, 2); + console.log(LOG_DEBUG, F("BinData: set 2 type=%lu size=%d"), binDataType, wii5BinData.getSize(binDataType)); + break; + case 201: + binDataType = wii5BinData.setBit(0, 0); + binDataType = wii5BinData.setBit(binDataType, 1); + console.log(LOG_DEBUG, F("BinData: set 0+1 type=%lu size=%d"), binDataType, wii5BinData.getSize(binDataType)); + break; + + case 250: + console.log(LOG_DEBUG, F("BinData: split test")); + { + binDataType = wii5BinData.setBit(0, 0); + binDataType = wii5BinData.setBit(binDataType, 1); + binDataType = wii5BinData.setBit(binDataType, 2); + binDataType = wii5BinData.setBit(binDataType, 11); + binDataType = wii5BinData.setBit(binDataType, 12); + binDataType = wii5BinData.setBit(binDataType, 13); + binDataType = wii5BinData.setBit(binDataType, 14); + console.log(LOG_DEBUG, F("BinData: split test set type=%lu size=%d"), binDataType, wii5BinData.getSize(binDataType)); + + uint8_t nextBit = 0; + uint32_t outType = 0; + + console.printf(F("# Orig ")); + wii5BinData.showBlocks(binDataType); + // SerialConsole.print(F("orig: ")); + // console.printBits(&binDataType, 16); + // SerialConsole.print(F(" new: ")); + // console.printBits(&outType, 16); + + uint8_t count = 0; + while (wii5BinData.getSplit(binDataType, 300, &nextBit, &outType)) { + console.log(LOG_DEBUG, F("BinData Splitting #=%d, original type=%lu, new type=%lu, size=%d, nextBit=%d"), + count, binDataType, outType, wii5BinData.getSize(outType), int(nextBit) + ); + console.printf(F("# New %d"), count); + wii5BinData.showBlocks(outType); + count++; + } + console.log(LOG_DEBUG, F("BinData: split test end")); + } + break; + case 300: + binDataType = wii5BinData.setBit(0, 0); + binDataType = wii5BinData.setBit(binDataType, 1); + binDataType = wii5BinData.setBit(binDataType, 2); + binDataType = wii5BinData.setBit(binDataType, 11); + binDataType = wii5BinData.setBit(binDataType, 12); + binDataType = wii5BinData.setBit(binDataType, 13); + binDataType = wii5BinData.setBit(binDataType, 14); + console.log(LOG_DEBUG, F("BinData: set MATHS FULL type=%lu size=%d"), binDataType, wii5BinData.getSize(binDataType)); + break; + + default: + if (val > 1000000) { + binDataType = val - 1000000; + } + } + break; + + // Console + case 'c': + switch(val) { + case 100: + { + console.log(LOG_FATAL, F("Console: Sending a manual text string to th econsole")); + char sendCmd[] = "setting,list"; + wii5Commands.injectCommand(sendCmd); + } + break; + case 101: + { + console.log(LOG_FATAL, F("Console: Sending a manual text string to th econsole")); + char sendCmd[] = "notexist"; + wii5Commands.injectCommand(sendCmd); + } + break; + default: + console.log(LOG_FATAL, "Demo log fatal...."); + break; + }; + break; + + + // LED + case 'l': + wii5Controller.shared5On(); + switch(val) { + case 10: + sh3dNodeIO.led1Set(LED_OFF); + break; + case 11: + sh3dNodeIO.led1Set(LED_ON); + break; + case 12: + sh3dNodeIO.led1Set(LED_SLOW); + break; + case 13: + sh3dNodeIO.led1Set(LED_FAST); + break; + case 14: + sh3dNodeIO.led1Set(LED_SHORT); + break; + case 15: + sh3dNodeIO.led1Set(LED_DOUBLE_GAP); + break; + case 16: + sh3dNodeIO.led1Set(LED_DOUBLE_LONG); + break; + case 17: + sh3dNodeIO.led1Set(LED_TRIPPLE); + break; + case 18: + sh3dNodeIO.led1Set(LED_TRIPPLE_GAP); + break; + case 19: + sh3dNodeIO.led1Set(LED_TRIPPLE_LONG); + break; + + /* + LED_SHORT_GAP, + LED_SHORT + */ + + case 20: + sh3dNodeIO.led2Set(LED_OFF); + break; + case 21: + console.log(LOG_DEBUG, F("BEEP: on")); + sh3dNodeIO.led2Set(LED_ON); + break; + case 22: + console.log(LOG_DEBUG, F("BEEP: slow")); + sh3dNodeIO.led2Set(LED_SLOW); + break; + case 23: + console.log(LOG_DEBUG, F("BEEP: fast")); + sh3dNodeIO.led2Set(LED_FAST); + break; + case 24: + console.log(LOG_DEBUG, F("BEEP: double")); + sh3dNodeIO.led2Set(LED_DOUBLE); + break; + case 25: + console.log(LOG_DEBUG, F("BEEP: tripple")); + sh3dNodeIO.led2Set(LED_TRIPPLE); + break; + case 26: + console.log(LOG_DEBUG, F("BEEP: short long")); + sh3dNodeIO.led2Set(LED_SHORT_LONG); + break; + case 27: + console.log(LOG_DEBUG, F("BEEP: double long")); + sh3dNodeIO.led2Set(LED_DOUBLE_LONG); + break; + case 28: + console.log(LOG_DEBUG, F("BEEP: tripple long")); + sh3dNodeIO.led2Set(LED_TRIPPLE_LONG); + break; + case 100: + wii5Setup.shutdownbeep(); + break; + case 101: + wii5Setup.bootbeep(); + break; + case 102: + sh3dNodeIO.led2Set(LED_TRIPPLE_LONG); + break; + case 103: + sh3dNodeIO.led2Set(LED_DOUBLE_LONG); + break; + case 104: + sh3dNodeIO.led2Set(LED_ONCE); + break; + case 105: + sh3dNodeIO.led2Set(LED_TWICE); + break; + }; + break; + + case 'S': + // pinMode(POWER_MATHS_PIN, INPUT); + // pinMode(POWER_MATHS_PIN, OUTPUT); + // digitalWrite(POWER_MATHS_PIN, LOW); + + switch(val) { + case 0: + console.log(LOG_DEBUG, F("SLEEP Commands:")); + console.log(LOG_DEBUG, F(" 1000 - TEST SLEEP Start: ALL PINS INPUT Direct sleep for 60 seconds")); + console.log(LOG_DEBUG, F(" 2000 - TEST SLEEP Start: POWER LOW, rest INPUT, Direct sleep for 60 seconds")); + console.log(LOG_DEBUG, F(" 3000 - TEST SLEEP Start: POWER LOW, rest INPUT, no sleep")); + console.log(LOG_DEBUG, F(" 4000 - TEST SLEEP Start: sleep only")); + console.log(LOG_DEBUG, F(" >0 - Sleep for n seconds")); + break; + + case 1000: + console.log(LOG_DEBUG, F("TEST SLEEP Start: ALL PINS INPUT Direct sleep for 60 seconds")); + // 5 Volts and Maths - FORCED OFF + pinMode(SHARED_5VOLT_PIN, OUTPUT); + digitalWrite(SHARED_5VOLT_PIN, LOW); + pinMode(POWER_MATHS_PIN, OUTPUT); + digitalWrite(POWER_MATHS_PIN, LOW); + + // Storage SD Card + // pinMode(POWER_SUBBOARD_1_PIN, INPUT); + // pinMode(POWER_SUBBOARD_2_PIN, INPUT); + pinMode(POWER_GPS_PIN, OUTPUT); + digitalWrite(POWER_GPS_PIN, LOW); + // pinMode(POWER_COMMS_PIN, INPUT); + // pinMode(POWER_RADIO_PIN, INPUT); + // pinMode(POWER_IMU_MPU9250_PIN, INPUT); + // pinMode(POWER_STROBE1_ON, INPUT); + + // Misc Controlling lines + pinMode(POWER_STORAGE_1_PIN, INPUT); + pinMode(POWER_STORAGE_2_PIN, INPUT); + pinMode(STORAGE_SD1_MATHS_PIN, OUTPUT); + digitalWrite(STORAGE_SD1_MATHS_PIN, LOW); + // pinMode(STORAGE_SD2_MATHS_PIN, INPUT); + pinMode(SPARTON_RESET, INPUT); + pinMode(RADIO_CS, INPUT); + pinMode(STORAGE_CS, INPUT); + + // LEDs + pinMode(LED_1, OUTPUT); + digitalWrite(LED_1, LOW); + pinMode(LED_2, INPUT); + pinMode(LED_3, INPUT); + + // Weather + pinMode(POWER_WEATHER_18B20_PIN, INPUT); + pinMode(POWER_WEATHER_PIN, INPUT); + + sh3dNodeUtil.sleep(60); + break; + case 2000: + console.log(LOG_DEBUG, F("TEST SLEEP Start: POWER LOW, rest INPUT, Direct sleep for 60 seconds")); + // Power set to LOW OUTPUT - rest to inputs + pinMode(SHARED_5VOLT_PIN, OUTPUT); + digitalWrite(SHARED_5VOLT_PIN, LOW); + pinMode(POWER_MATHS_PIN, OUTPUT); + digitalWrite(POWER_MATHS_PIN, LOW); + + // Storage SD Card + pinMode(POWER_STORAGE_1_PIN, OUTPUT); + digitalWrite(POWER_STORAGE_1_PIN, LOW); + pinMode(POWER_STORAGE_2_PIN, OUTPUT); + digitalWrite(POWER_STORAGE_2_PIN, LOW); + // pinMode(POWER_SUBBOARD_1_PIN, OUTPUT); + // digitalWrite(POWER_SUBBOARD_1_PIN, LOW); + // pinMode(POWER_SUBBOARD_2_PIN, OUTPUT); + // digitalWrite(POWER_SUBBOARD_2_PIN, LOW); + pinMode(POWER_GPS_PIN, OUTPUT); + digitalWrite(POWER_GPS_PIN, LOW); + pinMode(POWER_COMMS_PIN, OUTPUT); + digitalWrite(POWER_COMMS_PIN, LOW); + pinMode(POWER_RADIO_PIN, OUTPUT); + digitalWrite(POWER_RADIO_PIN, LOW); + // pinMode(POWER_IMU_MPU9250_PIN, OUTPUT); + // digitalWrite(POWER_IMU_MPU9250_PIN, LOW); + pinMode(POWER_STROBE1_ON, OUTPUT); + digitalWrite(POWER_STROBE1_ON, LOW); + + // Misc Controlling lines + pinMode(STORAGE_SD1_MATHS_PIN, INPUT); + pinMode(STORAGE_SD2_MATHS_PIN, INPUT); + pinMode(SPARTON_RESET, INPUT); + pinMode(RADIO_CS, INPUT); + pinMode(STORAGE_CS, INPUT); + + // LEDs + pinMode(LED_1, INPUT); + pinMode(LED_2, INPUT); + pinMode(LED_3, INPUT); + + // Weather + pinMode(POWER_WEATHER_18B20_PIN, INPUT); + pinMode(POWER_WEATHER_PIN, INPUT); + + sh3dNodeUtil.sleep(60); + pinMode(LED_1, OUTPUT); + pinMode(LED_2, OUTPUT); + pinMode(SHARED_5VOLT_PIN, INPUT); + digitalWrite(SHARED_5VOLT_PIN, HIGH); + pinMode(POWER_MATHS_PIN, INPUT); + digitalWrite(POWER_MATHS_PIN, HIGH); + sh3dNodeIO.led1Set(LED_FAST); + + break; + + case 3000: + console.log(LOG_DEBUG, F("TEST SLEEP Start: POWER LOW, rest INPUT, no sleep")); + // Power set to LOW OUTPUT - rest to inputs + pinMode(SHARED_5VOLT_PIN, OUTPUT); + digitalWrite(SHARED_5VOLT_PIN, LOW); + pinMode(POWER_MATHS_PIN, OUTPUT); + digitalWrite(POWER_MATHS_PIN, LOW); + + // Storage SD Card + pinMode(POWER_STORAGE_1_PIN, OUTPUT); + digitalWrite(POWER_STORAGE_1_PIN, LOW); + pinMode(POWER_STORAGE_2_PIN, OUTPUT); + digitalWrite(POWER_STORAGE_2_PIN, LOW); + // pinMode(POWER_SUBBOARD_1_PIN, OUTPUT); + // digitalWrite(POWER_SUBBOARD_1_PIN, LOW); + // pinMode(POWER_SUBBOARD_2_PIN, OUTPUT); + // digitalWrite(POWER_SUBBOARD_2_PIN, LOW); + pinMode(POWER_GPS_PIN, OUTPUT); + digitalWrite(POWER_GPS_PIN, LOW); + pinMode(POWER_COMMS_PIN, OUTPUT); + digitalWrite(POWER_COMMS_PIN, LOW); + pinMode(POWER_RADIO_PIN, OUTPUT); + digitalWrite(POWER_RADIO_PIN, LOW); + // pinMode(POWER_IMU_MPU9250_PIN, OUTPUT); + // digitalWrite(POWER_IMU_MPU9250_PIN, LOW); + pinMode(POWER_STROBE1_ON, OUTPUT); + digitalWrite(POWER_STROBE1_ON, LOW); + + // Misc Controlling lines + pinMode(STORAGE_SD1_MATHS_PIN, INPUT); + pinMode(STORAGE_SD2_MATHS_PIN, INPUT); + pinMode(SPARTON_RESET, INPUT); + pinMode(RADIO_CS, INPUT); + pinMode(STORAGE_CS, INPUT); + + // LEDs + pinMode(LED_1, INPUT); + pinMode(LED_2, INPUT); + pinMode(LED_3, INPUT); + + // Weather + pinMode(POWER_WEATHER_18B20_PIN, INPUT); + pinMode(POWER_WEATHER_PIN, INPUT); + break; + case 4000: + console.log(LOG_DEBUG, F("TEST SLEEP Start: sleep only")); + sh3dNodeUtil.sleep(60); + break; + default: + #ifdef WII5_GPS + wii5Gps.powerOff(true); + #endif + #ifdef WII5_IMU_SPARTON + wii5Sparton.powerOff(true); + #endif + #ifdef WII5_COMMS_IRIDIUM + wii5Iridium.powerOff(true); + #endif + #ifdef WII5_RADIO_LORA + wii5RadioLoRa.powerOff(true); + #endif + sh3dNodeUtil.sleep(val); + console.log(LOG_DEBUG, F("TEST SLEEP: Requested=%lu, Actual=%lu, Reasons=%d"), val, sh3dNodeUtil.sleepLastSeconds, sh3dNodeUtil.sleepLastReason); + break; + }; + + console.log(LOG_DEBUG, F("TEST SLEEP Finished: Requested=%lu, Actual=%lu, Reasons=%d"), val, sh3dNodeUtil.sleepLastSeconds, sh3dNodeUtil.sleepLastReason); + break; + + // pulse + case 'O': + if (pin > 1) { + console.printf(F("PULSE pin=%d\r\n"), pin); + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + pulsePin = pin; + } + break; + + // Output + case 'o': + if (pin > 1) { + console.printf(F("OUTPUT pin=%d\r\n"), pin); + pinMode(pin, OUTPUT); + pulsePin = 0; + } + break; + + // Input + case 'i': + if (pin > 1) { + console.printf(F("INPUT pin=%d\r\n"), pin); + pinMode(int(console.getVal()), INPUT); + pulsePin = 0; + } + break; + + // Low + case 'x': + if (val > 100) { + pin = int(val / 100); + pinMode(pin, OUTPUT); + } + if (pin > 1) { + pulsePin = 0; + console.printf(F("# SET LOW pin=%d\r\n"), pin); + digitalWrite(pin, LOW); + delay(10); // Give it 3 millis + console.printf(F("# final output=%d\r\n"), int(digitalRead(pin))); + } + break; + + // HIGH + case 'X': + if (val > 100) { + pin = int(val / 100); + pinMode(pin, OUTPUT); + } + if (pin > 1) { + pulsePin = 0; + console.printf(F("# SET HIGH pin=%d\r\n"), pin); + digitalWrite(pin, HIGH); + delay(10); + console.printf(F("# final output=%d\r\n"), int(digitalRead(pin))); + } + break; + + // NOTE: Special cases for testing that are not in Help + case 'Z': + switch(val) { + case 1: + SerialConsole.println(F("About to do a copy to a direct buffer")); SerialConsole.flush(); + #ifdef WII5_BUFFER_SPARTON + strcpy_P(wii5BufferSparton ,(PGM_P) F("accelrange 3 set drop")); + SerialConsole.println(wii5BufferSparton); SerialConsole.flush(); + #endif + break; + case 2: + SerialConsole.println(F("About to do a copy to a pointer buffer")); SerialConsole.flush(); + #ifdef WII5_BUFFER_SPARTON + strcpy_P(wii5BufferString ,(PGM_P) F("accelrange 4 set drop")); + SerialConsole.println(wii5BufferSparton); SerialConsole.flush(); + #endif + break; + case 3: + SerialConsole.println(F("About to do a use programLine")); SerialConsole.flush(); + #ifdef WII5_IMU_SPARTON + wii5Sparton.programLine(1); + SerialConsole.println(wii5BufferString); SerialConsole.flush(); + #endif + break; + case 10: + SerialConsole.println(F("@File,10000/101/test.log,START:\r\n")); + break; + case 11: + SerialConsole.println(F("@File,10000/101/test.log,37:a,b,c,d\r\n")); + break; + case 12: + SerialConsole.println(F("@File,10000/101/test.log,END:\r\n")); + break; + case 20: + SerialConsole.println(F("@File,10000/101/test.log,START:\r\n")); + break; + case 21: + SerialConsole.println(F("@File,10000/101/test.log,37:a,b,c,d\r\n")); + break; + case 22: + SerialConsole.println(F("@File,10000/101/test.log,END:\r\n")); + break; + case 100: + break; + case 666: + // Doing a reset !!!! + SerialConsole.println(F("RESET NOW!")); + SerialConsole.flush(); + sh3dNodeUtil.reset(); + break; + + case 101: + { + uint32_t testIt = 0; + uint32_t until = 40960; + while (1) { + if (testIt >= until) { + console.log(LOG_DEBUG, F("Finished - TestIt %lu of Until %lu"), testIt, until); + break; + } + testIt++; + if ( (testIt % 1000) == 0) { + console.log(LOG_DEBUG, F("Working - TestIt %lu of Until %lu"), testIt, until); + } + } + } + + case 210: + console.log(LOG_DEBUG, F("Flag: Disablebeep False")); + wii5Config.setFlag(WII5FLAGS_DISABLEBEEP, false); + break; + + case 211: + console.log(LOG_DEBUG, F("Flag: Disablebeep true")); + wii5Config.setFlag(WII5FLAGS_DISABLEBEEP, true); + break; + + + default: + break; + } + break; + + case 'W': + SerialConsole.print(F("WARNING - Disabled external WDT reset pin - runCount=")); + SerialConsole.println(sh3dNodeConfig.getRunCount()); + wii5Controller.tempDisableWDT = true; + break; + + case '%': + // Show other sizes too + console.log(LOG_DEBUG, F("SIZE: WII5 Modules")); + console.log(LOG_DEBUG, F("Size of Battery = %d"), sizeof(wii5Battery)); + console.log(LOG_DEBUG, F("Size of Commands = %d"), sizeof(wii5Commands)); + console.log(LOG_DEBUG, F("Size of Config = %d"), sizeof(wii5Config)); + console.log(LOG_DEBUG, F("Size of Controller = %d"), sizeof(wii5Controller)); + console.log(LOG_DEBUG, F("Size of Display = %d"), sizeof(wii5Display)); + #ifdef WII5_GPS + console.log(LOG_DEBUG, F("Size of Gps = %d"), sizeof(wii5Gps)); + #endif + console.log(LOG_DEBUG, F("Size of Help = %d"), sizeof(wii5Help)); + #ifdef WII5_COMMS_IRIDIUM + console.log(LOG_DEBUG, F("Size of Iridium = %d"), sizeof(wii5Iridium)); + #endif + console.log(LOG_DEBUG, F("Size of Maths = %d"), sizeof(wii5Maths)); + #ifdef WII5_RTC + console.log(LOG_DEBUG, F("Size of RTC = %d"), sizeof(wii5RTC)); + #endif + #ifdef WII5_RADIO_LORA + console.log(LOG_DEBUG, F("Size of RadioLoRa = %d"), sizeof(wii5RadioLoRa)); + #endif + #ifdef WII5_DEBUG_SERIAL + console.log(LOG_DEBUG, F("Size of SerialDebug = %d"), sizeof(SerialDebug)); + #endif + #ifdef WII5_MATHS_SERIAL + console.log(LOG_DEBUG, F("Size of SerialMaths = %d"), sizeof(SerialMaths)); + #endif + #ifdef WII5_IMU_SPARTON + console.log(LOG_DEBUG, F("Size of Sparton = %d"), sizeof(wii5Sparton)); + #endif + console.log(LOG_DEBUG, F("Size of Strings = %d"), sizeof(wii5Strings)); + console.log(LOG_DEBUG, F("Size of Weather_18B20 = %d"), sizeof(wii5Weather_18B20)); + console.log(LOG_DEBUG, F("SIZE: Sh3d Modules")); + console.log(LOG_DEBUG, F("Size of Console = %d"), sizeof(console)); + #ifdef WII5_RADIO_LORA + console.log(LOG_DEBUG, F("Size of Radio = %d"), sizeof(sh3dNodeRadio)); + console.log(LOG_DEBUG, F("Size of Network = %d"), sizeof(sh3dNodeNetwork)); + #endif + console.log(LOG_DEBUG, F("Size of Config` = %d"), sizeof(sh3dNodeConfig)); + console.log(LOG_DEBUG, F("Size of Util = %d"), sizeof(sh3dNodeUtil)); + console.log(LOG_DEBUG, F("Size of IO = %d"), sizeof(sh3dNodeIO)); + console.log(LOG_DEBUG, F("SIZE: Other")); + // Radio itself, SD Card etc etc console.log(LOG_DEBUG, F("Size of IO = %d"), sizeof(sh3dNodeIO)); + break; + + case '*': + wii5Display.dumpPins(); + break; + + // Radio + case 'r': + // sh3dNodeNetwork.sendEcho(); + #ifdef WII5_RADIO_LORA + switch(val) { + case 0: + console.printf(F("Radio: Stop")); + wii5RadioLoRa.stop(); + break; + case 3: + console.log(LOG_DEBUG, F("Radio: Sending echo")); + sh3dNodeNetwork.sendEcho(); + break; + case 100: + console.log(LOG_DEBUG, F("Radio: Disable Passthrough")); + wii5RadioLoRa.setPassthrough(0); + break; + case 101: + console.log(LOG_DEBUG, F("Radio: Enable Passthrough")); + wii5RadioLoRa.setPassthrough(1); + break; + case 110: + console.log(LOG_DEBUG, F("Radio: Disable Debug")); + wii5RadioLoRa.setDebug(0); + break; + case 111: + console.log(LOG_DEBUG, F("Radio: Enable Debug")); + wii5RadioLoRa.setDebug(1); + break; + default: + break; + } + #endif + break; + + // STORAGE + case 'm': + + console.log(LOG_DEBUG, F("SOTRAGE: Val=%ul"), val); + switch(val) { + case 0: + console.log(LOG_DEBUG, F("SD: Off")); + wii5Controller.setSDOff(); + break; + case 1: + console.log(LOG_DEBUG, F("SD: 1")); + wii5Controller.setSD1(); + break; + case 2: + console.log(LOG_DEBUG, F("SD: 2")); + wii5Controller.setSD2(); + break; + + case 5: + if (sdBlock.cardOpen(wii5Controller.getSD())) { + console.log(LOG_DEBUG, F("SD: cardOpen - Success")); + } + else { + console.log(LOG_DEBUG, F("SD: cardOpen - Failure")); + } + break; + + case 6: + if (sdBlock.cardClose()) { + console.log(LOG_DEBUG, F("SD: cardClose - Success")); + } + else { + console.log(LOG_DEBUG, F("SD: cardClose - Failure")); + } + break; + + case 9: + console.log(LOG_DEBUG, F("SD: metadataList (last 100) - start")); + sdBlock.metadataList(0, 100); + console.log(LOG_DEBUG, F("SD: metadataList (last 100) - end")); + break; + + case 10: + if (sdBlock.cardIsOpen()) + sdBlock.cardClose(); + wii5Controller.setSDOff(); + console.log(LOG_DEBUG, F("SD: close card and disable")); + break; + + case 11: + console.log(LOG_DEBUG, F("SD: 1 - cardOpen...")); + wii5Controller.setSD1(); + delay(1500); + if (sdBlock.cardOpen(wii5Controller.getSD())) { + console.log(LOG_DEBUG, F("SD: 1 - cardOpen - ON and Success")); + } + else { + console.log(LOG_DEBUG, F("SD: 1 - cardOpen - Failure")); + } + break; + + case 12: + console.log(LOG_DEBUG, F("SD: 2 - cardOpen...")); + wii5Controller.setSD2(); + delay(1500); + if (sdBlock.cardOpen(wii5Controller.getSD())) { + console.log(LOG_DEBUG, F("SD: 2 - cardOpen - ON and Success")); + } + else { + console.log(LOG_DEBUG, F("SD: 2 - cardOpen - Failure")); + } + break; + + case 99: + console.log(LOG_DEBUG, F("SD - Performance test block - data is not saved to allocation table")); + console.flush(); + if (!sdBlock.cardIsOpen()) + sdBlock.cardOpen(wii5Controller.getSD()); + sdBlock.dataOpen(501); + + captureWriteMax = 0; + captureWriteMin = 1000; + captureWriteOver = 0; + + // Start with 1000 blocks + console.log(LOG_DEBUG, F("Starting 1000 blocks")); + console.flush(); + for(uint32_t b = 0; b < 1000; b++) { + if ((b % 10) == 0) { + console.log(LOG_DEBUG, F("Record %lu of 1000"), b); + } + + sdBlock.dataPrepare(); + spartonData = (WII5_DATA_SpartonBinary*)&sdBlock.block->data; + for(uint8_t r = 0; r < 15; r++) { + spartonData->accel_x = 1; + spartonData->accel_y = b; + spartonData->accel_z = r; + spartonData->pose_x = 1; + spartonData->pose_y = b; + spartonData->pose_z = r; + spartonData++; + } + + captureWriteTime = 0; + sdBlock.dataWrite(); + captureWriteTemp = captureWriteTime; + + if (captureWriteTemp > captureWriteMax) + captureWriteMax = captureWriteTemp; + if (captureWriteTemp < captureWriteMin) + captureWriteMin = captureWriteTemp; + if (captureWriteTemp > 5) + captureWriteOver++; + } + + sdBlock.dataOpen(501); + + wii5Display.atStatsSend( + F("ManualTestSparton"), + F("writeMax=%lu,writeMin=%lu,writeOver=%lu"), + captureWriteMax, captureWriteMin, captureWriteOver + ); + console.flush(); + break; + + case 100: + // TODO Causing crash ! + if (!sdBlock.cardIsOpen()) { + console.printf(F("# SD Card needs to be open again... Getting current\r\n")); + sdBlock.cardOpen(wii5Controller.getSD()); + } + if (sdBlock.singleRead(sdPrev)) { + console.printf(F("# singleRead later data=%s\r\n"), + sdBlock.block->data + ); + sdPrev++; + } + else { + console.printf(F("# SingleRead - no more available or failed\r\n")); + } + break; + + case 101: + console.log(LOG_DEBUG, F("SD - Writing a single entry")); + sdPrev = 0; + sdBlock.debug = true; + if (!sdBlock.cardIsOpen()) + sdBlock.cardOpen(wii5Controller.getSD()); + sdBlock.singlePrepare(); + sprintf_P(sdBlock.block->data, PSTR("My test hello world 1234567890 Wow XXX YYY ZZZ - %lu"), millis()); + if (sdBlock.singleWrite()) + console.printf(F("# Success write\r\n")); + else + console.printf(F("# Failed write\r\n")); + console.printf(F("# SAVED Single data=%s\r\n"), + sdBlock.block->data + ); + if (sdBlock.singleRead(sdPrev)) { + console.printf(F("# singleRead after data=%s\r\n"), + sdBlock.block->data + ); + } + else { + console.printf(F("# Failed to reload\r\n"), + sdBlock.block->data + ); + } + sdBlock.cardClose(); + // sdBlock.allocationUpdate(); + break; + + case 200: + console.log(LOG_INFO, F("SD - Reading last set of capture data")); + sdPrev = 0; + if (!sdBlock.cardIsOpen()) + sdBlock.cardOpen(wii5Controller.getSD()); + + // Read last entry + sdBlock.metadataRead(0); + WII5MetaDataObject* local_metadata = (WII5MetaDataObject*)(sdBlock.metadata->data); + // TODO Display MetaData + + console.log(LOG_INFO, F("SD metadata read deviceId=%lu, recordId=%lu"), + sdBlock.metadata->deviceId, sdBlock.metadata->recordId + ); + + // TODO Read Blocks and Display Data + for(uint32_t b = 0; b < 5; b++) { + if (sdBlock.read(sdBlock.metadata->dataBlockStart + b)) { + console.log(LOG_INFO, F("SD - block = %d"), int(b)); + spartonData = (WII5_DATA_SpartonBinary*)&sdBlock.block->data; + for(uint8_t r = 0; r < 15; r++) { + console.log(LOG_INFO, F("SD - record z=%d"), + int(spartonData->accel_z) + ); + /* + spartonData->accel_x = 1; + spartonData->accel_y = b; + spartonData->accel_z = r; + spartonData->pose_x = 1; + spartonData->pose_y = b; + spartonData->pose_z = r; + */ + spartonData++; + } + } + else { + console.log(LOG_INFO, F("SD - failed to read block = %ul"), b); + } + } + console.log(LOG_INFO, F("SD - Finsihed, closing card")); + sdBlock.cardClose(); + break; + + case 333: + console.log(LOG_DEBUG, F("SD - Writing a sample set of capture data")); + sdPrev = 0; + if (!sdBlock.cardIsOpen()) + sdBlock.cardOpen(wii5Controller.getSD()); + sdBlock.dataOpen(100); + + for(uint8_t b = 0; b < 5; b++) { + console.log(LOG_DEBUG, F("SD - block = %d"), int(b)); + sdBlock.dataPrepare(); + spartonData = (WII5_DATA_SpartonBinary*)&sdBlock.block->data; + for(uint8_t r = 0; r < 15; r++) { + console.log(LOG_DEBUG, F("SD - record = %d"), int(r)); + spartonData->accel_x = 1; + spartonData->accel_y = b; + spartonData->accel_z = r; + spartonData->pose_x = 1; + spartonData->pose_y = b; + spartonData->pose_z = r; + spartonData++; + } + sdBlock.dataWrite(); + } + + console.log(LOG_DEBUG, F("SD - dataClose")); + sdBlock.dataClose(false); + + // Create empty single entry for results. + sdBlock.dataOpen(wii5Config.getRecordCount()); + sdBlock.dataPrepare(); + sdBlock.dataWrite(); + sdBlock.dataClose(true); + + console.log(LOG_DEBUG, F("SD - metadataPrepare, updateMetadata, metadataWrite")); + sdBlock.metadataPrepare(); + wii5Display.updateMetadata(sdBlock.metadata->data); + sdBlock.metadataWrite(); + console.log(LOG_DEBUG, F("SD - cardClose")); + sdBlock.cardClose(); + break; + + + case 77701: + wii5Controller.setSD1(); + if (sdBlock.cardOpen(1, true)) { + console.log(LOG_DEBUG, F("SD: 1 - cardFormat - Open")); + if (sdBlock.cardClose()) + console.log(LOG_DEBUG, F("SD: 1 - cardFormat - Complete")); + else + console.log(LOG_DEBUG, F("SD: 1 - cardFormat - Failed format")); + } + else { + console.log(LOG_DEBUG, F("SD: 1 - cardFormat - Failure")); + } + break; + + case 77702: + wii5Controller.setSD2(); + if (sdBlock.cardOpen(2, true)) { + console.log(LOG_DEBUG, F("SD: 2 - cardFormat - Open")); + if (sdBlock.cardClose()) + console.log(LOG_DEBUG, F("SD: 2 - cardFormat - Complete")); + else + console.log(LOG_DEBUG, F("SD: 2 - cardFormat - Failed format")); + } + else { + console.log(LOG_DEBUG, F("SD: 2 - cardFormat - Failure")); + } + break; + + } + break; + + // GPS + #ifdef WII5_GPS + case 'g': + switch(val) { + case 0: + console.log(LOG_DEBUG, F("GPS: Stop")); + wii5Gps.off(); + break; + case 1: + console.log(LOG_DEBUG, F("GPS: Start")); + wii5Gps.on(); + break; + case 2: + console.log(LOG_DEBUG, F("GPS: Start auto position")); + wii5Gps.autoPos(); + waitGPS = true; + break; + case 3: + console.log(LOG_DEBUG, F("GPS: Start auto accurate")); + wii5Gps.autoAccurate(); + waitGPS = true; + break; + case 66: + console.log(LOG_DEBUG, F("GPS: REPL (... to exit)")); + break; + case 50: + console.log(LOG_DEBUG, F("GPS: Dump")); + wii5Gps.dump(); + break; + case 100: + console.log(LOG_DEBUG, F("GPS: Disable Passthrough")); + wii5Gps.setPassthrough(0); + break; + case 101: + console.log(LOG_DEBUG, F("GPS: Enable Passthrough")); + wii5Gps.setPassthrough(1); + break; + case 110: + console.log(LOG_DEBUG, F("GPS: Disable Debug")); + wii5Gps.setDebug(0); + break; + case 111: + console.log(LOG_DEBUG, F("GPS: Enable Debug")); + wii5Gps.setDebug(1); + break; + default: + break; + }; + enableGps = (val > 0); + break; + #endif + + // Sparton + #ifdef WII5_IMU_SPARTON + case 'p': + switch(val) { + case 0: + console.log(LOG_DEBUG, F("SpartoL stop")); + wii5Sparton.stop(); + break; + case 1: + console.log(LOG_DEBUG, F("Sparton start")); + wii5Sparton.start(); + break; + case 66: + console.log(LOG_DEBUG, F("SPARTON: REPL (... to exit)")); + wii5Sparton.repl(); + break; + case 67: + console.log(LOG_DEBUG, F("SPARTON: info!")); + wii5Sparton.info(); + break; + case 68: + console.log(LOG_DEBUG, F("SPARTON: simple 1Hz passthrough text!")); + wii5Sparton.setHz(1); + wii5Sparton.setPassthrough(1); + wii5Sparton.setBinary(false); + wii5Sparton.setRecords(1000); + wii5Sparton.start(); + break; + case 100: + console.log(LOG_DEBUG, F("Sparton: Disable Passthrough")); + wii5Sparton.setPassthrough(0); + break; + case 101: + console.log(LOG_DEBUG, F("Sparton: Enable Passthrough")); + wii5Sparton.setPassthrough(1); + break; + case 110: + console.log(LOG_DEBUG, F("Sparton: Disable Debug")); + wii5Sparton.setDebug(0); + break; + case 111: + console.log(LOG_DEBUG, F("Sparton: Enable Debug")); + wii5Sparton.setDebug(1); + break; + case 120: + console.log(LOG_DEBUG, F("Sparton: Disable Capture")); + wii5Sparton.setCapture(0); + break; + case 121: + console.log(LOG_DEBUG, F("Sparton: Enable Capture")); + wii5Sparton.setCapture(1); + break; + case 130: + console.log(LOG_DEBUG, F("Sparton: 8Hz")); + wii5Sparton.setHz(8); + break; + case 131: + console.log(LOG_DEBUG, F("Sparton: 64Hz")); + wii5Sparton.setHz(64); + break; + case 140: + console.log(LOG_DEBUG, F("Sparton: NOT binary")); + wii5Sparton.setBinary(false); + break; + case 141: + console.log(LOG_DEBUG, F("Sparton: Binary")); + wii5Sparton.setBinary(true); + break; + case 200: + console.log(LOG_DEBUG, F("Sparton: 8hz 200 records automatic")); + wii5Sparton.automatic(8, 200, false); + break; + case 201: + console.log(LOG_DEBUG, F("Sparton: 8hz 1000 records automatic")); + wii5Sparton.automatic(8, 1000, false); + break; + case 202: + console.log(LOG_DEBUG, F("Sparton: 8hz 5120 records automatic")); + wii5Sparton.automatic(8, 5120, false); + break; + case 210: + console.log(LOG_DEBUG, F("Sparton: 8hz capture + 200 records automatic")); + wii5Sparton.automatic(8, 200, true); + break; + case 211: + console.log(LOG_DEBUG, F("Sparton: 8hz capture + 1000 records automatic")); + wii5Sparton.automatic(8, 1000, true); + break; + case 212: + console.log(LOG_DEBUG, F("Sparton: 8hz capture + 5120 records automatic")); + wii5Sparton.automatic(8, 5120, true); + break; + case 300: + console.log(LOG_DEBUG, F("Sparton: 64hz 200 records automatic")); + wii5Sparton.automatic(64, 200, false); + break; + case 301: + console.log(LOG_DEBUG, F("Sparton: 64hz 1000 records automatic")); + wii5Sparton.automatic(64, 1000, false); + break; + case 302: + console.log(LOG_DEBUG, F("Sparton: 64hz 5120 records automatic")); + wii5Sparton.automatic(64, 5120, false); + break; + case 310: + console.log(LOG_DEBUG, F("Sparton: 64hz capture + 200 records automatic")); + wii5Sparton.automatic(64, 200, true); + break; + case 311: + console.log(LOG_DEBUG, F("Sparton: 64hz capture + 5000 records automatic")); + wii5Sparton.automatic(64, 5000, true); + /* + console.log(LOG_DEBUG, F("Sparton: TIGHT LOOP UNTIL COMPLETE - you will see serial output only, no control.")); + console.flush(); + while (!wii5Sparton.captureFinished()) { + wii5Sparton.loop(); + } + console.log(LOG_DEBUG, F("Sparton: END TIGHT LOOP - back to normal")); + console.flush(); + */ + break; + case 312: + console.log(LOG_DEBUG, F("Sparton: 64hz capture + 40960 records automatic")); + wii5Sparton.automatic(64, 40960, true); + /* + console.log(LOG_DEBUG, F("Sparton: TIGHT LOOP UNTIL COMPLETE - you will see serial output only, no control.")); + console.flush(); + while (!wii5Sparton.captureFinished()) { + wii5Sparton.loop(); + } + console.log(LOG_DEBUG, F("Sparton: END TIGHT LOOP - back to normal")); + console.flush(); + */ + + break; + + default: + if (val >= 1000) { + val = val - 1000; + console.log(LOG_DEBUG, F("Sparton: Set records = %lu"), val); + wii5Sparton.setRecords(val); + } + break; + }; + enableSparton = (val > 0); + break; + #endif + + // Iridium + #ifdef WII5_COMMS_IRIDIUM + case 'q': + switch(val) { + case 0: + console.log(LOG_DEBUG, F("Iridium: stop")); + wii5Iridium.stop(); + break; + case 1: + console.log(LOG_DEBUG, F("Iridium: start")); + wii5Iridium.start(); + break; + case 2: + console.log(LOG_DEBUG, F("Iridium: sendText")); + wii5Iridium.setText(F("ExampleNo1")); + wii5Iridium.requestSendText(); + break; + case 3: + console.log(LOG_DEBUG, F("Iridium: sendText")); + wii5Iridium.setText(F("Longer example text")); + wii5Iridium.requestSendText(); + break; + case 4: + console.log(LOG_DEBUG, F("Iridium: sendBin")); + // sprintf_P(wii5BinaryIridium, PSTR("TestString:devid=%lu,rec=%lu"), wii5Config.getDeviceId(), wii5Config.getRecordCount()); + sprintf_P(wii5BinaryIridium, PSTR("x1y2z3"), wii5Config.getDeviceId(), wii5Config.getRecordCount()); + wii5Iridium.setBinSize(strlen(wii5BinaryIridium)); + wii5Iridium.requestSendBinary(); + break; + case 41: + console.log(LOG_DEBUG, F("Iridium: sendBin")); + // sprintf_P(wii5BinaryIridium, PSTR("TestString:devid=%lu,rec=%lu"), wii5Config.getDeviceId(), wii5Config.getRecordCount()); + sprintf_P(wii5BinaryIridium, PSTR("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), wii5Config.getDeviceId(), wii5Config.getRecordCount()); + wii5Iridium.setBinSize(strlen(wii5BinaryIridium)); + wii5Iridium.requestSendBinary(); + break; + case 42: + console.log(LOG_DEBUG, F("Iridium: sendBin")); + // sprintf_P(wii5BinaryIridium, PSTR("TestString:devid=%lu,rec=%lu"), wii5Config.getDeviceId(), wii5Config.getRecordCount()); + sprintf_P(wii5BinaryIridium, PSTR("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), wii5Config.getDeviceId(), wii5Config.getRecordCount()); + wii5Iridium.setBinSize(strlen(wii5BinaryIridium)); + wii5BinaryIridium[5] = '\0'; + wii5BinaryIridium[10] = '\0'; + wii5Iridium.requestSendBinary(); + break; + case 5: + console.log(LOG_DEBUG, F("Iridium: sendBin (BinData prpeare)")); + wii5BinData.createData(1, wii5BinaryIridium, 340, 0); + wii5BinData.setData(1, wii5BinaryIridium, 340); // TODO Hard coded + wii5Iridium.setBinSize(wii5BinData.getSize(1)); + wii5Iridium.requestSendBinary(); + break; + case 66: + console.log(LOG_DEBUG, F("Iridium: REPL (... to exit)")); + wii5Iridium.repl(); + break; + case 80: + console.log(LOG_DEBUG, F("Iridium: request firmware et al")); + wii5Iridium.requestFirmware(); + break; + case 100: + console.log(LOG_DEBUG, F("Iridium: Disable Passthrough")); + wii5Iridium.setPassthrough(0); + break; + case 101: + console.log(LOG_DEBUG, F("Iridium: Enable Passthrough")); + wii5Iridium.setPassthrough(1); + break; + case 110: + console.log(LOG_DEBUG, F("Iridium: Disable Debug")); + wii5Iridium.setDebug(0); + break; + case 111: + console.log(LOG_DEBUG, F("Iridium: Enable Debug")); + wii5Iridium.setDebug(1); + break; + + default: + break; + }; + enableIridium = (val > 0); + break; + #endif + + // Weather + case 'w': + switch(val) { + case 0: + console.log(LOG_DEBUG, F("No support for Weather Off - try 'w1;' for single read'"), 1); + break; + case 1: + console.log(LOG_DEBUG, F("Get Temperature...")); + wii5Weather_18B20.temperatureRead(); + break; + default: + break; + }; + break; + + // RTC + #ifdef WII5_RTC + case 'R': + switch(val) { + case 66: + console.log(LOG_DEBUG, F("RTC test mode ...")); + wii5RTC.test(); + break; + case 100: + { + time_t t = now(); + console.printf(F("# DateTime: Current Time=")); + console.printDateTime(t); + console.printNewLine(); + DateTime testdt(t); + console.printf(F("# DateTime: Input=%lu, Output=%lu\r\n"), + // Original + t, + // Round Trip - should be identical + testdt.unixtime() + ); + console.printf(F("# DateTime: Parts y=%d n=%d d=%d h=%d m=%d s=%d\r\n"), + testdt.year(), testdt.month(), testdt.day(), + testdt.hour(), testdt.minute(), testdt.second() + ); + } + break; + case 101: + { + float tin = wii5RTC.getTemperature(); + console.printf(F("# RTC Temperature = %d\r\n"), int(tin * 100)); + } + break; + + default: + console.log(LOG_DEBUG, F("RTC setting time=%lu"), val); + if (val > 1000) { + setTime(val); + } + break; + }; + break; + #endif + + // Battery + case 'v': + switch(val) { + case 0: + console.log(LOG_DEBUG, F("Battery Voltage: OFF")); + wii5Battery.stop(); + break; + case 1: + console.log(LOG_DEBUG, F("Battery Voltage: ON")); + wii5Battery.start(); + break; + default: + break; + }; + enableBattery = (val > 0); + break; + + // Power Management (sort of) + case 'P': + console.log(LOG_DEBUG, F("POWER MANAGEMENT: (+0 off, +1 to turn on)")); + switch(val) { + case 0: + break; + case 20: + wii5Battery.powerOff(); + break; + case 21: + wii5Battery.powerOn(); + break; + #ifdef WII5_GPS + case 30: + wii5Gps.powerOff(); + break; + case 31: + wii5Gps.powerOn(); + break; + #endif + #ifdef WII5_COMMS_IRIDIUM + case 40: + wii5Iridium.powerOff(); + break; + case 41: + wii5Iridium.powerOn(); + break; + #endif + case 50: + #ifdef WII5_LORA_RADIO + wii5RadioLoRa.powerOff(); + #endif + break; + case 51: + #ifdef WII5_RADIO_LORA + wii5RadioLoRa.powerOn(); + #endif + break; + #ifdef WII5_IMU_SPARTON + case 60: + wii5Sparton.powerOff(); + break; + case 61: + wii5Sparton.powerOn(); + break; + #endif + case 70: + wii5Maths.powerOff(); + break; + case 71: + wii5Maths.powerOn(); + break; + }; + + console.log(LOG_DEBUG, F("20 Battery = %d"), wii5Battery.powerStatus() ? 1 : 0); + #ifdef WII5_GPS + console.log(LOG_DEBUG, F("30 GPS = %d"), wii5Gps.powerStatus() ? 1 : 0); + #endif + #ifdef WII5_COMMS_IRIDIUM + console.log(LOG_DEBUG, F("40 Iridium = %d"), wii5Iridium.powerStatus() ? 1 : 0); + #endif + #ifdef WII5_RADIO_LORA + console.log(LOG_DEBUG, F("50 RadioLoRa = %d"), wii5RadioLoRa.powerStatus() ? 1 : 0); + #endif + #ifdef WII5_IMU_SPARTON + console.log(LOG_DEBUG, F("60 Sparton = %d"), wii5Sparton.powerStatus() ? 1 : 0); + #endif + // console.log(LOG_DEBUG, F("70 Maths = %d"), wii5Maths.powerStatus() ? 1 : 0); + // storage - in protected ATM - console.log(LOG_DEBUG, F("100 Battery = %d"), wii5Battery.powerStatus() ? 1 : 0); + break; + + case 'M': + console.log(LOG_DEBUG, F("Maths CPU Control")); + switch(val) { + case 0: + wii5Maths.stop(); + console.log(LOG_DEBUG, F(" - stop called")); + break; + case 1: + wii5Maths.start(WII5MATHSMODE_COMMS); + console.log(LOG_DEBUG, F(" - start called")); + break; + case 100: + console.log(LOG_DEBUG, F("Maths: Disable keep on")); + wii5Maths.setMathsUntil(0); + break; + case 101: + console.log(LOG_DEBUG, F("Maths: Keep on until the year 2023")); + wii5Maths.setMathsUntil(1700000000); // Like 2023 + break; + + case 110: + console.log(LOG_DEBUG, F("Maths: Disable Debug")); + wii5Maths.setDebug(0); + break; + case 111: + console.log(LOG_DEBUG, F("Maths: Enable Debug")); + wii5Maths.setDebug(1); + break; + }; + break; + + case 'n': + console.log(LOG_DEBUG, F("Communications: val=%lu"), val); + switch(val) { + case 0: + console.log(LOG_DEBUG, F("Communications: Idle")); + wii5Communications.stop(); + break; + + case 100: + console.log(LOG_DEBUG, F("Communications: Simple")); + wii5Communications.setSimpleMode(); + wii5Communications.start(); + break; + + case 101: + console.log(LOG_DEBUG, F("Communications: Simple")); + wii5Communications.setSimpleMode(); + wii5Communications.sendBinModeType(1, 1); + wii5Communications.start(); + break; + + case 200: + console.log(LOG_DEBUG, F("Communications: Idle")); + wii5Communications.setAutoMode(); + wii5Communications.start(); + break; + + case 201: + console.log(LOG_DEBUG, F("Communications: Idle")); + wii5Communications.signalSD(); + wii5Communications.start(); + break; + + case 3: + console.log(LOG_DEBUG, F("Communications: Auto but no transmit")); + + wii5Controller.setSD1(); + delay(1500); + if (sdBlock.cardOpen(wii5Controller.getSD())) { + console.log(LOG_DEBUG, F("SD: 1 - cardOpen - ON and Success")); + } + else { + console.log(LOG_DEBUG, F("SD: 1 - cardOpen - Failure")); + } + + wii5Communications.setAutoMode(); + wii5Communications.signalSD(); + wii5Communications.start(); + wii5Communications.setTESTING(); + break; + + default: + break; + }; + break; + + + default: + // return false; + break; + } + } +} + +WII5ModeManualTest wii5ModeManualTest; diff --git a/WII5ModeManualTest.h b/WII5ModeManualTest.h new file mode 100644 index 0000000..872b973 --- /dev/null +++ b/WII5ModeManualTest.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModeManualTest.h + * @brief Manual test mode: operator-driven hardware exercise via the console. + */ + +#ifndef WII5ModeManualTest_h +#define WII5ModeManualTest_h + +#include +#include +#include + +/** + * @brief Manual test mode: operator-driven hardware exercise. + * + * Provides a console REPL where the operator types single-letter + * commands ("X;", "G;", etc.) to exercise individual subsystems + * (LEDs, buttons, GPS, Iridium, Sparton, SD cards, etc.). Used in the + * lab and during pre-deployment checkout. + */ +class WII5ModeManualTest : public WII5Mode { + public: + WII5ModeManualTest() {} + /** @brief Reset internal counters. */ + void reset(); + /** @brief One-time bring-up. */ + void begin(); + /** @brief Tick: read console, dispatch one-shot test commands. */ + void loop(); + bool enableMetadata; + bool enableIridium; + bool enableGps; + bool enableSparton; + bool enableBattery; + bool waitGPS; + + // virtual void metadataPrint(File* fh); + + protected: + elapsedMillis wait; + uint8_t pulsePin; + elapsedMillis pulseWait; + + // Lots of improvement... + +}; + +extern WII5ModeManualTest wii5ModeManualTest; + +#endif diff --git a/WII5ModePosition.cpp b/WII5ModePosition.cpp new file mode 100644 index 0000000..152842b --- /dev/null +++ b/WII5ModePosition.cpp @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModePosition.cpp + * @brief Position mode: periodic GPS + Iridium telemetry only (low power). + */ + +/* + +Position mode - a simple repeatign mode that will capture position data and send it. + +*/ + +#include +#include +#include + +void WII5ModePosition::reset() { + step = WII5POSITION_START; + wait = 0; +} + +void WII5ModePosition::begin() { +} + +void WII5ModePosition::start() { + step = WII5POSITION_STROBE; wait = 0; +} + +void WII5ModePosition::stop() { + step = WII5POSITION_END; wait = 0; +} + +void WII5ModePosition::loop() { + bool first = (lastStep != step); + lastStep = step; + + switch (step) { + case WII5POSITION_START: + if (first) { + // TODO This seems bad, perhaps only if next is 10 minutes? + // First time in, turn off devices we don't need. + // Can we turn off Maths. + sh3dNodeIO.led1Set( LED_SHORT ); + wii5Controller.setSDOff(); + wii5Gps.off(); + wii5Iridium.stop(); + wii5Sparton.stop(); + wii5Controller.shared5Off(); + + console.log(LOG_INFO, F("Position: Waiting for %lu minutes"), + wii5Display.minutesUntilNext(wii5Config.getPositionPeriod()) + ); + } + else if ((wii5Gps.sinceOnLast > (WII5TIME_1HOUR * 1000)) && (!wii5Gps.isTimeValid())) { + console.log(LOG_ERROR, F("Position: Internal clock / time is not valid, turn on GPS")); + step = WII5POSITION_TIME; wait = 0; + } + else if (wait > (WII5TIME_1DAY * 1000)) { + console.log(LOG_ERROR, F("Position: Timeout - over 24 hours, moving on to do a location and data send")); + step = WII5POSITION_STROBE; wait = 0; + } + else { + // TODO - reduce this complicated task ? Lots of maths, being run at full speed + minutes = (hour() * 60) + minute(); + if ( (minutes % wii5Config.getPositionPeriod()) == 0) { + console.log(LOG_INFO, F("Position: Starting minutes match period min=%lu, period=%lu"), minutes, wii5Config.getPositionPeriod()); + step = WII5POSITION_STROBE; wait = 0; + } + } + // TODO Sleep ? + break; + + case WII5POSITION_STROBE: + // TODO - Manage the strobes + // console.log(LOG_DEBUG, F("Position: strobe -> gps_start (skipping)")); + if (first) { + wii5Config.updateRecordCount(); + console.log(LOG_DEBUG, F("Position: New recordcount=%lu"), wii5Config.getRecordCount()); + } + step = WII5POSITION_GPS_START; wait = 0; + break; + + case WII5POSITION_TIME: + if (first) { + wii5Gps.autoTime(); + } + else if (wii5Gps.ready()) { + step = WII5POSITION_START; wait = 0; + } + else if (wait > 300000) { + step = WII5POSITION_START; wait = 0; + } + break; + + case WII5POSITION_GPS_START: + // Ask the GPS for accurate data + console.log(LOG_DEBUG, F("Position: Starting GPS, Battery, Temperature")); + #ifdef WII5_GPS + wii5Gps.autoAccurate(); + #endif + wii5Battery.start(); + wii5Weather_18B20.temperatureRead(); + step = WII5POSITION_GPS_WAIT; wait = 0; + break; + + case WII5POSITION_GPS_WAIT: + if (first) { + displayWait = 0; + } + else if (wii5Gps.ready()) { + if (wii5Gps.isError()) { + console.log(LOG_ERROR, F("Position: GPS Finished with ERROR")); + } + wii5Gps.off(); + step = WII5POSITION_IRIDIUM_WAIT; wait = 0; + } + else if (wait > 300000) { + console.log(LOG_ERROR, F("Position: GPS Timed out after 300 seconds")); + step = WII5POSITION_IRIDIUM_WAIT; wait = 0; + } + else if (displayWait > 10000) { + console.log(LOG_INFO, F("Position: Waiting for GPS Lock - %lu seconds"), (wait / 1000)); + displayWait = 0; + } + break; + + case WII5POSITION_IRIDIUM_WAIT: + if (first) { + console.log(LOG_INFO, F("Position: Communications start")); + wii5Communications.setSimpleMode(); + // Increment updateRecordCount for each save to SD OR send to Iridium + wii5Communications.sendBinModeType(wii5Config.getPositionBinaryType(), wii5Config.getRecordCount()); + wii5Communications.start(); + } + else if (!wii5Communications.isRunning()) { + console.log(LOG_INFO, F("Position: Communications end")); + step = WII5POSITION_END; wait = 0; + } + // TODO 10 minutes ok + else if (wait > 600000) { + console.log(LOG_FATAL, F("Position: Communications timeout")); + step = WII5POSITION_END; wait = 0; + } + break; + + case WII5POSITION_END: + if (first) { + console.log(LOG_DEBUG, F("Position: End")); + } + wii5Communications.stop(); + wii5Gps.off(); + + // Stop everything + wii5Controller.setSDOff(); + wii5Sparton.stop(); + wii5Controller.shared5Off(); + + step = WII5POSITION_START; wait = 0; + break; + + default: + console.log(LOG_FATAL, F("POSITION: Default step... step=%d"), step); + step = WII5POSITION_START; + } +} + +WII5ModePosition wii5ModePosition; diff --git a/WII5ModePosition.h b/WII5ModePosition.h new file mode 100644 index 0000000..bfe161b --- /dev/null +++ b/WII5ModePosition.h @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModePosition.h + * @brief Declares WII5ModeLowBattery (low-battery mode). + * + * @note The WII5ModePosition.h / WII5ModeLowBattery.h header pair are + * swapped relative to their filenames in this codebase: this file + * (WII5ModePosition.h) declares class WII5ModeLowBattery, and + * WII5ModeLowBattery.h declares class WII5ModePosition. The corresponding + * .cpp files match their filenames. TODO: untangle and rename. + */ + +#ifndef WII5ModeLowBattery_h +#define WII5ModeLowBattery_h + +#include +#include +#include + +enum WII5LOWBATTERY_STEPS { + WII5LOWBATTERY_START, + WII5LOWBATTERY_STROBE, + WII5LOWBATTERY_TIME, + WII5LOWBATTERY_GPS_START, + WII5LOWBATTERY_GPS_WAIT, + WII5LOWBATTERY_IRIDIUM_WAIT, + WII5LOWBATTERY_END +}; + +/** + * @brief Low-battery mode: degraded operation when supply is depleted. + * + * Suppresses Capture mode's heavy operations (IMU, SD writes), keeps + * a minimum-viable position+telemetry cadence, and pesters Iridium + * with a battery-low alert. The buoy returns to its default mode once + * battery voltage recovers (or via an explicit Iridium override). + */ +class WII5ModeLowBattery : public WII5Mode { + public: + WII5ModeLowBattery() {} + /** @brief Reset to WII5LOWBATTERY_START. */ + void reset(); + /** @brief One-time bring-up. */ + void begin(); + /** @brief State-machine tick. */ + void loop(); + /** @brief Begin a low-battery alert cycle. */ + void start(); + /** @brief Abort the current cycle. */ + void stop(); + + protected: + bool first; + elapsedMillis displayWait; + elapsedMillis wait; + WII5LOWBATTERY_STEPS step; + WII5LOWBATTERY_STEPS lastStep; + uint32_t minutes; // since midnight. +}; + +extern WII5ModeLowBattery wii5ModeLowBattery; + +#endif diff --git a/WII5ModeSelfTest.cpp b/WII5ModeSelfTest.cpp new file mode 100644 index 0000000..872abc3 --- /dev/null +++ b/WII5ModeSelfTest.cpp @@ -0,0 +1,651 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModeSelfTest.cpp + * @brief Self test mode: automated boot-time hardware checks. + */ + +/* + +WII5 SelfTest + +TODO: + * Show values at the end + * Maths loop back test + * Out of range Temperature / Voltage ? + * Not detecting data Iridium even though it is there + * Not detecting data Sparton even though it is there + +MODE Default: + * Standard tests (including LED/Buzzer/Button) + * But don't expect user feedback (aka MANUAL for results) + * Turn on Maths, wait for boot and Hello, Shutdown maths + +MODE FromMaths: + * Standard tests (including LED/Buzzer/Button) + * No Maths tests at all + +MODE NoMaths: + * Existing code, expect user to check voltages etc + +*/ + +#include +#include +#include + +void WII5ModeSelfTest::reset() { + step = WII5ST_START; stepWait = 0; +} + +void WII5ModeSelfTest::begin() { + step = WII5ST_START; + mode = WII5SM_DEFAULT; +} + +WII5SELFTEST_MODE WII5ModeSelfTest::getMode() { + return mode; +} +void WII5ModeSelfTest::setMode(WII5SELFTEST_MODE m) { + mode = m; +} + +void WII5ModeSelfTest::dump(bool toConsole, Print* toOther, bool hideNone) { + count_passed = 0; + count_nonenc = 0; + count_failed = 0; + + // TODO Temp hack - BOARD should always be success + results[WII5STD_BOARD] = WII5STS_SUCCESS; + + for (WII5SELFTEST_DEVICES i = 0; i < WII5STD_DEVICES; i = i + 1) { + + switch(results[i]) { + case WII5STS_NONE: + case WII5STS_NC: + count_nonenc++; + break; + + case WII5STS_SUCCESS: + case WII5STS_HUMAN: + case WII5STS_SLOW: + count_passed++; + break; + + default: + count_failed++; + }; + + if ( !hideNone && ( (results[i] == WII5STS_NONE) || (results[i] == WII5STS_NC)) ) { + // Hidden this time + } + else { + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("@Metadata,selftest,%s,"), + wii5Strings.strSelfTestDevice(i) + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("status=%s,"), + wii5Strings.strSelfTestStatus(results[i]) + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + + snprintf_P( + wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, + PSTR("description=%s\r\n"), + wii5Strings.strSelfTestStatusLong(results[i]) + ); + if (toOther) toOther->print(wii5BufferConsolePrint); + if (toConsole) console.print(wii5BufferConsolePrint); + } + } +} + +void WII5ModeSelfTest::loop() { + first = (step != stepLast); + stepLast = step; + + if (first) + count++; + + if (console.available()) { + + // Exit Test gracefully - show report + if (console.getCommand() == 'X') { + step = WII5ST_REPORT; stepWait = 0; first = true; + } + + // Restart existing test + else if (console.getCommand() == 'S') { + step = WII5ST_START; stepWait = 0; first = true; + } + + // Force WDT Test and exit + else if (console.getCommand() == 'W') { + console.printf(F("WARNING - Disabled external WDT reset pin - runCount=%lu\r\n"), sh3dNodeConfig.getRunCount()); + wii5Controller.tempDisableWDT = true; + wii5Controller.setDefaultMode(); // Back to default mode now? so no reboot/report + return; + } + + } + + switch (step) { + case WII5ST_START: + if (first) { + count = 0; + for (WII5SELFTEST_DEVICES i = 0; i < WII5STD_DEVICES; i = i + 1) { + results[i] = WII5STS_NONE; + } + results[WII5STD_BOARD] = WII5STS_SUCCESS; + console.log(LOG_INFO, F("SelfTest %d/%d: Start"), count, WII5ST_MAX); + } + step = WII5ST_SHUTDOWN; stepWait = 0; + break; + + case WII5ST_SHUTDOWN: // Shutdown the existing bits (e.g. stopping anything we need to) + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: Shutdown old processes"), count, WII5ST_MAX); + } + step = WII5ST_LED; stepWait = 0; + break; + + case WII5ST_LED: + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: LED (check for fast, irregular then back to normal) y; | n; to confirm/reject"), count, WII5ST_MAX); + sh3dNodeIO.led1Set(LED_FAST); + stepCount = 0; + } + + if (console.available() && (console.getCommand() == 'y')) { + sh3dNodeIO.led1Set(LED_SLOW); + console.log(LOG_INFO, F("SelfTest: success")); + results[WII5STD_LED] = WII5STS_SUCCESS; + step = WII5ST_BUZZER; stepWait = 0; + } + else if (console.available() && (console.getCommand() == 'n')) { + sh3dNodeIO.led1Set(LED_SLOW); + console.log(LOG_INFO, F("SelfTest: failed")); + results[WII5STD_LED] = WII5STS_UNKNOWN; + step = WII5ST_BUZZER; stepWait = 0; + } + + if (stepWait > 20000) { + sh3dNodeIO.led1Set(LED_SLOW); + console.log(LOG_INFO, F("SelfTest: timeout")); + results[WII5STD_LED] = WII5STS_TIMEOUT; + step = WII5ST_BUZZER; stepWait = 0; + } + else if (stepWait > 5000) { + if (stepCount == 1) { + sh3dNodeIO.led1Set(LED_DOUBLE_LONG); + stepCount++; + } + } + else if (stepWait > 2500) { + if (stepCount == 0) { + sh3dNodeIO.led1Set(LED_DOUBLE); + stepCount++; + } + } + break; + + case WII5ST_BUZZER: + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: BUZZER (check for fast, irregular then back to normal) y; | n; to confirm/reject"), count, WII5ST_MAX); + sh3dNodeIO.led2Set(LED_FAST); + stepCount = 0; + } + if (console.available() && (console.getCommand() == 'y')) { + sh3dNodeIO.led2Set(LED_OFF); + results[WII5STD_BUZZER] = WII5STS_SUCCESS; + step = WII5ST_BUTTON; stepWait = 0; + } + else if (console.available() && (console.getCommand() == 'n')) { + sh3dNodeIO.led2Set(LED_OFF); + results[WII5STD_BUZZER] = WII5STS_UNKNOWN; + step = WII5ST_BUTTON; stepWait = 0; + } + if (stepWait > 20000) { + sh3dNodeIO.led2Set(LED_OFF); + results[WII5STD_BUZZER] = WII5STS_TIMEOUT; + step = WII5ST_BUTTON; stepWait = 0; + } + else if (stepWait > 5000) { + if (stepCount == 1) { + sh3dNodeIO.led2Set(LED_DOUBLE_LONG); + stepCount++; + } + } + else if (stepWait > 2500) { + if (stepCount == 0) { + sh3dNodeIO.led2Set(LED_DOUBLE); + stepCount++; + } + } + break; + + case WII5ST_BUTTON: + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: Button. Toggle button at least twice"), count, WII5ST_MAX); + wii5Commands.disableButtons = 0; + stepCount = 0; + } + else if (sh3dNodeIO.button1->isClicked()) { + stepCount++; + } + else if (stepCount >= 2) { + console.log(LOG_INFO, F("SelfTest: Button detected 2 or more clicks success")); + results[WII5STD_BUTTON] = WII5STS_SUCCESS; + step = WII5ST_INFORMATION; stepWait = 0; + } + else if (console.available() && (console.getCommand() == 'y')) { + console.log(LOG_INFO, F("SelfTest: Button user clicked 'y' manually - human yes")); + results[WII5STD_BUTTON] = WII5STS_HUMAN; + step = WII5ST_INFORMATION; stepWait = 0; + } + else if (console.available() && (console.getCommand() == 'n')) { + console.log(LOG_INFO, F("SelfTest: Button user clicked 'n' manually - failed")); + results[WII5STD_BUTTON] = WII5STS_UNKNOWN; + step = WII5ST_INFORMATION; stepWait = 0; + } + else if (stepWait > 20000) { + console.log(LOG_INFO, F("SelfTest: Button timeout")); + results[WII5STD_BUTTON] = WII5STS_TIMEOUT; + step = WII5ST_INFORMATION; stepWait = 0; + } + break; + + case WII5ST_INFORMATION: // What do we know: our ID: our run count etc + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: Information"), count, WII5ST_MAX); + } + step = WII5ST_CONFIG; stepWait = 0; + break; + + case WII5ST_CONFIG: // Check we can read and write config + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: Config"), count, WII5ST_MAX); + } + + if ( (wii5Config.getDeviceId() < 1) ) { + results[WII5STD_EEPROM] = WII5STS_UNKNOWN; + } + else if ( + (wii5Config.getDeviceId() > 1000) + && (wii5Config.getDeviceId() < 99999) + ) { + results[WII5STD_EEPROM] = WII5STS_SUCCESS; + } + else { + results[WII5STD_EEPROM] = WII5STS_OUTOFRANGE; + } + step = WII5ST_GPS_RECEIVE; stepWait = 0; + break; + + case WII5ST_GPS_RECEIVE: // Turn on GPS and receive some NMEA + #ifdef WII5_GPS + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: GPS Started, please wait"), count, WII5ST_MAX); + wii5Gps.autoAccurate(); + } + if ( + (stepWait > 10000) + || (wii5Gps.isError()) + || (wii5Gps.ready()) + ) { + console.log(LOG_INFO, F("SelfTest %d/%d: GPS complete"), count, WII5ST_MAX); + step = WII5ST_GPS_OFF; stepWait = 0; + } + #else + step = WII5ST_SD0; stepWait = 0; + results[WII5STD_GPS] = WII5STS_NC; + #endif + break; + + case WII5ST_GPS_OFF: // Turn off: and check GPS is ok + #ifdef WII5_GPS + if (first) { + wii5Gps.off(); // TODO inconsistent OFF vs STOP ? + console.log(LOG_INFO, F("SelfTest %d/%d: GPS Off"), count, WII5ST_MAX); + } + if(wii5Gps.isError()) { + results[WII5STD_GPS] = WII5STS_UNKNOWN; + } + else if (wii5Gps.ready()) { + results[WII5STD_GPS] = WII5STS_SUCCESS; + } + else if (wii5Gps.gps->charsProcessed() > 300) { + results[WII5STD_GPS] = WII5STS_SLOW; + } + else { + results[WII5STD_GPS] = WII5STS_TIMEOUT; + } + step = WII5ST_SD0; stepWait = 0; + break; + #else + step = WII5ST_SD0; stepWait = 0; + #endif + + // TODO GPS power testing + + case WII5ST_SD0: // Make sure SD are both turned off + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: SD0 - powering on but not configured"), count, WII5ST_MAX); + } + if (stepWait > 1000) { + step = WII5ST_SD1; stepWait = 0; + } + break; + + case WII5ST_SD1: // Open SD1: write a file with unique id/datetime + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: SD1"), count, WII5ST_MAX); + + results[WII5STD_SD1] = WII5STS_SUCCESS; + step = WII5ST_SD2; stepWait = 0; + } + break; + + case WII5ST_SD2: // Open SD2 and write a new file + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: SD2"), count, WII5ST_MAX); + // TODO See above + results[WII5STD_SD2] = WII5STS_SUCCESS; + step = WII5ST_SD_OFF; stepWait = 0; + } + break; + + // TODO: SD1_B / SD2_B verification — re-enable once data round-trip + // checks are implemented (similar to the SD Card tester). + + case WII5ST_SD_OFF: // Shut it down + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: SD_OFF"), count, WII5ST_MAX); + } + step = WII5ST_IRIDIUM; stepWait = 0; + break; + + case WII5ST_IRIDIUM: // Turn on Iridium and wait for some responses + #ifdef WII5_COMMS_IRIDIUM + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: Iridium (takes time, Console loggging enabled)"), count, WII5ST_MAX); + wii5Iridium.setPassthrough(false); + wii5Iridium.requestFirmware(); + } + // Found IMEI - Yay Iridium ! + else if (strlen(wii5Iridium.imei) > 3) { + wii5Iridium.stop(); + results[WII5STD_IRIDIUM] = WII5STS_SUCCESS; + step = WII5ST_BATTERY_1; stepWait = 0; + } + else if (stepWait > 10000) { + wii5Iridium.stop(); + results[WII5STD_IRIDIUM] = WII5STS_TIMEOUT; + step = WII5ST_BATTERY_1; stepWait = 0; + } + #else + results[WII5STD_IRIDIUM] = WII5STS_NC; + step = WII5ST_BATTERY_1; stepWait = 0; + #endif + break; + + case WII5ST_BATTERY_1: // Check battery 1 has volts and doesn't jitter too much + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: Battery 1"), count, WII5ST_MAX); + wii5Battery.start(); + } + else if (stepWait > 5000) { + results[WII5STD_BATTERY] = WII5STS_TIMEOUT; + wii5Battery.stop(); + step = WII5ST_WEATHER; stepWait = 0; + } + else if (! wii5Battery.isRunning()) { + if ( + (wii5Battery.value < 1500) + && (wii5Battery.value > 700) + ) { + results[WII5STD_BATTERY] = WII5STS_SUCCESS; + } + else { + results[WII5STD_BATTERY] = WII5STS_OUTOFRANGE; + } + wii5Battery.stop(); + step = WII5ST_WEATHER; stepWait = 0; + } + break; + + /* + case WII5ST_BATTERY_2: // If it exists + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: Battery 2"), count, WII5ST_MAX); + } + step = WII5ST_MATHS_START; stepWait = 0; + break; + */ + + case WII5ST_WEATHER: + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: Read Temperature"), count, WII5ST_MAX); + wii5Weather_18B20.temperatureRead(); + if ( + (wii5Weather_18B20.value > 100) + && (wii5Weather_18B20.value < 5000) + && (wii5Weather_18B20.value < 2000) + ) { + results[WII5STD_18B20] = WII5STS_SUCCESS; + } + else if ( + (wii5Weather_18B20.value) + ) { + // OR slow ? + results[WII5STD_18B20] = WII5STS_OUTOFRANGE; + } + else { + results[WII5STD_18B20] = WII5STS_UNKNOWN; + } + } + step = WII5ST_5VOLT_START; stepWait = 0; + break; + + case WII5ST_5VOLT_START: + if (first) { + if (mode == WII5SM_FROMMATHS) { + console.log(LOG_INFO, F("SelfTest %d/%d: From Maths - skipping 5V and Maths"), count, WII5ST_MAX); + step = WII5ST_RTC; stepWait = 0; + } + else { + console.log(LOG_INFO, F("SelfTest %d/%d: 5VOLT Power On for 30 seconds"), count, WII5ST_MAX); + } + } + else if (console.available() && (console.getCommand() == 'y')) { + step = WII5ST_5VOLT_OFF; stepWait = 0; + } + else if (console.available() && (console.getCommand() == 'n')) { + step = WII5ST_RTC; stepWait = 0; + } + else if (stepWait > 30000) { + step = WII5ST_5VOLT_OFF; stepWait = 0; + } + break; + + case WII5ST_5VOLT_OFF: + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: 5VOLT Off for 30 seconds"), count, WII5ST_MAX); + } + else if (console.available() && (console.getCommand() == 'y')) { + results[WII5STD_5VOLT] = WII5STS_SUCCESS; + step = WII5ST_MATHS_START; stepWait = 0; + } + else if (console.available() && (console.getCommand() == 'n')) { + results[WII5STD_5VOLT] = WII5STS_UNKNOWN; + step = WII5ST_RTC; stepWait = 0; + } + else if (stepWait > 30000) { + results[WII5STD_5VOLT] = WII5STS_TIMEOUT; + step = WII5ST_MATHS_START; stepWait = 0; + } + break; + + case WII5ST_MATHS_START: // Turn it on ! and make sure we get response + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: Maths Power On for 30 seconds"), count, WII5ST_MAX); + wii5Maths.powerOn(); + } + else if (console.available() && (console.getCommand() == 'y')) { + step = WII5ST_MATHS_OFF; stepWait = 0; + } + else if (console.available() && (console.getCommand() == 'n')) { + step = WII5ST_RTC; stepWait = 0; + } + else if (stepWait > 30000) { + step = WII5ST_MATHS_OFF; stepWait = 0; + } + break; + + case WII5ST_MATHS_OFF: // Turn it off: and make sure it is off + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: Maths Off for 30 seconds"), count, WII5ST_MAX); + wii5Maths.powerOff(); + } + else if (console.available() && (console.getCommand() == 'y')) { + results[WII5STD_MATHS] = WII5STS_SUCCESS; + step = WII5ST_RTC; stepWait = 0; + } + else if (console.available() && (console.getCommand() == 'n')) { + results[WII5STD_MATHS] = WII5STS_UNKNOWN; + step = WII5ST_RTC; stepWait = 0; + } + else if (stepWait > 30000) { + results[WII5STD_MATHS] = WII5STS_TIMEOUT; + step = WII5ST_RTC; stepWait = 0; + } + break; + + case WII5ST_RTC: // Do we have one: what version: what details + if (first) { + #ifdef WII5_RTC + console.log(LOG_INFO, F("SelfTest %d/%d: RTC - Checking Temperature"), count, WII5ST_MAX); + int t = int(wii5RTC.getTemperature()); + console.log(LOG_INFO, F("SelfTest RTC temperature = %d"), t); + if (t > 1 && t < 50) { + results[WII5STD_RTC] = WII5STS_SUCCESS; + } + else { + results[WII5STD_RTC] = WII5STS_OUTOFRANGE; + } + #else + console.log(LOG_INFO, F("SelfTest %d/%d: RTC - NOT ENABLED"), count, WII5ST_MAX); + results[WII5STD_RTC] = WII5STS_NC; + #endif + } + step = WII5ST_SPARTON; stepWait = 0; + break; + + case WII5ST_SPARTON: // Turn it on: get some values: tun it off + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: Sparton"), count, WII5ST_MAX); + wii5Sparton.automatic(64, 180, false); + } + else if (wii5Sparton.captureFinished()) { + wii5Sparton.stop(); + if ( + 1 + // (wii5Sparton.lastAccelZ > 900) + // && (wii5Sparton.lastAccelZ < 1100) + ) { + results[WII5STD_SPARTON] = WII5STS_SUCCESS; + } + else { + results[WII5STD_SPARTON] = WII5STS_OUTOFRANGE; + } + step = WII5ST_REPORT; stepWait = 0; + } + else if (stepWait > 30000) { + wii5Sparton.stop(); + results[WII5STD_SPARTON] = WII5STS_TIMEOUT; + step = WII5ST_REPORT; stepWait = 0; + } + break; + + case WII5ST_REPORT: // Report what happened + if (first) { + console.log(LOG_INFO, F("SelfTest %d/%d: COMPLETE - Running Report (console only)"), count, WII5ST_MAX); + // step = WII5ST_SHUTDOWN; + // wii5Metadata.setPassthrough(true); + // wii5Metadata.setCapture(false); + // wii5Metadata.report(); + + /* + + Human readalbe + + * ID + * Number of passes / fails - % + * List of fails + + */ + + console.printf(F("# SelfTest: Passed=%d, None/NC=%d, Failed=%d\r\n"), + count_passed, count_nonenc, count_failed + ); + + for (WII5SELFTEST_DEVICES i = 0; i < WII5STD_DEVICES; i = i + 1) { + switch(results[i]) { + case WII5STS_NONE: + case WII5STS_NC: + case WII5STS_SUCCESS: + case WII5STS_HUMAN: + case WII5STS_SLOW: + break; + default: + console.printf(F("# FAILED = %s"), + wii5Strings.strSelfTestDevice(i) + ); + console.printf(F(" status=%s"), + wii5Strings.strSelfTestStatus(results[i]) + ); + console.printf(F(" description=%s\r\n"), + wii5Strings.strSelfTestStatusLong(results[i]) + ); + break; + }; + } + + // Manual output numbers we know.... + console.printf(F("# VALUES: Temperature=%d\r\n"), wii5Weather_18B20.value); + console.printf(F("# VALUES: Battery=%d\r\n"), wii5Battery.value); + if (mode == WII5SM_FROMMATHS) { + console.printf(F("# 60 seconds until back to default mode\r\n")); + } + else { + console.printf(F("# Commands (or 60 seconds until reboot)\r\n")); + console.printf(F("# .;=back to default, W;=disable WDT, R;=restart.\r\n")); + } + } + else if (stepWait > 60000) { + if (mode == WII5SM_FROMMATHS) { + wii5Controller.setDefaultMode(); + } + else { + wii5Controller.setDefaultMode(); // Back to default mode now? + sh3dNodeUtil.reset(); + } + } + break; + default: + step = WII5ST_START; + } +} + +WII5ModeSelfTest wii5ModeSelfTest; diff --git a/WII5ModeSelfTest.h b/WII5ModeSelfTest.h new file mode 100644 index 0000000..a6bdcbd --- /dev/null +++ b/WII5ModeSelfTest.h @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModeSelfTest.h + * @brief Self test mode: automated boot-time hardware checks. + */ + +#ifndef WII5ModeSelfTest_h +#define WII5ModeSelfTest_h + +#include +#include +#include + +enum WII5SELFTEST_STEPS { + WII5ST_START, + WII5ST_SHUTDOWN, // Shutdown the existing bits + WII5ST_INFORMATION, // What do we know, our ID, our run count etc + WII5ST_LED, + WII5ST_BUZZER, + WII5ST_BUTTON, + WII5ST_CONFIG, // Check we can read and write config + WII5ST_GPS_RECEIVE, // Turn on GPS and receive some NMEA + WII5ST_GPS_OFF, // Turn off, and check GPS is ok + WII5ST_SD0, // Make sure SD are both turned off + WII5ST_SD1, // Open SD1, write a file with unique id/datetime + WII5ST_SD2, // Open SD2 and write a new file + WII5ST_SD1_B, // Check the SD1 data is there + WII5ST_SD2_B, // Check the SD2 data is there + WII5ST_SD_OFF, // Shut it down + WII5ST_IRIDIUM, // Turn on Iridium and wait for some responses + WII5ST_BATTERY_1, // Check battery 1 has volts and doesn't jitter too much + WII5ST_BATTERY_2, // If it exists + WII5ST_WEATHER, // Do we get some temperatures + WII5ST_MATHS_START, // Turn it on ! and make sure we get response + WII5ST_MATHS_OFF, // Turn it off, and make sure it is off + WII5ST_5VOLT_START, // Turn it on ! and make sure we get response + WII5ST_5VOLT_OFF, // Turn it off, and make sure it is off + WII5ST_RTC, // Do we have one, what version, what details + WII5ST_SPARTON, // Turn it on, get some values, tun it off + WII5ST_REPORT, // Report what happened +}; + +enum WII5SELFTEST_MODE { + WII5SM_DEFAULT, // Run from AVR CPU (while running), Maths not running, but tested + WII5SM_FROMMATHS, // Test being run by the Maths CPU, therefore can't play maths + WII5SM_NOMATHS, // Simple test, no maths installed, user entered variables +}; + +#define WII5ST_MAX 25 + +/** + * @brief Self test mode: automated hardware checkout. + * + * Walks every subsystem once (LED, buzzer, button, config, GPS, SD x2, + * Iridium, batteries, weather, Maths CPU, 5V rail, RTC, Sparton) and + * records pass/fail in `results[]`. Three submodes: + * - WII5SM_DEFAULT: AVR-driven, expects an attached operator + * - WII5SM_FROMMATHS: invoked by the Maths CPU; skips Maths checks + * - WII5SM_NOMATHS: simple check, no Maths CPU present + */ +class WII5ModeSelfTest : public WII5Mode { + public: + WII5ModeSelfTest() {} + /** @brief Reset to WII5ST_START. */ + void reset(); + /** @brief One-time bring-up. */ + void begin(); + /** @brief Run the next test step (called repeatedly while active). */ + void loop(); + /** @brief Print the results table; `toOther` optionally mirrors elsewhere. */ + void dump(bool toConsole = true, Print* toOther = NULL, bool hideNone = false); + /** @brief Current self-test submode. */ + WII5SELFTEST_MODE getMode(); + /** @brief Select self-test submode. */ + void setMode(WII5SELFTEST_MODE m); + + protected: + WII5SELFTEST_MODE mode; + elapsedMillis wait; + WII5SELFTEST_STEPS step; + WII5SELFTEST_STEPS stepLast; + elapsedMillis stepWait; + uint32_t stepCount; // Use for what ever you like + bool first; + + uint16_t count; + uint16_t count_passed; + uint16_t count_failed; + uint16_t count_nonenc; + + WII5SELFTEST_STATUS results[WII5STD_DEVICES]; +}; + +extern WII5ModeSelfTest wii5ModeSelfTest; + +#endif diff --git a/WII5ModeSleep.cpp b/WII5ModeSleep.cpp new file mode 100644 index 0000000..5825016 --- /dev/null +++ b/WII5ModeSleep.cpp @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModeSleep.cpp + * @brief Sleep mode: long sleeps between wake-ups; powers down the Maths CPU. + */ + +/* + +Sleeping.... Not a short one, this is the full deal. + + * Start + - Are we holding - jump to hold + - Or calculate next sleep time + * Wait + - Shut down Devices + * Sleep + - Sleep for n seconds + * Update + - Start doing an update + * Wait Gps + * Wait Iridium + * Wait Maths + +*/ + +#include +#include +#include + +void WII5ModeSleep::reset() { + step = WII5SLEEP_START; stepWait = 0; + lastStep = WII5SLEEP_SLEEPING; +} + +void WII5ModeSleep::begin() { + step = WII5SLEEP_START; stepWait = 0; + lastStep = WII5SLEEP_SLEEPING; +} + +void WII5ModeSleep::loop() { + first = (step != lastStep); + lastStep = step; + + switch (step) { + case WII5SLEEP_START: + // TODO - Check we have valid time + // TODO - Check this is a timed mode - and change back to default + // - If default, stays here forever + + // First time - LED on + if (first) { + console.log(LOG_INFO, F("Sleep: Waiting up to 60 seconds before power down")); + sh3dNodeIO.led1Set( LED_DOUBLE ); + } + + // Check maths on hold - it always wins + else if (wii5Maths.remainingHold() > 0) { + console.log(LOG_ERROR, F("Sleep: Maths holding, switching to wait")); + step = WII5SLEEP_BUTTONS; stepWait = 0; + } + + // TODO 2024 - Removed time at boot start + // Check if GPS is old and been an hour since we checked it, force it now + // else if ((wii5Gps.sinceOnLast > (WII5TIME_1HOUR * 1000)) && (!wii5Gps.isTimeValid())) { + // console.log(LOG_ERROR, F("Sleep: Internal clock / time is not valid, turn on GPS")); + // step = WII5SLEEP_TIME; stepWait = 0; + // } + + // Normal mode - calculate next period - Wait 60 seconds first (TODO 2024) + else if (stepWait > 60000) { + // Convert minutes to seconds - note, getSleepPeriod will not return over 24 hours. + sleepNextSeconds = (uint32_t)wii5Config.getSleepPeriod() * (uint32_t)60; + + // Check if we have any thing we need to do + // e.g. Check are we allowed to sleep (rules) + // Log our intent + + // What about auto switching to Capture - can we do that? Should we + + #ifdef DEBUG_SLEEP + console.log(LOG_DEBUG, F("Sleep: step -> wait")); + #endif + step = WII5SLEEP_WAIT; stepWait = 0; + } + break; + + case WII5SLEEP_WAIT: + // Has everything finished - e.g. Maths. Give it a bit of time. How long? Need to think about it. + if (first) { + #ifdef WII5_GPS + wii5Gps.off(); + #endif + wii5Communications.stop(); + wii5Iridium.stop(); + wii5Sparton.stop(); + + // Maths off ? + // wii5Maths.off(); + + wii5Controller.setSDOff(); // Force off then power down SD + wii5Controller.shared5Off(); + } + // TODO 2024 - Wait 20 seconds afterall off before sleeping + else if (stepWait > 20000) { + #ifdef DEBUG_SLEEP + console.log(LOG_DEBUG, F("Sleep: stop devices - step -> sleeping")); + #endif + step = WII5SLEEP_SLEEPING; stepWait = 0; + } + break; + + case WII5SLEEP_SLEEPING: + // Something up - should never be here + if (sleepNextSeconds < 10) { + console.log(LOG_ERROR, F("Sleep: Time < 10 seconds, skipping to UPDATE mode")); + step = WII5SLEEP_UPDATE; + } + else { + if (sleepNextSeconds > 21600) { + console.log(LOG_ERROR, F("Sleep: ERROR > 6 hours (21600) seconds, changing to 1 hour")); + sleepNextSeconds = 3600; + } + + // Actual sleep + console.log(LOG_INFO, F("Sleep: sleeping %d hours = %lu seconds"), int(sleepNextSeconds / 3600), sleepNextSeconds); + + // Sleep, turning off Maths + wii5Setup.sleepBefore(); + // TODO 2024 - Consider sleep loop - not actually WDT but rather just tight loop, therefore leaving WDT normal for now + sh3dNodeUtil.sleep(sleepNextSeconds); + wii5Setup.sleepAfter(); + + // } + console.log(LOG_INFO, F("Sleep: Woke up - requested=%lu, actual=%lu, reasons=%d"), sleepNextSeconds, sh3dNodeUtil.sleepLastSeconds, sh3dNodeUtil.sleepLastReason); + + switch(sh3dNodeUtil.sleepLastReason) { + case 0: + console.log(LOG_FATAL, F("Unknwon error inside sleep function")); + // TODO What next? - not sure how to deal with this + // TODO put it in an error counter... if the counter gets too large or rescent + // Then switch back to default mode. + step = WII5SLEEP_START; stepWait = 0; + break; + + // Normal exit + case 1: + // TODO 12 hours is up - turn on GPS etc and send/receive data + step = WII5SLEEP_UPDATE; + break; + + // TODO Identify that this needs to stay on for some seconds for + // button Processing + + // These are like a reboot - coming out of sleep. Beep like one + case 10: + case 11: + wii5Setup.bootbeep(); + step = WII5SLEEP_BUTTONS; + break; + + default: + console.log(LOG_ERROR, F("Sleep: Unknown mode = %d"), step); + step = WII5SLEEP_UPDATE; + break; + } + } + break; + + case WII5SLEEP_BUTTONS: + if (first) { + sh3dNodeIO.led1Set( LED_DOUBLE ); + console.log(LOG_ERROR, F("Sleep: BUTTONS process Waiting for Maths and another 10 seconds for buttons")); + wii5Maths.stop(); + } + // Maths stopped and 30 seconds + else if (wii5Maths.isOff() && (stepWait > 30000)) { + console.log(LOG_ERROR, F("Sleep: BUTTONS process finished, Maths off, starting sleep process (30 seconds)")); + step = WII5SLEEP_START; stepWait = 0; + } + // 1 hour passed - shut it down buoys + else if (stepWait > ((uint32_t)WII5TIME_1HOUR * 1000)) { + console.log(LOG_FATAL, F("Sleep: Failed timeout of Buttons")); + step = WII5SLEEP_START; stepWait = 0; + } + break; + + case WII5SLEEP_TIME: + if (first) { + wii5Gps.autoTime(); + displayWait = 0; + } + else if (wii5Gps.ready()) { + step = WII5SLEEP_START; stepWait = 0; + } + else if (stepWait > 300000) { + step = WII5SLEEP_START; stepWait = 0; + } + else if (displayWait > 10000) { + console.log(LOG_INFO, F("Sleep: Waiting for GPS Time - %lu seconds"), (stepWait / 1000)); + displayWait = 0; + } + break; + + case WII5SLEEP_UPDATE: + if (first) { + sh3dNodeIO.led1Set( LED_DOUBLE ); + #ifdef WII5_GPS + wii5Gps.autoAccurate(); + #endif + wii5Battery.start(); + wii5Weather_18B20.temperatureRead(); + displayWait = 0; + wii5Maths.stop(); + } + else if (wii5Gps.ready()) { + if (wii5Gps.isError()) { + console.log(LOG_ERROR, F("Sleep: GPS Finished with ERROR")); + } + wii5Gps.off(); + step = WII5SLEEP_COMMS; stepWait = 0; + } + else if (stepWait > 300000) { + console.log(LOG_ERROR, F("Sleep: GPS Timed out after 300 seconds")); + step = WII5SLEEP_COMMS; stepWait = 0; + } + else if (displayWait > 10000) { + console.log(LOG_INFO, F("Sleep: Waiting for GPS Lock - %lu seconds"), (stepWait / 1000)); + displayWait = 0; + } + break; + + case WII5SLEEP_COMMS: + if (first) { + sh3dNodeIO.led1Set( LED_DOUBLE ); + console.log(LOG_INFO, F("Sleep: Communications start")); + wii5Communications.setSimpleMode(); + wii5Communications.sendBinModeType(wii5Config.getSleepBinaryType(), wii5Config.getRecordCount()); + wii5Communications.start(); + } + else if (!wii5Communications.isRunning()) { + console.log(LOG_INFO, F("Sleep: Communications end")); + step = WII5SLEEP_START; stepWait = 0; + } + // TODO 10 minutes ok + else if (stepWait > 600000) { + console.log(LOG_FATAL, F("Sleep: Communications timeout")); + step = WII5SLEEP_START; stepWait = 0; + } + break; + + default: + console.log(LOG_FATAL, F("SLEEP: Default step... step=%d"), step); + step = WII5SLEEP_START; stepWait = 0; + break; + } +} + +WII5ModeSleep wii5ModeSleep; diff --git a/WII5ModeSleep.h b/WII5ModeSleep.h new file mode 100644 index 0000000..c767780 --- /dev/null +++ b/WII5ModeSleep.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5ModeSleep.h + * @brief Sleep mode: long sleeps between wake-ups; powers down the Maths CPU. + */ + +#ifndef WII5ModeSleep_h +#define WII5ModeSleep_h + +#include +#include +#include + +// TODO - Namespace these - e.g enum class ... +enum WII5SLEEP_STEPS { + WII5SLEEP_START, + WII5SLEEP_WAIT, + WII5SLEEP_SLEEPING, + WII5SLEEP_BUTTONS, + WII5SLEEP_UPDATE, + WII5SLEEP_TIME, + WII5SLEEP_COMMS, + WII5SLEEP_FINISH +}; + +/** + * @brief Sleep mode: long sleeps between wake-ups. + * + * Computes how long to sleep until the next configured wake-up + * (capture window, position update, etc.), powers down the Maths CPU + * and other peripherals, and uses sh3dNodeUtil.sleep() to put the AVR + * into deep sleep. + */ +class WII5ModeSleep : public WII5Mode { + public: + WII5ModeSleep() {} + /** @brief Reset to WII5SLEEP_START. */ + void reset(); + /** @brief One-time bring-up. */ + void begin(); + /** @brief State-machine tick. */ + void loop(); + + protected: + bool first; + WII5SLEEP_STEPS lastStep; + elapsedMillis stepWait; + elapsedMillis displayWait; + WII5SLEEP_STEPS step; + uint32_t sleepNextSeconds; // How long we going to sleep (cleared after sleep) +}; + +extern WII5ModeSleep wii5ModeSleep; + +#endif diff --git a/WII5Power.cpp b/WII5Power.cpp new file mode 100644 index 0000000..7be6d3e --- /dev/null +++ b/WII5Power.cpp @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Power.cpp + * @brief Base class for power-managed peripherals (powerOn/powerOff state). + */ + +/* + +WII5Power - Power PIN version of base class - can also be used stand alone + +TODO + * (low) pulse - How do we have a loop function or simiar to turn off the pin after a period. + * (medium) two pin - one one on, one off + * (medium) shared pin - sharing a single pin (could this be done better/together) + * (low) deug - approve upon the macro that is here. + +*/ + +#include +#include +#include + +/* POWER PIN Debuggig + This can produce an overwhelming amount of serial output + Uses safeLog to prevent output on alternative serial / SD Card. + Add a _PIN versio to allow logging of a single pin + + NOTE: This should be generalised across all the modules (different selectors) +*/ +// #define POWERDEBUG +// #define POWERDEBUG_PIN POWER_GPS_PIN + +#ifdef POWERDEBUG + #if defined(POWERDEBUG_PIN) + #define DODEBUG(...) if(powerData_Pin == POWERDEBUG_PIN) console.safeLog(LOG_DEBUG, __VA_ARGS__) + #else + #define DODEBUG(...) console.safeLog(LOG_DEBUG, __VA_ARGS__) + #endif +#else + #define DODEBUG(...) (void)0 +#endif + +// powerOn +void WII5Power::powerOn(bool force) { + // FATAL ERROR (can we afford to Serial print here?) + if (powerData_Pin < 2) + return; + + DODEBUG(F("Power (ON): ON request %d=%s, status=%d, force=%d"), powerData_Pin, wii5Strings.strPinWII5Name(powerData_Pin), powerData_On, force); + if (powerData_Input || !powerData_On || force) { + powerData_Input = false; + // Shared pin + if (powerData_Shared) { + DODEBUG(F("Power(ON): shared requested")); + powerData_Shared->powerOn(); + } + + // Pin Modes + DODEBUG(F("Power(ON): pinMode OUTPUT")); + pinMode(powerData_Pin, OUTPUT); + if (powerData_PinOff > 0) + pinMode(powerData_PinOff, OUTPUT); + + DODEBUG(F("Power(ON): digitalWrite %d"), powerData_OnIsHigh ? HIGH : LOW); + digitalWrite(powerData_Pin, powerData_OnIsHigh ? HIGH : LOW); + + if (powerData_PinOff > 0) { + DODEBUG(F("Power(ON): WARNING - Untested PinOff Code")); + digitalWrite(powerData_PinOff, powerData_OnIsHigh ? LOW : HIGH); + } + + // Variables + powerData_Elapsed = 0; + powerData_Last = now(); + powerData_On = true; + + // Status + // setStatus(WII5STATUS_ON); + } + DODEBUG(F("Power(ON) : Actual=%d OnIs=%d"), digitalRead(powerData_Pin), powerData_OnIsHigh); +} + +// powerInput - Turn this pin back into an input - either for safety or sleep +void WII5Power::powerInput() { + powerData_Input = true; + pinMode(powerData_Pin, INPUT); +} + +// powerOff +void WII5Power::powerOff(bool force) { + if (powerData_Pin < 2) + return; + + DODEBUG(F("Power (OFF): OFF request %d=%s, status=%d, force=%d"), powerData_Pin, wii5Strings.strPinWII5Name(powerData_Pin), powerData_On, force); + if (powerData_Input || powerData_On || force) { + powerData_Input = false; + + // Shared pin + if (powerData_Shared) + powerData_Shared->powerOff(); + + // Local pin + DODEBUG(F("Power(OFF): pinMode OUTPUT")); + pinMode(powerData_Pin, OUTPUT); + if (powerData_PinOff > 0) + pinMode(powerData_PinOff, OUTPUT); + + // Output + DODEBUG(F("Power(OFF): digitalWrite %d"), powerData_OnIsHigh ? HIGH : LOW); + digitalWrite(powerData_Pin, powerData_OnIsHigh ? LOW : HIGH); + if (powerData_PinOff > 0) + digitalWrite(powerData_PinOff, powerData_OnIsHigh ? HIGH : LOW); + + // Variables (caclulate totals) + powerData_LastTotal = int(powerData_Elapsed / 1000); + powerData_Elapsed = 0; + powerData_Last = now(); + powerData_On = false; + + // Status + // setStatus(WII5STATUS_OFF); + } + DODEBUG(F("Power(OFF) : Actual=%d OnIs=%d"), digitalRead(powerData_Pin), powerData_OnIsHigh); +} + +void WII5Power::powerSetPin(uint8_t p, bool onHigh, uint8_t pOff, uint8_t pulse) { + powerData_Pin = p; + powerData_OnIsHigh = onHigh; + powerData_PinOff = pOff; + powerData_Pulse = pulse; + powerData_Input = true; + powerData_On = false; + powerData_Elapsed = 0; + powerData_Last = 0; + powerData_LastTotal = 0; + powerData_Shared = NULL; +} +void WII5Power::powerSetShared(WII5Power *s) { + powerData_Shared = s; +} + +// Status and output +bool WII5Power::powerStatus() { + // NOTE: This is not accurate if the pin as still an INPUT + return powerData_On; +} +uint32_t WII5Power::powerLast() { + return powerData_Last; +} +uint32_t WII5Power::powerElapsed() { + return powerData_Elapsed; +} +uint32_t WII5Power::powerLastTotal() { + return powerData_LastTotal; +} + diff --git a/WII5Power.h b/WII5Power.h new file mode 100644 index 0000000..7cd59dc --- /dev/null +++ b/WII5Power.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Power.h + * @brief Base class for power-managed peripherals (powerOn/powerOff state). + */ + +#ifndef WII5Power_h +#define WII5Power_h + +#include + +class WII5Power : public WII5 { + public: + WII5Power() { + powerData_Pin = 0; powerData_PinOff = 0; powerData_Pulse = 0; powerData_OnIsHigh = false; + powerData_On = false; powerData_Elapsed = 0; powerData_Last = 0; powerData_LastTotal = 0; + powerData_Input = true; + } + virtual WII5_DRIVERS driverId() {return WII5DRIVER_POWER;} + void powerSetPin(uint8_t p, bool onHigh = true, uint8_t pOff = 0, uint8_t pulse = 0); + void powerSetShared(WII5Power *s); + virtual void powerOn(bool force = false); + virtual void powerOff(bool force = false); + virtual void powerInput(); + bool powerStatus(); + uint32_t powerLast(); + uint32_t powerElapsed(); + uint32_t powerLastTotal(); + + protected: + // PIN and which way high + uint8_t powerData_Pin; + uint8_t powerData_PinOff; // If this is set, assume Pin to turn on, PinOff to turn off + uint8_t powerData_Pulse; // Numbers of 10s of milliseconds, from 10 to 2550 + bool powerData_OnIsHigh; + + // Shared pin - see WII5PowerShared (note it can just be a PIN but Shared counts) + WII5Power *powerData_Shared; + + // Current status + bool powerData_On; + bool powerData_Input; // Pin is currently input - e.g. bootup or sleep + + // How long since on (millisecond timer, not effected by time changes) + elapsedMillis powerData_Elapsed; + + // Actual time truned on (effected by tim echange) + time_t powerData_Last; + + // Number of seconds it was on for + uint32_t powerData_LastTotal; +}; + +#endif diff --git a/WII5RTC.cpp b/WII5RTC.cpp new file mode 100644 index 0000000..8666e06 --- /dev/null +++ b/WII5RTC.cpp @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5RTC.cpp + * @brief Real-time clock helper. + */ + +/* + +WII5RTC - + +*/ + +#include +#include +#include +#include "RTClib.h" +RTC_DS3231 rtc; + +uint32_t syncProvider() { + return rtc.now().unixtime(); +} + +void WII5RTC::begin() { + if (! rtc.begin()) { + SerialConsole.println("RTC: Couldn't find RTC"); + } + + // TODO Do the lost power thing + + // Then connect to GPS + + console.printf(F("RTC: startup date time from RTC=")); + console.printDateTime(rtc.now().unixtime()); + console.printNewLine(); + + setSyncProvider(syncProvider); // the function to get the time from the RTC + if(timeStatus() != timeSet) + SerialConsole.println("RTC: Unable to sync with the RTC"); + else + SerialConsole.println("RTC: has set the system time"); +} + +/* + +What type of device +Whuihc Library +Can they suse SPI or I2c or, writred wrong? + +What are the pins + +*/ + +float WII5RTC::getTemperature() { + return rtc.getTemperature(); +} + +void WII5RTC::setRTC(uint32_t t) { + if (t == 0) { + t = now(); + } + rtc.adjust(t); + console.log(LOG_INFO, F("RTC: Adjusted to new time provided")); + console.printf(F("# TimeSet=")); + console.printDateTime(t); + console.printf(F(" RTC=")); + console.printDateTime(rtc.now().unixtime()); + console.printNewLine(); +} + +void WII5RTC::test() { + + // TODO Move to Strings for space + char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + + // TODO Move this code into begin ! + if (rtc.lostPower()) { + SerialConsole.println(F("RTC lost power, lets set the time!")); + // following line sets the RTC to the date & time this sketch was compiled + // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); + // Fixed date temporary + rtc.adjust(1483268462); + // TODO Log this ! + } + + elapsedMillis doIt = 0; + + while (doIt < 15000) { + DateTime nowrtc = rtc.now(); + time_t nowsys = now(); + + // System vs RTC + SerialConsole.print(F("# UnixTimeStamp System = ")); + SerialConsole.print(nowsys); + SerialConsole.print(F(" RTC = ")); + SerialConsole.print(nowrtc.unixtime()); + SerialConsole.println(); + + SerialConsole.print(nowrtc.year(), DEC); + SerialConsole.print('/'); + SerialConsole.print(nowrtc.month(), DEC); + SerialConsole.print('/'); + SerialConsole.print(nowrtc.day(), DEC); + SerialConsole.print(" ("); + SerialConsole.print(daysOfTheWeek[nowrtc.dayOfTheWeek()]); + SerialConsole.print(") "); + SerialConsole.print(nowrtc.hour(), DEC); + SerialConsole.print(':'); + SerialConsole.print(nowrtc.minute(), DEC); + SerialConsole.print(':'); + SerialConsole.print(nowrtc.second(), DEC); + SerialConsole.println(); + + SerialConsole.print(F(" since midnight 1/1/1970 = ")); + SerialConsole.print(nowrtc.unixtime()); + SerialConsole.print("s = "); + SerialConsole.print(nowrtc.unixtime() / 86400L); + SerialConsole.println("d"); + + // calculate a date which is 7 days and 30 seconds into the future + DateTime future (nowrtc + TimeSpan(7,12,30,6)); + + SerialConsole.print(F(" now + 7d + 30s: ")); + SerialConsole.print(future.year(), DEC); + SerialConsole.print('/'); + SerialConsole.print(future.month(), DEC); + SerialConsole.print('/'); + SerialConsole.print(future.day(), DEC); + SerialConsole.print(' '); + SerialConsole.print(future.hour(), DEC); + SerialConsole.print(':'); + SerialConsole.print(future.minute(), DEC); + SerialConsole.print(':'); + SerialConsole.print(future.second(), DEC); + SerialConsole.println(); + + SerialConsole.println(); + delay(1000); + } + SerialConsole.println(F("End of test")); +} + +WII5RTC wii5RTC; diff --git a/WII5RTC.h b/WII5RTC.h new file mode 100644 index 0000000..b9c2007 --- /dev/null +++ b/WII5RTC.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5RTC.h + * @brief Real-time clock helper. + */ + +#ifndef WII5RTC_h +#define WII5RTC_h +#include + +// Why not inherit our WII5 class? +class WII5RTC { + public: + WII5RTC() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;} + virtual WII5_DRIVERS driverId() {return WII5DRIVER_RTC;} + void begin(); + void test(); + void setRTC(uint32_t t = 0); + float getTemperature(); + + protected: + bool testDone; +}; + +extern WII5RTC wii5RTC; + +#endif diff --git a/WII5RadioLoRa.cpp b/WII5RadioLoRa.cpp new file mode 100644 index 0000000..16362d0 --- /dev/null +++ b/WII5RadioLoRa.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5RadioLoRa.cpp + * @brief LoRa radio driver (RFM95) — optional, gated on WII5_RADIO_LORA. + */ + +/* + +WII5RadioLoRa + +*/ + +#include +#include + +#ifdef WII5_RADIO_LORA + +#include + +void WII5RadioLoRa::begin() { + powerSetPin(POWER_RADIO_PIN, POWER_RADIO_ON); + powerOn(true); + running = true; + // sh3dNodeRadio.begin(wii5BufferRadio, WII5_BUFFER_RADIO, &wii5RadioRF95); + // sh3dNodeNetwork.begin(); + sh3dNodeBroadcast.begin(); +} + +// start / stop — currently no-ops. Radio power is managed in begin(); modes +// don't (yet) toggle the radio on demand. Kept as overrides of the WII5Power +// virtuals so callers can still invoke them safely. +void WII5RadioLoRa::start(bool force) { + (void)force; +} + +void WII5RadioLoRa::stop(bool force) { + (void)force; +} + + +void WII5RadioLoRa::loop() { + sh3dNodeRadio.loop(); + if (passthrough && sh3dNodeRadio.lastValid) { + console.printf(F("Radio:record - yay. Length = %d. RSSI = %d."), + sh3dNodeRadio.lastLength, + sh3dNodeRadio.lastRadio.rssi, + sh3dNodeRadio.lastRadio.snr + ); + console.printf("DATA: "); + console.printf((char*)sh3dNodeRadio.buffer); + console.printf("\r\n"); + sh3dNodeRadio.lastClear(); + } + + sh3dNodeNetwork.loop(); + if (sh3dNodeNetwork.lastValid) { + console.printf(F("Radio:packet: type=%d\r\n"),(int(sh3dNodeNetwork.lastPacketType))); + } +} + +// No disable ATM +void WII5RadioLoRa::setPassthrough(bool in) { + passthrough = in; +} +bool WII5RadioLoRa::getPassthrough() { + return passthrough; +} +void WII5RadioLoRa::setDebug(bool in) { + debug = in; +} +bool WII5RadioLoRa::getDebug() { + return debug; +} + +WII5RadioLoRa wii5RadioLoRa; + +#endif diff --git a/WII5RadioLoRa.h b/WII5RadioLoRa.h new file mode 100644 index 0000000..66e4b27 --- /dev/null +++ b/WII5RadioLoRa.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5RadioLoRa.h + * @brief LoRa radio driver (RFM95) — optional, gated on WII5_RADIO_LORA. + */ + +#ifndef WII5RadioLoRa_h +#define WII5RadioLoRa_h +#ifdef WII5_RADIO_LORA + + +#include +#include + +class WII5RadioLoRa : public WII5Power { + public: + WII5RadioLoRa() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;} + virtual WII5_DRIVERS driverId() {return WII5DRIVER_RADIO;} + void begin(); + void loop(); + void start(bool force = false); + void stop(bool force = false); + + void setPassthrough(bool in); + bool getPassthrough(); + void setDebug(bool in); + bool getDebug(); + protected: + bool running; + bool passthrough; + bool debug; + uint32_t sendCount; +}; + +extern WII5RadioLoRa wii5RadioLoRa; + +#endif +#endif diff --git a/WII5SerialDebug.cpp b/WII5SerialDebug.cpp new file mode 100644 index 0000000..2d52e4e --- /dev/null +++ b/WII5SerialDebug.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5SerialDebug.cpp + * @brief Optional debug-serial port (SoftwareSerial) — gated on WII5_DEBUG_SERIAL. + */ + +#include +#include + +#ifdef WII5_DEBUG_SERIAL +SoftwareSerial SerialDebug(WII5_DEBUG_SERIAL_RX, WII5_DEBUG_SERIAL_TX); +#endif diff --git a/WII5SerialDebug.h b/WII5SerialDebug.h new file mode 100644 index 0000000..87feafb --- /dev/null +++ b/WII5SerialDebug.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5SerialDebug.h + * @brief Optional debug-serial port (SoftwareSerial) — gated on WII5_DEBUG_SERIAL. + */ + +#ifndef WII5SerialDebug_h +#define WII5SerialDebug_h + +#ifdef WII5_DEBUG_SERIAL +#include +extern SoftwareSerial SerialDebug; +#endif + +#endif diff --git a/WII5SerialManager.cpp b/WII5SerialManager.cpp new file mode 100644 index 0000000..cb5f0b1 --- /dev/null +++ b/WII5SerialManager.cpp @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5SerialManager.cpp + * @brief Serial-port helper: AT-response parsing, command queue. + */ + +/* + +WII5SerialManager - Base class for managed serial ports + +Sketch uses 106588 bytes (41%) of program storage space. Maximum is 258048 bytes. +Global variables use 5621 bytes (68%) of dynamic memory, leaving 2571 bytes for local variables. Maximum is 8192 bytes. + +Global variables use 5621 bytes (68%) of dynamic memory, leaving 2571 bytes for local variables. Maximum is 8192 bytes. + +*/ + +#include +#include +#include + +void WII5SerialManager::sendNewLine() { + stream->print(F("\r\n")); +} + +void WII5SerialManager::beginSerialManager() { + // console.log(LOG_DEBUG, F("SerialManager: Buffer size=%d"), bufMax); + processTimeout = 15000; // TODO This should be configurable +} +void WII5SerialManager::endSerialManager() { + // closeFile(); +} + +// No disable ATM +void WII5SerialManager::enableNmea() { + gps = new TinyGPSPlus(); +} +void WII5SerialManager::setPassthrough(bool in) { + passthrough = in; +} +bool WII5SerialManager::getPassthrough() { + return passthrough; +} +void WII5SerialManager::setDebug(bool in) { + debug = in; +} +bool WII5SerialManager::getDebug() { + return debug; +} +void WII5SerialManager::setCapture(bool in) { + capture = false; +} +bool WII5SerialManager::getCapture() { + return capture; +} + +void WII5SerialManager::setRecords(uint32_t in) { + recordTotal = in; +} +uint32_t WII5SerialManager::getRecords() { + return recordTotal; +} + +void WII5SerialManager::setTimeout(uint32_t in) { + processTimeout = in; +} + +uint32_t WII5SerialManager::getTimeout() { + return processTimeout; +} + +// TODO - Make this reusable - e.g. plug in different list of lines (callback?) +// Before calling: +// - probramCount (where to start), -programTotal (where to end) (not length) +// - sendCount = 0 +WII5_SERIALCMDS WII5SerialManager::sendAll(WII5SERIAL_LAST lastUntil) { + // Sent everyting - spin on + if (programCount > programTotal) { + return WII5SERIALCMD_OK; + } + + // First count - read line to buffer + if (sendCount == 0) { + programLine(programCount); + } + switch (sendAndWait()) { + // Still waiting. + case WII5SERIALCMD_WAITING: + if (debug && ((sendCount % 10000) == 0)) + console.log(LOG_DEBUG, F("SERIAL: waiting, sendCount=%lu"), sendCount); + break; + // Allow these to fall through - we will spin on lines even if error + case WII5SERIALCMD_TIMEOUT: + console.log(LOG_ERROR, F("SERIAL: TIMEOUT sending: %s"), buffer); + programCount++; // NOTE - Just spin on for now + sendCount = 0; + // TODO - consier return WII5SERIALCMD_TIMEOUT; + break; + case WII5SERIALCMD_ERROR: + console.log(LOG_ERROR, F("SERIAL: ERROR sending: %s"), buffer); + programCount++; // NOTE - Just spin on for now + sendCount = 0; + // TODO - consier return WII5SERIALCMD_ERROR; + break; + case WII5SERIALCMD_OK: + // TODO why? + if (debug) { + programLine(programCount); + } + programCount++; + sendCount = 0; + break; + default: + console.log(LOG_ERROR, F("SERIAL: Unknown sendAndWait return")); + programCount++; // NOTE - Just spin on for now + sendCount = 0; + break; + } + return WII5SERIALCMD_WAITING; +} +// TODO return true when done - either timeout or error or success +// - Instead could return: +// CMD_WAITING - Stil waiting +// CMD_OK - OK - success +// CMD_ERROR - Error returned to us (see buffer) +// CMD_TIMEOUT - Timeout +WII5_SERIALCMDS WII5SerialManager::sendAndWait(WII5SERIAL_LAST lastUntil) { + // First time in - send the string + if (sendCount == 0) { + // Send to IMU (this should allow for write, or print and with none, \r, \n or \r\n + // stream->write(buffer, strlen(buffer)); + stream->print(buffer); + sendNewLine(); + if (passthrough) + consoleBuffer(); + // if (debug) + // console.log(LOG_DEBUG, F("SerialManager: send line: %s"), buffer); + // Clear timeout + processWait = 0; + + // Clear any incoming data + last = WII5SERIALLAST_NONE; + bufCount = 0; fieldLoc = 0; + buffer[bufCount] = '\0'; + processMode = WII5SERIALPARSER_OKHUH; + } + // Future times + else { + // Did we get OK or ERROR? (could this get into other modes - e.g. LINE or RECORD) + if (last == lastUntil) { + sendCount = 0; + return WII5SERIALCMD_OK; + } + else if (last == WII5SERIALLAST_ERROR) { + sendCount = 0; + return WII5SERIALCMD_ERROR; + } + } + + if (processWait > processTimeout) { + sendCount = 0; + return WII5SERIALCMD_TIMEOUT; + } + // Increment counter and inform - WAITING + sendCount++; + return WII5SERIALCMD_WAITING; +} + +// SerialManager Things: +// - Turn Off +// - Start now +// - Start at X + + +void WII5SerialManager::loop() { + // Process the Serial - only if it is running - to consider this more... + if (stream) { + while (stream->available()) { + // One line at a time - to allow proessing return data + if (processSerial(stream->read())) { + break; + } + } + } +} + +void WII5SerialManager::start(bool force) {} +void WII5SerialManager::stop(bool force) {} + +void WII5SerialManager::setBuffer(char* buf, uint8_t sz) { + buffer = buf; + bufMax = sz; +} + +void WII5SerialManager::setBaudrate(uint32_t baud) { + console.log(LOG_WARN, F("Baudrate: Not supported by this module")); +} + +void WII5SerialManager::repl(uint32_t baud) { + uint8_t lastDot = 0; + + start(); + if (baud > 0) + setBaudrate(baud); + if (!stream) { + console.log(LOG_FATAL, F("SerialManager: Can not do REPL - no stream after start")); + return; + } + while (1) { + if (SerialConsole.available()) { + char c = SerialConsole.read(); + if (c == '.') + lastDot++; + else + lastDot = 0; + if (lastDot >= 3) { + SerialConsole.flush(); + stream->flush(); + stop(); + console.log(LOG_DEBUG, F("SerialManager: Completed REPL. User exit.")); + return; + } + stream->write(c); + } + + if (stream->available()) { + char c = stream->read() ; + SerialConsole.write(c); + } + } +} + +void WII5SerialManager::processBufferLine() { + if (debug) + console.log(LOG_DEBUG, F("SerialManager: received line: %s"), buffer); + last = WII5SERIALLAST_LINE; +} + +void WII5SerialManager::processBufferOK() { + if (bufCount < 1) + return; + + // If last 2 chars = "ok" + if ( + (strncmp_P(buffer,(PGM_P) F("OK"), 2) == 0) + || (strncmp_P(buffer,(PGM_P) F("ok"), 2) == 0) + ) { + if (debug) + console.log(LOG_DEBUG, F("SerialManager: received ok: len=%d, str=%s"), bufCount, buffer); + last = WII5SERIALLAST_OK; + } + + // READY + if ( + (strncmp_P(buffer,(PGM_P) F("READY"), 5) == 0) + || (strncmp_P(buffer,(PGM_P) F("ready"), 5) == 0) + ) { + if (debug) + console.log(LOG_DEBUG, F("SerialManager: received ready: len=%d, str=%s"), bufCount, buffer); + last = WII5SERIALLAST_READY; + } + + if (strncmp_P(buffer,(PGM_P) F("+CSQ:"), 5) == 0) { + lastVal = atol(&buffer[5]); + if (debug) + console.log(LOG_DEBUG, F("SerialManager: received CSQ: val=%lu, buffer=%s"), lastVal, buffer); + last = WII5SERIALLAST_ATCSQ; + } + + if (strncmp_P(buffer,(PGM_P) F("+SBDI:"), 6) == 0) { + // +SBDI:,,,,, + // MO status + // 0 = No SBD message to send + // 1 = SBD message successfully sent from the 9602 to the GSS + // 2 = An error occurred while attempting to send SBD message from 9602 to GSS + // MOMSN + // Unique number of this message sent, Keep + // MT status + // 0 = No SBD message to receive from the GSS + // 1 = SBD message successfully received from the GSS (how to get?) + // 2 = An error occurred while attempting to perform a mailbox check or receive a message from the GSS. + // MT length + // The MT length is the length in bytes of the mobile terminated SBD message received from the GSS. If no message was received, this eld will be zero + // MT queued + // MT queued is a count of mobile terminated SBD messages waiting at the GSS to be transferred to the 9602 + // + // TODO Note - these need to be split on "," and stored in an array + // ?recordVals[] + // lastVal = atol(&buffer[4]); + // Serial:< +SBDI: 1, 8, 0, 0, 0, 0 + lastVal = atol(&buffer[5]); + /* + uint8_t lastMOStatus; + uint16_t lastMOMSN; + uint8_t lastMTStatus; + uint16_t lastMTMSN; + uint16_t lastMTLength; + uint16_t lastMTQueued; + */ + if (debug) + console.log(LOG_DEBUG, F("SerialManager: received SBDI: buffer=%s"), buffer); + + // +SBDI:,,,,, + // Can't seem to ignore the spaces? + // sscanf_P(&buffer[6], PSTR("%d,%d,%d,%d,%d,%d"), lastMSOStatus, lastMOMSN, lastMTStatus, lastMTMSN, lastMTLength, lastMTQueued); + + for(fieldCount = 0; fieldCount < WII5_FIELD_MAX; fieldCount++) { + recordVals[fieldCount] = 0; + } + fieldCount = 0; + + p = strtok(&buffer[6], ","); + while (p) { + if (fieldCount < WII5_FIELD_MAX) { + recordVals[fieldCount] = atol(p); + p = strtok(NULL, ","); + fieldCount++; + } + else { + p = NULL; + } + } + + last = WII5SERIALLAST_ATSBDI; + } + + if (strncmp_P(buffer,(PGM_P) F("BOOT Version:"), 13) == 0) { + // TODO Note - these need to be split on "," and stored in an array + // ?recordVals[] + // lastVal = atol(&buffer[4]); + if (debug) + console.log(LOG_DEBUG, F("SerialManager: received BOOT Version: buffer=%s"), buffer); + last = WII5SERIALLAST_BOOTVERSION; + } + + // A number, but do we check the whole thing? + if (buffer[0] == '3') { + // lastVal = atol(&buffer[0]); + // TODO Keeping this? Howto + if (debug) + console.log(LOG_DEBUG, F("SerialManager: received a number=%s"), buffer); + last = WII5SERIALLAST_NUMBER; + } + + /* + if (debug) { + if (bufCount == 0) + console.log(LOG_DEBUG, F("SerialManager: failed ok: len=%d, str={NA}"), bufCount); + else if (bufCount >= 2) + console.log(LOG_DEBUG, F("SerialManager: failed ok: len-2=%c len-1=%c"), buffer[bufCount - 2], buffer[bufCount - 1]); + else + console.log(LOG_DEBUG, F("SerialManager: failed ok: len=%d, str=%s"), bufCount, buffer); + } + */ + // ??? last = WII5SERIALLAST_OK; +} + +// Sample Buffer Field - just assume longs +void WII5SerialManager::processBufferField(char *buf, uint8_t len) { + if (fieldCount < WII5_FIELD_MAX) { + // Check size is valid + if (len >= WII5SERIALMAANGER_FIELD_SIZE) { + console.log(LOG_FATAL, F("SerialManager: processBufferField too large len=%d, available=%d"), int(len), int(WII5SERIALMAANGER_FIELD_SIZE)); + return; + } + + // Copy the data + // TODO find all strncpy and do this !!! + memset(fieldBuffer, 0, sizeof(fieldBuffer)); + strncpy(fieldBuffer, buf, len); + + // Convert to Long + recordVals[fieldCount] = atol(buf); + fieldCount++; + } +} + +void WII5SerialManager::processRecord() { + if (bufCount == 0) return; + recordCount++; + last = WII5SERIALLAST_RECORD; +} + +// Basic line parsers +bool WII5SerialManager::processSerial(char in) { + // processMode + // NONE, LINE, RECORD, OKHUH, AT + if (processMode == WII5SERIALPARSER_NONE) return false; + + // TODO Untested binary1 mode - from Iridium.... + if (processMode == WII5SERIALPARSER_BINARY1) { + // TODO - maximum buffer size + + console.printf(F("# BINARY1 received = %d - %d\r\n"), bufCount, int(in)); + + binBuffer[bufCount] = in; + bufCount++; + + if (binBufferExpect == 0) { + // Look for length + if ((bufCount > 1) && (binBuffer[bufCount - 2] == '\0')) { + binBufferExpect = (uint8_t)binBuffer[bufCount - 1]; + console.printf(F("# BINARY1 Expect = %d\r\n"), binBufferExpect); + bufCount = 0; + } + // Hmm... too much guff... + else if (bufCount > 25) { + // Something went very wrong. + processMode = WII5SERIALPARSER_NONE; + console.printf(F("# BINARY1 No length provided\r\n")); + return false; + } + } + else if (bufCount >= (binBufferExpect + 2)) { + processMode = WII5SERIALPARSER_NONE; + return true; + } + + return false; + } + + // TODO return gps->encode(in); + if (gps) + gps->encode(in); + + // NOT a new line + if ( (in != '\n') && (in != '\r') ) { + // Process 1 field at a time (when comma) + if ( (processMode == WII5SERIALPARSER_RECORD) && (in == ',') ) { + processBufferField(&buffer[fieldLoc], bufCount - fieldLoc); + buffer[bufCount] = ','; + bufCount++; + fieldLoc = bufCount; // Next field will start here (could be null) + } + + // Keep whole record + else { + if ((bufCount + 1) < bufMax) { + buffer[bufCount] = in; + bufCount++; + buffer[bufCount] = '\0'; + } + else { + // TODO Log - just once, FATAL + // last = WII5SPARTONRETURN_ERROR; + } + } + } + + // New line - end of record + else { + writePassthrough(); + writeCapture(); + + // CSV Data + if (processMode == WII5SERIALPARSER_RECORD) { + processBufferField(&buffer[fieldLoc], bufCount - fieldLoc); + processRecord(); + } + + else if (processMode == WII5SERIALPARSER_OKHUH) { + processBufferOK(); + } + // An unknown line + else { + processBufferLine(); + } + + bufCount = 0; + // buffer[0] = '\0'; + fieldCount = 0; + fieldLoc = 0; + + return true; + } + return false; +} + +void WII5SerialManager::writePassthrough() { + if (passthrough && bufCount > 0) + consoleBuffer(true); // Input +} + +// Allow override of output of lines to log file (or even multiple) +void WII5SerialManager::writeCapture() { +} + +void WII5SerialManager::sendLine(uint16_t l) { + programLine(l); + stream->print(buffer); + sendNewLine(); + if (passthrough) + consoleBuffer(); +} + +// TODO: Move from buffer to wii5StringBuffer +void WII5SerialManager::programLine(uint16_t l) { + if (debug) + console.log(LOG_DEBUG, F("SerialManager: set command=%d buffer=%s"), l, buffer); +} + +void WII5SerialManager::consolePrefix(bool input) { + if (input) + console.printf(F("# Serial:< ")); + else + console.printf(F("# Serial:> ")); +} + +void WII5SerialManager::consoleBuffer(bool input, bool useLog) { + // TODO: This will fail if split comma and buffer gets added nulls + if (useLog) { + // TODO prefix? + // TODO Log level? + console.log(LOG_INFO, F("Serial:%s %s"), input ? "<" : ">", buffer); + } + else { + consolePrefix(input); + console.printf(buffer); + console.printNewLine(); + } +} + +WII5_DRIVERS WII5SerialManager::driverId() { + return WII5DRIVER_UNKNOWN; +} + +/* +void WII5SerialManager::displaySTATS() { + // TODO wii5Display.atStatsSend + console.printf(F("STATS:%s:SerialManager:TODO\r\n"), wii5Strings.strDriver(driverId())); +} +*/ + +// Where? and What? - assumes buffer now has the comma separated data correct +void WII5SerialManager::sendAtDataLine() { + // In other words - assume this serial port, instead of say.... Console + // if (!s) s = stream; - currently assumes console + + // NOTE: Steal the console buffer - so don't use it for debugging half way through here. + // You can still use console.print, just not printf, or log + + // TODO How to calculate CNC + wii5Display.atDataSend( + F("wii5direct"), // 8 or 64? + // NOTE: Must not add the "@" - it isn't used in CRC calculation + F("%s"), buffer + ); +} diff --git a/WII5SerialManager.h b/WII5SerialManager.h new file mode 100644 index 0000000..0e0ed9a --- /dev/null +++ b/WII5SerialManager.h @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5SerialManager.h + * @brief Serial-port helper: AT-response parsing, command queue. + */ + +#ifndef WII5SerialManager_h +#define WII5SerialManager_h + +#include +#include +#include +#include +#include +#include +#include + +// TODO Really? +#define WII5SERIALMAANGER_FIELD_SIZE 25 + +/* + +WII5SerialManager - Base class for managed serial ports + +Special and Shared Types + + WII5SERIAL_LAST + What was the last message we received + + WII5SERIAL_PARSERS + Which parser we currently using? + +*/ + +enum WII5SERIAL_PARSERS { + WII5SERIALPARSER_NONE, + WII5SERIALPARSER_LINE, + WII5SERIALPARSER_BINARY1, // likely to have multiple binary fomrats, this one, has the length in the first 2 bytes + // and last 2 bytes are checksum + WII5SERIALPARSER_RECORD, + WII5SERIALPARSER_OKHUH, + WII5SERIALPARSER_AT +}; + +enum WII5SERIAL_LAST { + WII5SERIALLAST_NONE, + WII5SERIALLAST_LINE, + WII5SERIALLAST_RECORD, + WII5SERIALLAST_OK, + WII5SERIALLAST_READY, + WII5SERIALLAST_ERROR, + WII5SERIALLAST_ATOK, // OK + WII5SERIALLAST_ATERR, + WII5SERIALLAST_ATSBDI, // including +SBDI and +SBDIX + WII5SERIALLAST_BOOTVERSION, + WII5SERIALLAST_NUMBER, + WII5SERIALLAST_ATCSQ // including +CSQ +}; + +class WII5SerialManager { + public: + WII5SerialManager() { + gps = NULL; + } + + virtual WII5_DRIVERS driverId(); + + virtual void loop(); + + void beginSerialManager(); + void endSerialManager(); + + void setPassthrough(bool in); + bool getPassthrough(); + void setDebug(bool in); + bool getDebug(); + void setCapture(bool in); + bool getCapture(); + + void repl(uint32_t baud = 0); + + virtual void sendNewLine(); + WII5_SERIALCMDS sendAll(WII5SERIAL_LAST lastUntil = WII5SERIALLAST_OK); + WII5_SERIALCMDS sendAndWait(WII5SERIAL_LAST lastUntil = WII5SERIALLAST_OK); + + virtual void setBaudrate(uint32_t baud); + + virtual void start(bool force = false); + virtual void stop(bool force = false); + + virtual void setRecords(uint32_t in); + virtual uint32_t getRecords(); + + virtual void writePassthrough(); + virtual void writeCapture(); + + // Public to allow easy access??? + TinyGPSPlus *gps; + void enableNmea(); + + // virtual void displaySTATS(); + virtual void sendAtDataLine(); + + void setTimeout(uint32_t in); + uint32_t getTimeout(); + + char* buffer; + char* binBuffer; + uint16_t binBufferCount; + uint16_t binBufferExpect; + + protected: + // Serial stream + // TODO Stream or HardwareSerial + HardwareSerial *stream; + + // Must be implemented in inherited class ! + + bool capture; // Capture to file - see openFile and closeFile + bool passthrough; // Passthrough to Serial console + bool debug; // Extra debugging to Serial Console + uint32_t sendCount; + + virtual bool processSerial(char in); + virtual void processBufferField(char *buf, uint8_t len); + virtual void processBufferOK(); + virtual void processBufferLine(); + virtual void processRecord(); + elapsedMillis processWait; + uint32_t processTimeout; + + virtual void programLine(uint16_t l); + virtual void sendLine(uint16_t l); + + virtual void consolePrefix(bool input = false); + virtual void consoleBuffer(bool input = false, bool useLog = false); + virtual void setBuffer(char* buf, uint8_t sz); + uint8_t bufMax; + char fieldBuffer[WII5SERIALMAANGER_FIELD_SIZE]; // TODO Configuration etc + uint8_t bufCount; + + uint8_t fieldLoc; + uint8_t fieldCount; + WII5SERIAL_PARSERS processMode; + uint8_t programCount; + uint8_t programTotal; + uint32_t recordCount; + uint32_t recordTotal; + WII5SERIAL_LAST last; + uint32_t lastVal; + uint32_t recordVals[WII5_FIELD_MAX]; + char *p; + + +}; + +#endif diff --git a/WII5SerialMaths.cpp b/WII5SerialMaths.cpp new file mode 100644 index 0000000..ea81f07 --- /dev/null +++ b/WII5SerialMaths.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5SerialMaths.cpp + * @brief Optional Maths-CPU serial port (SoftwareSerial) — gated on WII5_MATHS_SERIAL. + */ + +#include +#include + +#ifdef WII5_MATHS_SERIAL +SoftwareSerial SerialMaths(WII5_MATHS_SERIAL_RX, WII5_MATHS_SERIAL_TX); +#endif diff --git a/WII5SerialMaths.h b/WII5SerialMaths.h new file mode 100644 index 0000000..25c2db9 --- /dev/null +++ b/WII5SerialMaths.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5SerialMaths.h + * @brief Optional Maths-CPU serial port (SoftwareSerial) — gated on WII5_MATHS_SERIAL. + */ + +#ifndef WII5SerialMaths_h +#define WII5SerialMaths_h + +#ifdef WII5_MATHS_SERIAL +#include +extern SoftwareSerial SerialMaths; +#endif + +#endif diff --git a/WII5SerialStatus.cpp b/WII5SerialStatus.cpp new file mode 100644 index 0000000..275aa37 --- /dev/null +++ b/WII5SerialStatus.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5SerialStatus.cpp + * @brief Optional status-serial port (SoftwareSerial) — gated on WII5_STATUS_SERIAL. + */ + +#include +#include + +#ifdef WII5_STATUS_SERIAL +SoftwareSerial SerialStatus(WII5_STATUS_SERIAL_RX, WII5_STATUS_SERIAL_TX); +#endif diff --git a/WII5SerialStatus.h b/WII5SerialStatus.h new file mode 100644 index 0000000..3d7d68b --- /dev/null +++ b/WII5SerialStatus.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5SerialStatus.h + * @brief Optional status-serial port (SoftwareSerial) — gated on WII5_STATUS_SERIAL. + */ + +#ifndef WII5SerialStatus_h +#define WII5SerialStatus_h + +#ifdef WII5_STATUS_SERIAL +#include +extern SoftwareSerial SerialStatus; +#endif + +#endif diff --git a/WII5Setup.cpp b/WII5Setup.cpp new file mode 100644 index 0000000..9551f24 --- /dev/null +++ b/WII5Setup.cpp @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Setup.cpp + * @brief Boot-time hardware bring-up: pin defaults, peripherals, and serial ports. + */ + +/* + +Setup - Turn everything on etc + +*/ + +#include +#include +#include +#include +#include + +// Debugging etc - move to header file +#define DEBUG_SLEEP + +void WII5Setup::beginSafe() { + + // WDT + #ifdef WDT_RESET + pinMode(WDT_RESET, OUTPUT); + digitalWrite(WDT_RESET, LOW); + #endif + + // Make buzzer OUTPUT and OFF + #ifdef POWER_BUZZER_PIN + pinMode(POWER_BUZZER_PIN, OUTPUT); + digitalWrite(POWER_BUZZER_PIN, LOW); + #endif + + // 5V - FORCE On until sure Maths is off + #ifdef SHARED_5VOLT_ON + pinMode(SHARED_5VOLT_PIN, OUTPUT); + digitalWrite(SHARED_5VOLT_PIN, HIGH); + #endif + + // Maths - FORCE On until sure Maths is off + #ifdef POWER_MATHS_PIN + pinMode(POWER_MATHS_PIN, OUTPUT); + digitalWrite(POWER_MATHS_PIN, HIGH); + #endif + + // SD: Power + #if POWER_STORAGE_1_PIN>0 + pinMode(POWER_STORAGE_1_PIN, OUTPUT); + digitalWrite(POWER_STORAGE_1_PIN, HIGH); + #endif + #if POWER_STORAGE_2_PIN>0 + pinMode(POWER_STORAGE_2_PIN, OUTPUT); + digitalWrite(POWER_STORAGE_2_PIN, HIGH); + #endif + + // SD: Buffers + #ifdef STORAGE_SD1_MATHS_PIN + pinMode(STORAGE_SD1_MATHS_PIN, OUTPUT); + digitalWrite(STORAGE_SD1_MATHS_PIN, HIGH); + pinMode(STORAGE_SD2_MATHS_PIN, OUTPUT); + digitalWrite(STORAGE_SD2_MATHS_PIN, LOW); + #endif + + // Sub 1 and 2 + #ifdef POWER_SUBBOARD_1_PIN + pinMode(POWER_SUBBOARD_1_PIN, OUTPUT); + digitalWrite(POWER_SUBBOARD_1_PIN, LOW); + #endif + #ifdef POWER_SUBBOARD_2_PIN + pinMode(POWER_SUBBOARD_2_PIN, OUTPUT); + digitalWrite(POWER_SUBBOARD_2_PIN, LOW); + #endif + + // GPS + pinMode(POWER_GPS_PIN, OUTPUT); + digitalWrite(POWER_GPS_PIN, LOW); + + // Iridium + pinMode(POWER_COMMS_PIN, OUTPUT); + digitalWrite(POWER_COMMS_PIN, LOW); + + // Batt2 + #ifdef BATTERY_2_PIN + pinMode(BATTERY_2_PIN, OUTPUT); + digitalWrite(BATTERY_2_PIN, LOW); + #endif + + // Radio + #ifdef WII5_RADIO_LORA + pinMode(POWER_RADIO_PIN, OUTPUT); + digitalWrite(POWER_RADIO_PIN, LOW); + #endif + + // Strobe + pinMode(POWER_STROBE1_ON, OUTPUT); + digitalWrite(POWER_STROBE1_ON, LOW); + + #ifdef TIMING_SPARTON_RUN + pinMode(TIMING_SPARTON_RUN, OUTPUT); + #endif + #ifdef TIMING_SPARTON_CHAR + pinMode(TIMING_SPARTON_CHAR, OUTPUT); + #endif + #ifdef TIMING_SPARTON_RECORD + pinMode(TIMING_SPARTON_RECORD, OUTPUT); + #endif + #ifdef TIMING_SD_WRITE + pinMode(TIMING_SD_WRITE, OUTPUT); + #endif +} + +void WII5Setup::bootbeep() { + #ifdef POWER_BUZZER_PIN + #ifdef SHARED_5VOLT_ON + digitalWrite(SHARED_5VOLT_PIN, HIGH); + #endif + digitalWrite(POWER_BUZZER_PIN, HIGH); + delay(250); + digitalWrite(POWER_BUZZER_PIN, LOW); + delay(250); + digitalWrite(POWER_BUZZER_PIN, HIGH); + delay(250); + digitalWrite(POWER_BUZZER_PIN, LOW); + #endif +} + +void WII5Setup::shutdownbeep() { + #ifdef POWER_BUZZER_PIN + #ifdef SHARED_5VOLT_ON + digitalWrite(SHARED_5VOLT_PIN, HIGH); + #endif + delay(250); + digitalWrite(POWER_BUZZER_PIN, HIGH); + delay(125); + digitalWrite(POWER_BUZZER_PIN, LOW); + delay(125); + digitalWrite(POWER_BUZZER_PIN, HIGH); + delay(125); + digitalWrite(POWER_BUZZER_PIN, LOW); + delay(125); + digitalWrite(POWER_BUZZER_PIN, HIGH); + delay(125); + digitalWrite(POWER_BUZZER_PIN, LOW); + delay(125); + digitalWrite(POWER_BUZZER_PIN, HIGH); + delay(125); + digitalWrite(POWER_BUZZER_PIN, LOW); + delay(125); + digitalWrite(POWER_BUZZER_PIN, HIGH); + delay(125); + digitalWrite(POWER_BUZZER_PIN, LOW); + delay(125); + digitalWrite(POWER_BUZZER_PIN, HIGH); + delay(125); + digitalWrite(POWER_BUZZER_PIN, LOW); + delay(125); + digitalWrite(POWER_BUZZER_PIN, HIGH); + delay(125); + digitalWrite(POWER_BUZZER_PIN, LOW); + delay(125); + digitalWrite(POWER_BUZZER_PIN, HIGH); + delay(125); + digitalWrite(POWER_BUZZER_PIN, LOW); + #endif +} + +void WII5Setup::begin() { + // TODO 2024 - Check state of button 2 is on + // if so set special Maths flag - alwayson? + // If button 2 goes off at any point or button1 on, then always off is forever off? + setupConsole(); + setupPower(); + setupIO(); + setupNetwork(); + setupWII5(); + + #ifdef WII5_STORAGE_SDBLOCK + sdBlock.debug = false; + sdBlock.stream = &Serial; + sdBlock.begin(wii5Config.getDeviceId()); + #endif +} + + +#ifdef WII5_DEBUG_SERIAL +void cb(uint8_t l, char *buf) { + switch(l) { + case LOG_INFO: + SerialDebug.print(F("LOG:INFO:")); + break; + case LOG_WARN: + SerialDebug.print(F("LOG:WARN:")); + break; + case LOG_ERROR: + SerialDebug.print(F("LOG:ERROR:")); + break; + case LOG_DEBUG: + SerialDebug.print(F("LOG:DEBUG:")); + break; + case LOG_FATAL: + SerialDebug.print(F("LOG:FATAL:")); + break; + default: + SerialDebug.print(F("LOG:?:")); + break; + }; + SerialDebug.print(buf); + SerialDebug.println(); +} +#endif + +void WII5Setup::setupPower() { + if (_setupPower) return; + _setupPower = true; +} + +void WII5Setup::setupConsole() { + if (_setupConsole) return; + _setupConsole = true; + SerialConsole.begin(SerialConsole_Baud); + + // Hard coded BAD - Where to put this for reuse in WII5 buoys + // Maybe a macro in the Board file? + console.begin(wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, wii5BufferConsoleCmd, WII5_BUFFER_CONSOLE_CMD); + // TODO ? console.enableNmea(); + + // Here we add the main console as an OUTPUT only + #if defined(WII5_DEBUG_SERIAL) && defined(WII5_DEBUG_SERIAL_CONSOLE) + console.add(&SerialConsole, CONSOLE_DIRECTION_OUTPUT); + #else + console.add(&SerialConsole); + #endif + + // NOTE: We are using Iridium as it is currently only Binary code we need + #ifdef WII5_GPS + console.setBinaryBuffer(wii5BinaryIridium, WII5_IRIDIUM_BIN_MAX); + #endif + #ifdef WII5_DEBUG_SERIAL + SerialDebug.begin(WII5_DEBUG_SERIAL_BAUD); + #ifdef WII5_DEBUG_SERIAL_CONSOLE + console.add(&SerialDebug, CONSOLE_DIRECTION_BOTH); + #else + console.setCallbackFunction(cb); + console.setCallbackLevel(LOG_FATAL); + #endif + #endif + + #ifdef WII5_STATUS_SERIAL + SerialStatus.begin(WII5_STATUS_SERIAL_BAUD); + #endif + + #ifdef WII5_MATHS_SERIAL + SerialMaths.begin(WII5_MATHS_SERIAL_BAUD); + console.add(&SerialMaths, CONSOLE_DIRECTION_BOTH); + #endif + + console.setLevel(LOG_INFO); +} + +void WII5Setup::setupNetwork() { + if (_setupNetwork) return; + _setupNetwork = true; + #ifdef WII5_RADIO_LORA + wii5RadioLoRa.begin(); + #endif +} + +void WII5Setup::setupIO() { + if (_setupIO) return; + _setupIO = true; + // IO - LED, Buttons et al + sh3dNodeIO.begin(LED_1, LED_2, LED_3, LED_4, BUTTON_1, BUTTON_2, BUTTON_3, BUTTON_4); + + // SET LED to Fast Flash + #if LED_1>0 + sh3dNodeIO.led1Set( LED_SHORT ); + sh3dNodeIO.led1SetDefault(LED_SHORT); + sh3dNodeIO.led1SetTimeout(300000); + #endif + #if LED_2>0 + #ifdef POWER_BUZZER_PIN + // Off after 5 seconds + sh3dNodeIO.led2Set( LED_OFF ); + sh3dNodeIO.led2SetDefault(LED_OFF); + sh3dNodeIO.led2SetTimeout(10000); + #else + sh3dNodeIO.led2Set( LED_SLOW ); + sh3dNodeIO.led2SetDefault(LED_SLOW); + sh3dNodeIO.led2SetTimeout(10000); + #endif + #endif + #if LED_3>0 + sh3dNodeIO.led3Set( LED_SLOW ); + sh3dNodeIO.led3SetDefault(LED_SLOW); + sh3dNodeIO.led3SetTimeout(10000); + #endif + #if LED_4>0 + sh3dNodeIO.led4Set( LED_SLOW ); + sh3dNodeIO.led4SetDefault(LED_SLOW); + sh3dNodeIO.led4SetTimeout(10000); + #endif + +} + +void WII5Setup::setupWII5() { + if (_setupWII5) return; + _setupWII5 = true; + wii5Config.begin(); + #ifdef WII5_RTC + wii5RTC.begin(); + #endif + wii5Commands.begin(); + wii5Battery.begin(); + #ifdef WII5_GPS + wii5Gps.begin(); + #endif + #ifdef WII5_COMMS_IRIDIUM + wii5Iridium.begin(); + #endif + wii5Maths.begin(); + // wii5MPU9250.begin(); + wii5Weather_18B20.begin(); + #ifdef WII5_IMU_SPARTON + wii5Sparton.begin(); + #endif + wii5Communications.begin(); +} + +void WII5Setup::sleepBefore(bool MathsON) { + // Tell devices they are really off. + wii5Sparton.stop(true); + wii5Iridium.stop(true); + wii5Gps.off(); + sdBlock.cardForceClose(); + + #ifdef WII5_WDT_INTERNAL + wdt_disable(); + #endif + + // Turn off Shared, and Maths CPU + pinMode(SHARED_5VOLT_PIN, OUTPUT); + pinMode(POWER_MATHS_PIN, OUTPUT); + if (MathsON) { + digitalWrite(SHARED_5VOLT_PIN, HIGH); + digitalWrite(POWER_MATHS_PIN, HIGH); + } + else { + digitalWrite(SHARED_5VOLT_PIN, LOW); + digitalWrite(POWER_MATHS_PIN, LOW); + } + + // Storage SD Card + #if POWER_STORAGE_1_PIN>0 + pinMode(POWER_STORAGE_1_PIN, OUTPUT); + digitalWrite(POWER_STORAGE_1_PIN, LOW); + #endif + #if POWER_STORAGE_2_PIN>0 + pinMode(POWER_STORAGE_2_PIN, OUTPUT); + digitalWrite(POWER_STORAGE_2_PIN, LOW); + #endif + + // GPS, Radio, MPU and Strobe + pinMode(POWER_GPS_PIN, OUTPUT); + digitalWrite(POWER_GPS_PIN, LOW); + pinMode(POWER_COMMS_PIN, OUTPUT); + digitalWrite(POWER_COMMS_PIN, LOW); + pinMode(POWER_RADIO_PIN, OUTPUT); + digitalWrite(POWER_RADIO_PIN, LOW); + pinMode(POWER_STROBE1_ON, OUTPUT); + digitalWrite(POWER_STROBE1_ON, LOW); + + // Misc Controlling lines + pinMode(STORAGE_SD1_MATHS_PIN, INPUT); + pinMode(STORAGE_SD2_MATHS_PIN, INPUT); + pinMode(SPARTON_RESET, INPUT); + pinMode(RADIO_CS, INPUT); + pinMode(STORAGE_CS, INPUT); + + // LEDs 2024 Updated to OUTPUT LOW + #if LED_1>0 + pinMode(LED_1, OUTPUT); + digitalWrite(LED_1, LOW); + #endif + #if LED_2>0 + pinMode(LED_2, OUTPUT); + digitalWrite(LED_2, LOW); + #endif + #if LED_3>0 + pinMode(LED_3, OUTPUT); + #endif + + // Weather + pinMode(POWER_WEATHER_18B20_PIN, INPUT); + pinMode(POWER_WEATHER_PIN, INPUT); +} + +void WII5Setup::sleepAfter() { + // Really make sure still output + pinMode(SHARED_5VOLT_PIN, OUTPUT); + pinMode(POWER_MATHS_PIN, OUTPUT); + + // Storage SD Card + #if POWER_STORAGE_1_PIN>0 + pinMode(POWER_STORAGE_1_PIN, OUTPUT); + digitalWrite(POWER_STORAGE_1_PIN, HIGH); + #endif + #if POWER_STORAGE_2_PIN>0 + pinMode(POWER_STORAGE_2_PIN, OUTPUT); + #endif + + // GPS, Radio, MPU and Strobe + pinMode(POWER_GPS_PIN, OUTPUT); + pinMode(POWER_COMMS_PIN, OUTPUT); + pinMode(POWER_RADIO_PIN, OUTPUT); + pinMode(POWER_STROBE1_ON, OUTPUT); + + // Misc Controlling lines + pinMode(STORAGE_SD1_MATHS_PIN, OUTPUT); + pinMode(STORAGE_SD2_MATHS_PIN, OUTPUT); + pinMode(SPARTON_RESET, OUTPUT); + pinMode(RADIO_CS, OUTPUT); + pinMode(STORAGE_CS, OUTPUT); + + // LEDs + pinMode(LED_1, OUTPUT); + #if LED_2>0 + pinMode(LED_2, OUTPUT); + #endif + #if LED_3>0 + pinMode(LED_3, OUTPUT); + #endif + + // Weather + pinMode(POWER_WEATHER_18B20_PIN, OUTPUT); + pinMode(POWER_WEATHER_PIN, OUTPUT); + + #ifdef WII5_WDT_INTERNAL + wdt_enable(WDTO_8S); + #endif +} + +WII5Setup wii5Setup; diff --git a/WII5Setup.h b/WII5Setup.h new file mode 100644 index 0000000..3bdb246 --- /dev/null +++ b/WII5Setup.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Setup.h + * @brief Boot-time hardware bring-up: pin defaults, peripherals, and serial ports. + */ + +#ifndef WII5Setup_h +#define WII5Setup_h + +#include +#include +#include + +class WII5Setup : public WII5 { + public: + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_SETUP;} + void begin(); + void beginSafe(); + void bootbeep(); + void shutdownbeep(); + void setupPower(); + void setupConsole(); + void setupNetwork(); + void setupIO(); + void setupWII5(); + void sleepBefore(bool mathsOn = false); + void sleepAfter(); + protected: + bool _setupPower; + bool _setupIO; + bool _setupWII5; + bool _setupConsole; + bool _setupNetwork; +}; + +extern WII5Setup wii5Setup; +#endif diff --git a/WII5Sh3dConfig.cpp b/WII5Sh3dConfig.cpp new file mode 100644 index 0000000..931410d --- /dev/null +++ b/WII5Sh3dConfig.cpp @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Sh3dConfig.cpp + * @brief WII5 adapter for the Sh3d shared configuration layer. + */ + +/* + +TODO 2024 - Sh3dConfig vs Config + +*/ + +#include +#include "WII5Sh3dConfig.h" +#include "WII5Sh3dConsole.h" +#include + +/* ---------------------------------------------------------------------- + +*/ + +// TODO Move this to Start Up +void WII5Sh3dConfig::begin() { + initialized = true; + readConfig(); + updateRunCount(); +}; + +void WII5Sh3dConfig::readConfig() { + EEPROM.readBlock(configStart, dataConfig); + + if ( + (dataConfig.configType == CONFIG_TYPE) + && (dataConfig.configVersion == CONFIG_VERSION) + ) { + return; + } + + console.log(LOG_ERROR, F("!EEPROM old=%d,%d. new=%d,%d"), + dataConfig.configType, + dataConfig.configVersion, + CONFIG_TYPE, + CONFIG_VERSION + ); + + memset(&dataConfig, 0, sizeof(dataConfig)); + dataConfig.configType = CONFIG_TYPE; + dataConfig.configVersion = CONFIG_VERSION; + updateConfig(); +} + +void WII5Sh3dConfig::updateConfig() { + EEPROM.updateBlock(configStart, dataConfig); +} + +SH3D_TYPE_UNIQUEID WII5Sh3dConfig::getNodeId() { + if ( (dataConfig.nodeId < 10000) || (dataConfig.nodeId > 32000) ) + console.safeLog(LOG_ERROR, F("Invalid nodeId")); + return dataConfig.nodeId; +} + +// TODO Ways of getting id etc? +SH3D_TYPE_UNIQUEID WII5Sh3dConfig::setNodeId(SH3D_TYPE_UNIQUEID newId) { + dataConfig.nodeId = newId; + updateConfig(); +} + +SH3D_TYPE_COUNTER WII5Sh3dConfig::getRunCount() { + return dataConfig.runCount; +} +SH3D_TYPE_COUNTER WII5Sh3dConfig::updateRunCount() { + dataConfig.runCount++; + updateConfig(); + return dataConfig.runCount; +} +SH3D_TYPE_COUNTER WII5Sh3dConfig::clearRunCount(SH3D_TYPE_COUNTER setId) { + dataConfig.runCount = setId; + updateConfig(); + return dataConfig.runCount; +} + +SH3D_TYPE_COUNTER WII5Sh3dConfig::getRecordCount() { + return dataConfig.recordCount; +} +SH3D_TYPE_COUNTER WII5Sh3dConfig::updateRecordCount() { + dataConfig.recordCount++; + updateConfig(); + return dataConfig.recordCount; +} +SH3D_TYPE_COUNTER WII5Sh3dConfig::clearRecordCount(SH3D_TYPE_COUNTER setId) { + dataConfig.recordCount = setId; + updateConfig(); + return dataConfig.recordCount; +} + +SH3D_TYPE_COUNTER WII5Sh3dConfig::getStorageLogLast() { + return dataConfig.storageLogLast; +} +SH3D_TYPE_COUNTER WII5Sh3dConfig::updateStorageLogLast() { + dataConfig.storageLogLast++; + updateConfig(); + return dataConfig.storageLogLast; +} +SH3D_TYPE_COUNTER WII5Sh3dConfig::clearStorageLogLast(SH3D_TYPE_COUNTER setId) { + dataConfig.storageLogLast = setId; + updateConfig(); + return dataConfig.storageLogLast; +} + +SH3D_TYPE_COUNTER WII5Sh3dConfig::getStorageSensorLast() { + return dataConfig.storageSensorLast; +} +SH3D_TYPE_COUNTER WII5Sh3dConfig::updateStorageSensorLast() { + dataConfig.storageSensorLast++; + updateConfig(); + return dataConfig.storageSensorLast; +} +SH3D_TYPE_COUNTER WII5Sh3dConfig::clearStorageSensorLast(SH3D_TYPE_COUNTER setId) { + dataConfig.storageSensorLast = setId; + updateConfig(); + return dataConfig.storageSensorLast; +} + +uint16_t sizeExtra() { + // TODO return extraLen; + return extraLen; +} +void readExtra(char *data, uint16_t len, uint16_t offset) { + // TODO EEPROM.readBlock(data, extraStart + offset, len); +} + +void updateExtra(char *data, uint16_t len, uint16_t offset) { + // TODO EEPROM.updateData(data, extraStart + offset, len); + // EEPROM.updateBlock(extraStart, dataConfig); +} + +WII5Sh3dConfig sh3dNodeConfig; diff --git a/WII5Sh3dConfig.h b/WII5Sh3dConfig.h new file mode 100644 index 0000000..9309b8c --- /dev/null +++ b/WII5Sh3dConfig.h @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Sh3dConfig.h + * @brief WII5 adapter for the Sh3d shared configuration layer. + */ + +#ifndef WII5Sh3dConfig_h +#define WII5Sh3dConfig_h + +#include //assumes Arduino IDE v1.0 or greater +#include //assumes Arduino IDE v1.0 or greater +#include +#include + +/* + +Features: + * Automatic configuration depending on hardware: + * Size of EEPROM + * TODO + * Automatic Versioning + * Automatic counters + * EEPROM protection - by alternative locations + + * Ability to extend + * Passing in one or more STRUCTS for the unused areas + * Helpers, like counters + +*/ + +// ============================================================================== +// Address and Sizes +// ============================================================================== +// TODO Should be variable, this needs lots of work +#define totalLen 512 +#define totalStart 0 +#define configStart 0 +#define configLen 128 +#define extraStart 128 +#define extraLen 384 + + +// ============================================================================== +// Signature (experimenting) +// ============================================================================== +// TODO HARD CODED FOR NOW +#define CONFIG_TYPE 114 +#define CONFIG_VERSION 12 + +// ============================================================================== +// Main Struct +// ============================================================================== +typedef struct { + // Internal - config data - might need more? + SH3D_TYPE_PACKET_TYPE configType; // Config Type + SH3D_TYPE_PACKET_VERSION configVersion; // Config Versio + + // Device ID + SH3D_TYPE_UNIQUEID nodeId; + + // Counters - To be moved around + SH3D_TYPE_COUNTER runCount; // Run ID - New ID each time device is booted + SH3D_TYPE_COUNTER recordCount; // Records, likes sesnsor data + + // Some common and Basic configurables about this device + // TODO how often - eg. do we want to do it every minute, every hour, daily, etc + + // Hard coded for Storage System - needs work + SH3D_TYPE_COUNTER storageLogLast; + SH3D_TYPE_COUNTER storageSensorLast; + +} typeConfig; + +class WII5Sh3dConfig { + public: + + void begin( ); + bool initialized = false; + + // Do reads + void readConfig(); + void updateConfig(); + + SH3D_TYPE_UNIQUEID getNodeId(); + SH3D_TYPE_UNIQUEID setNodeId(SH3D_TYPE_UNIQUEID newId); + + SH3D_TYPE_COUNTER getRunCount(); + SH3D_TYPE_COUNTER updateRunCount(); + SH3D_TYPE_COUNTER clearRunCount(SH3D_TYPE_COUNTER setId = 0); + + // Record ID for Sensors + SH3D_TYPE_COUNTER getRecordCount(); + SH3D_TYPE_COUNTER updateRecordCount(); + SH3D_TYPE_COUNTER clearRecordCount(SH3D_TYPE_COUNTER setId = 0); + + // Storage + SH3D_TYPE_COUNTER getStorageLogLast(); + SH3D_TYPE_COUNTER updateStorageLogLast(); + SH3D_TYPE_COUNTER clearStorageLogLast(SH3D_TYPE_COUNTER setId = 0); + SH3D_TYPE_COUNTER getStorageSensorLast(); + SH3D_TYPE_COUNTER updateStorageSensorLast(); + SH3D_TYPE_COUNTER clearStorageSensorLast(SH3D_TYPE_COUNTER setId = 0); + + // Not sure I like these names, `` + uint16_t sizeExtra(); + uint16_t startExtra(); + void readExtra(char *data, uint16_t len, uint16_t offset); + void updateExtra(char *data, uint16_t len, uint16_t offset); + + // Storage Location etc - as we can't errase a whole device + bool getFlashErased(); + bool setFlashErased(bool in); + + + protected: + typeConfig dataConfig; // Main Config Data + +}; + +extern WII5Sh3dConfig sh3dNodeConfig; + +#endif diff --git a/WII5Sh3dConsole.cpp b/WII5Sh3dConsole.cpp new file mode 100644 index 0000000..306079d --- /dev/null +++ b/WII5Sh3dConsole.cpp @@ -0,0 +1,658 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Sh3dConsole.cpp + * @brief Console abstraction: routes printf/log to one or more serial streams. + */ + +/* + + Console code + + TODO: 2024 Check this is the only copy of console and simplify + + Map console to multiple outputs. Use Printf and a buffer. Automatically + support PROGMEM (Flash memory), String object and char pointers. + + * TODO This should be callbacks + * NMEA Strings updated - do This + * Etc + + * TODO Binary is almost certainly out by one start/end or both + + * TODO Write test code that sends streams: + * NMEA - obvious one + * @CSV + * !Binary + + Console console(); + void setup() { + SerialConsole.begin(115200); + console.begin(); + console.add(&SerialConsole); + + console.printf(F("Hello world")); + + * Rename to something better than console + * Release open source on github Sh3d + * Add print from SD card - ability to get and print data form a SD card out + * Add print from PROGMEM arrays + * Add print from SPIFlash + + NOTE on NMEA devices + * NMEA is always processed from incoming data on the consoles + * If a second "TinyGPS++" is required, you can addNmea stream instead + * OR even better, just add a stream as add(&HWSerial, CONSOLE_DIRECTION_INPUT); + + Restructure to simpler methods: + * BEFORE + = Sketch size 15378 + * AFTER + = Sketch size 15046 (saved 340 bytes) + + Restructure to error numbers: + * Sh3dNodeErrors + * console.error(n) + * console.safeError(n) + * uint16_t - 01..65 modules split, 1000 message per module + * Could we add an optional long? +*/ +#include "Arduino.h" +#include +#include +#include +// printf support +// Just a string +void WII5Sh3dConsole::print(char *out) { + for(byte i = 0; i < CONSOLE_STREAMS; i++) { + if ( + streams[i] + && ( + (streamsDirection[i] == CONSOLE_DIRECTION_BOTH) + || (streamsDirection[i] == CONSOLE_DIRECTION_OUTPUT) + ) + ) { + streams[i]->print(out); + #ifdef CONSOLE_DEBUG_FLUSH + streams[i]->flush(); + #endif + } + } +} + +// sprintf - STREAM printf +void WII5Sh3dConsole::sprintf(Stream *s, const __FlashStringHelper *out, ... ) { + if (!check()) return; + va_list argptr; + va_start(argptr, out); + VSNPRINTF(printBuffer, bufferMax, (const char *)out, argptr); + va_end(argptr); + s->print(printBuffer); +} + +// printf support +void WII5Sh3dConsole::printf(char* out, ...) { + if (!check()) return; + va_list argptr; + va_start(argptr, out); + vsnprintf(printBuffer, bufferMax, out, argptr); + va_end(argptr); + print(printBuffer); +} + +// Convert FLASH String to String +void WII5Sh3dConsole::printf(const __FlashStringHelper *out, ... ){ + if (!check()) return; + va_list argptr; + va_start(argptr, out); + VSNPRINTF(printBuffer, bufferMax, (const char *)out, argptr); + va_end(argptr); + print(printBuffer); +} + +// TODO Move to Sh3dNodeErrors +void WII5Sh3dConsole::printPrefix(uint8_t level) { + switch(level) { + case LOG_INFO: + printf(F(CONSOLE_PRE_INFO)); + break; + case LOG_WARN: + printf(F(CONSOLE_PRE_WARN)); + break; + case LOG_ERROR: + printf(F(CONSOLE_PRE_ERROR)); + break; + case LOG_DEBUG: + printf(F(CONSOLE_PRE_DEBUG)); + break; + case LOG_FATAL: + printf(F(CONSOLE_PRE_FATAL)); + break; + default: + // Do we need unknown? + break; + } + printDateTime(); + printf(F(CONSOLE_POST_TIME)); +} + +// printf support +void WII5Sh3dConsole::log(uint8_t l, char* out, ...) { + if (level < l) return; + if (!check()) return; + printPrefix(l); + va_list argptr; + va_start(argptr, out); + vsnprintf(printBuffer, bufferMax, out, argptr); + va_end(argptr); + print(printBuffer); + printNewLine(); +} + +// Convert FLASH String to String +void WII5Sh3dConsole::log(uint8_t l, const __FlashStringHelper *out, ... ){ + if (level < l) return; + if (!check()) return; + printPrefix(l); + va_list argptr; + va_start(argptr, out); + VSNPRINTF(printBuffer, bufferMax, (const char *)out, argptr); + va_end(argptr); + print(printBuffer); + printNewLine(); +} + +// TODO - Compile time option to remove unused entries - e.g We may not support String +void WII5Sh3dConsole::safeLog(uint8_t l, char* out, ...) { + if (!check()) return; + printPrefix(l); + va_list argptr; + va_start(argptr, out); + vsnprintf(printBuffer, bufferMax, out, argptr); + va_end(argptr); + print(printBuffer); + printNewLine(); + flush(); +} + +void WII5Sh3dConsole::safeLog(uint8_t l, const __FlashStringHelper *out, ... ){ + if (!check()) return; + printPrefix(l); + va_list argptr; + va_start(argptr, out); + VSNPRINTF(printBuffer, bufferMax, (const char *)out, argptr); + va_end(argptr); + print(printBuffer); + printNewLine(); + flush(); +} + +void WII5Sh3dConsole::printDateTime(uint32_t t) { + if (t == 0) { + if (!displayDateTime) return; // Automatic mode + #ifdef _Time_h + t = now(); + #endif + } + // uint16_t - date.year, + // uint8_t - date.month, date.day, + // uint8_t - time.hour, time.minute, time.second + sprintf_P(printBuffer, PSTR("%04d-%02d-%02dT%02d:%02d:%02d"), + year(t), + int(month(t)), + int(day(t)), + int(hour(t)), + int(minute(t)), + int(second(t)) + ); + print(printBuffer); +} + +void WII5Sh3dConsole::loop() { + // Reset cmd + if (cmd) { + cmdCount = 0; + csv_count = 0; + csv_last = 0; + cmdType = 0; + cmd = '\0'; + } + // Reset Binary + if (binAvailable) { + binAvailable = false; + binReceiveCount = 0; + } + + for(byte i = 0; i < CONSOLE_STREAMS; i++) { + // NOTE - while important, but can be too much - limit? + if ( + (streamsDirection[i] == CONSOLE_DIRECTION_BOTH) + || (streamsDirection[i] == CONSOLE_DIRECTION_INPUT) + ) { + while (streams[i] && streams[i]->available()) + processCmd(streams[i]->read()); + } + } +} + +void WII5Sh3dConsole::processCsv() { + // TODO check for csv_count max and cmdCount max Etc + + // Point to the start of the last strinb before the comma + csv_str[csv_count] = &cmdBuffer[csv_last]; + + // Null the ',' + cmdBuffer[cmdCount] = '\0'; + + // Parse to ingeger + csv_uint32[csv_count] = atol(csv_str[csv_count]); + + // Next character + csv_last = cmdCount + 1; + + // console.log(LOG_DEBUG, F("CSV found comma last=%d, count=%d, str=%s, lu=%lu"), int(csv_last), int(csv_count), csv_str[csv_count], csv_uint32[csv_count]); + + csv_count++; +} + +// processCmd +void WII5Sh3dConsole::processCmd(char in) { + // DO Timeout + if ( (cmdCount > 0) && (waitInput > CONSOLE_TIMEOUT) ) { + printf(F("# CONSOLE: Timeout for new Serial Command. Max time = %d seconds"), int(CONSOLE_TIMEOUT / 1000)); + printNewLine(); + cmdCount = 0; + binAvailable = false; + binReceiveCount = 0; + } + + // BINARY Data mode - keep here + if (binEnable && (cmdType == CONSOLE_TYPE_BINDATA_ID)) { + if (cmdCount == 0) { + printf(F("# FATAL ERROR - Must not be in BINDATA mode with no count, Invalid mode")); printNewLine(); + cmdCount = 0; + cmdType = 0; + } + + binBuffer[binReceiveCount] = in; + binReceiveCount++; + + if (binReceiveCount >= binReceiveExpect) { + // COMPLETE ! + cmdType = 0; + cmdCount = 0; + binAvailable = true; + return; + } + + if (binReceiveCount >= binMax) { + // TODO This should NOT be allowed to happen - since we check below + safeLog(LOG_FATAL, F("CONSOLE: Binary incoming data too large")); + cmdCount = 0; + cmd = '\0'; + val = 0; + csv_last = 0; + csv_count = 0; + binReceiveCount = 0; + } + + // Rest of function is TEXT processing - leave now + return; + } + + // TEXT Mode - NOT a new line, keep data + if ( (in != '\n') && (in != '\r') ) { + // First character - see if it is $ + if ( (cmdCount == 0) ) { + // Reset timeout + waitInput = 0; + + // $ = NMEA = 1 + if (in == CONSOLE_TYPE_NMEA_C0) { + cmdType = CONSOLE_TYPE_NMEA_ID; + } + + // CSV + else if (in == CONSOLE_TYPE_CSV_C0) { + csv_last = 1; // Skip the "@" + // Start all at Zero + for (csv_count = 0; csv_count < CONSOLE_TYPE_CSV_LEN; csv_count++) { + csv_uint32[csv_count] = 0; + } + csv_count = 0; + cmdType = CONSOLE_TYPE_CSV_ID; + // console.log(LOG_DEBUG, F("CSV START last=%d, count=%d"), int(csv_last), int(csv_count)); + } + + // BINARY + else if (binEnable && (in == CONSOLE_TYPE_BIN_C0)) { + cmdType = CONSOLE_TYPE_BIN_ID; + binReceiveCount = 0; + binReceiveExpect = 0; + binReceiveCRC = 0; // Optional? + } + + // Else assume standard ending in ";" + else { + cmdType = CONSOLE_TYPE_OTHER_ID; + } + } + + // Keep cmdBuffer if not NMEA + if ( + // Keep Other + cmdType == CONSOLE_TYPE_OTHER_ID // Keep for external use + || cmdType == CONSOLE_TYPE_CSV_ID // Keep and process on the fly + || cmdType == CONSOLE_TYPE_NMEA_ID // NMEA + || cmdType == CONSOLE_TYPE_BIN_ID + ) { + + // TODO test out by one + if (cmdCount < (consoleMax - 1)) { + cmdBuffer[cmdCount] = in; + + // TODO ',' should be #define in case we use : or tab or something + if (cmdType == CONSOLE_TYPE_CSV_ID && in == ',') { + processCsv(); + } + + cmdCount++; + cmdBuffer[cmdCount] = '\0'; + } + } + } + + // TEXT MODE - New Line - process - NOTE: This may get called twice (\r then \n) + // - Once leaving this, the only way for a new command to start + // is to set cmdCount = 0 + else { + // Do nothing with this + if (cmdType == CONSOLE_TYPE_IGNORE_ID) { + } + + // Blank everything + else if (cmdType == CONSOLE_TYPE_DESTROY_ID) { + cmdCount = 0; + cmd = '\0'; + val = 0; + csv_last = 0; + csv_count = 0; + } + + // Binary + else if (binEnable && (cmdType == CONSOLE_TYPE_BIN_ID)) { + if (cmdCount < 2) { + printf(F("# CONSOLE: Error, no size requests for binary data")); printNewLine(); + printf(F("BINARY ERROR: No size")); printNewLine(); + cmdCount = 0; + cmdType = 0; + } + else { + val = atol(&cmdBuffer[1]); + if (val >= binMax) { + printf(F("# CONSOLE: Request Binary larger than buffer")); printNewLine(); + printf(F("BINARY ERROR: Too large")); printNewLine(); + cmd = '\0'; + cmdType = 0; + cmdCount = 0; + } + else { + printf(F("# CONSOLE: Request Binary size=%d"), binReceiveExpect); printNewLine(); + printf(F("BINARY OK: Ready to send")); printNewLine(); + binReceiveExpect = val; + binReceiveCount = 0; + cmdType = CONSOLE_TYPE_BINDATA_ID; + } + } + } + + // Add optional ending, or even CRC + else if (cmdType == CONSOLE_TYPE_OTHER_ID) { + if (echo) + log(LOG_DEBUG, F("CONSOLE Receved: %s"), cmdBuffer); + // Check we have a buffe? + if (cmdCount >= (consoleMax - 1)) { + // printf(F("# CONSOLE overflow: cmd size - c=%d of %d\r\n"), cmdCount, consoleMax); + printf(F("# CONSOLE cmd overflow")); + printNewLine(); + cmdCount = 0; + cmdType = CONSOLE_TYPE_IGNORE_ID; + } + else if (cmdBuffer[cmdCount - 1] != ';') { + printf(F("# CONSOLE: standard input must end in ; count=%d, last=%d"), cmdCount, (uint8_t)cmdBuffer[cmdCount =- 1]); + printNewLine(); + cmdCount = 0; + cmdType = CONSOLE_TYPE_IGNORE_ID; + } + else { + cmd = cmdBuffer[0]; + val = atol(&cmdBuffer[1]); + cmdType = CONSOLE_TYPE_IGNORE_ID; + } + } + else if ( cmdType == CONSOLE_TYPE_NMEA_ID ) { + // Ignore the NMEA - already processed + // TODO: Maybe mark the buffer as now a copy of the NMEA for passthrough? + } + else if ( cmdType == CONSOLE_TYPE_CSV_ID ) { + cmdCount++; // Spin on, so this will be like the "," where we add the null + processCsv(); + cmd = CONSOLE_TYPE_CSV_C0; + cmdType = CONSOLE_TYPE_IGNORE_ID; + if (echo) { + printPrefix(LOG_DEBUG); + printf(F("CONSOLE Received (@): ")); + for (i = 0; i < console.getCsvCount(); i++) { + printf(console.getCsvBuffer(i)); + printf(","); + } + printNewLine(); + } + } + } +} + +bool WII5Sh3dConsole::available(){ + return cmd != '\0'; +} + +// begin - using External Buffers +void WII5Sh3dConsole::begin(char* buf, uint16_t bufLen, char* cmdBuf, uint16_t cmdLen) { + // TODO + echo = true; + + printBuffer = buf; + bufferMax = bufLen; + cmdBuffer = cmdBuf; + consoleMax = cmdLen; + cmdType = 0; + setDateTime(); +} + +// begin - Allocate the buffers and get things going +void WII5Sh3dConsole::begin(uint16_t bufLen, uint16_t cmdLen) { + if ( (bufLen + cmdLen) >= freeMemory() ) + internalErrorNotEnoughMemory(); + else { + printBuffer = (char*)malloc(bufLen); + if (printBuffer == NULL) + internalErrorNotEnoughMemory(); + else + bufferMax = bufLen; + + cmdBuffer = (char*)malloc(cmdLen); + if (cmdBuffer == NULL) + internalErrorNotEnoughMemory(); + else + consoleMax = cmdLen; + + cmdType = 0; + setDateTime(); + } +} + +void WII5Sh3dConsole::internalErrorNotEnoughMemory() { + SerialConsole.println(F("CONSOLE IntErr: failbuf")); +} +void WII5Sh3dConsole::internalErrorNoBufferStream() { + SerialConsole.println(F("CONSOLE IntErr: buf, stream")); +} + +bool WII5Sh3dConsole::check() { + if (streamCount == 0) { + internalErrorNoBufferStream(); + return false; + } + if (bufferMax == 0) { + internalErrorNoBufferStream(); + return false; + } + return true; +} + +void WII5Sh3dConsole::clearCommand() { + cmdCount = 0; + csv_count = 0; + csv_last = 0; + cmdType = 0; + cmd = '\0'; +} + +void WII5Sh3dConsole::clearCallback() { + callback_default = false; + callback = NULL; +} +void WII5Sh3dConsole::setCallbackFunction(CallbackFunction f) { + callback_default = false; + callback = f; +} +void WII5Sh3dConsole::setCallbackLevel(uint8_t l) { + callback_level = l; +} +uint8_t WII5Sh3dConsole::getCallbackLevel() { + return callback_level; +} + +// Add a stream - currently very limited (0 NEITHER, 1 INPUT, 2 OUTPUT, 3 BOTH) +bool WII5Sh3dConsole::add(Stream *s, uint8_t direction) { + for(byte i = 0; i < CONSOLE_STREAMS; i++) { + if (streams[i] == s) { + return true; + } + else if (! streams[i]) { + streams[i] = s; + streamsDirection[i] = direction; + streamCount++; + return true; + } + } + // Internal Error ! + return false; +} + +bool WII5Sh3dConsole::remove(Stream *s) { + for(byte i = 0; i < CONSOLE_STREAMS; i++) { + if (streams[i] == s) { + streams[i] = NULL; + streamsDirection[i] = 0; + streamCount--; + return true; + } + } + return false; +} + +void WII5Sh3dConsole::setLevel(byte l) { + level = l; +} + +// Flush sending....no longer doing inputs +void WII5Sh3dConsole::flush() { + for(byte i = 0; i < CONSOLE_STREAMS; i++) { + if (streams[i]) { + streams[i]->flush(); + } + } +} + +void WII5Sh3dConsole::printNewLine() { + printf(F("\r\n")); +} + +bool WII5Sh3dConsole::enableBinary() { + binEnable = true; +} +bool WII5Sh3dConsole::disableBinary() { + binEnable = false; +} +void WII5Sh3dConsole::setBinaryBuffer(char *buf, uint16_t len) { + binBuffer = buf; + binMax = len; + binEnable = true; +} + +// Pretty Print Binary +#define PERLINE 10 +void WII5Sh3dConsole::printBinary(char* buf, uint16_t len) { + + printf(F("LEN = %d\r\n"), len); + uint16_t i = 0; + while (i < len) { + + for (uint16_t x = 0; x < PERLINE; x++) { + if ( (i+x) < len) { + printf(F("0x%.2X "), (uint8_t)buf[i+x]); + } + else { + printf(F(" "), (int16_t)buf[i+x]); + } + } + + + printf(F(" |")); + for (uint16_t x = 0; x < PERLINE; x++) { + if ( + ((i+x) < len) + && ((uint8_t)buf[i+x] >= 32) + && ((uint8_t)buf[i+x] < 127) + ) { + printBuffer[x] = buf[i+x]; + } + else { + printBuffer[x] = ' '; + } + + } + printBuffer[PERLINE] = '\0'; + print(printBuffer); + printf(F("|\r\n")); + + i = i + PERLINE; + } + + printNewLine(); +} + +void WII5Sh3dConsole::printBits(void* buf, uint16_t len) { + for (uint16_t x = 0; x < len; x++) { + void* n = buf + (x/8); + printf(bitRead(*(uint8_t*)n, x % 8) ? "1" : "0"); + } + printNewLine(); + SerialConsole.print(F("BIN: ")); + SerialConsole.print(*(uint16_t*)buf, BIN); + SerialConsole.println(); +} + +void WII5Sh3dConsole::setEcho(bool in) { + echo = in; +} +bool WII5Sh3dConsole::getEcho() { + return echo; +} + +WII5Sh3dConsole console; diff --git a/WII5Sh3dConsole.h b/WII5Sh3dConsole.h new file mode 100644 index 0000000..05adee1 --- /dev/null +++ b/WII5Sh3dConsole.h @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Sh3dConsole.h + * @brief Console abstraction: routes printf/log to one or more serial streams. + */ + +#ifndef WII5Sh3dConsole_h +#define WII5Sh3dConsole_h + + +#include +#include +#include +#include + +// TODO Default serial port? +#define SerialConsole Serial + +// Enable debug flush, if you want regular flushing for crashes etc +#define CONSOLE_DEBUG +// #define CONSOLE_DEBUG_FLUSH + +#define CONSOLE_STREAMS 2 // TODO - Can we allocate more if needed? + +// Enable to allow Strings in the print lines instead of F() and "" +// #define CONSOLE_USE_STRINGS + +// Default levels +#define LOG_NONE -1 +#define LOG_FATAL 0 +#define LOG_ERROR 1 +#define LOG_WARN 2 +#define LOG_INFO 3 +#define LOG_DEBUG 4 +#define LOG_ALL 5 + +#define CONSOLE_DIRECTION_NONE 0 +#define CONSOLE_DIRECTION_INPUT 1 +#define CONSOLE_DIRECTION_OUTPUT 2 +#define CONSOLE_DIRECTION_BOTH 3 + +// Default sizex - overloaded in begin +#if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__) +#define CONSOLE_BUFFER_SIZE 100 +#define CONSOLE_COMMAND_SIZE 25 +#define VSNPRINTF vsnprintf_P +#elif defined(CORE_TEENSY) +#define CONSOLE_BUFFER_SIZE 200 +#define CONSOLE_COMMAND_SIZE 75 +#define VSNPRINTF vsnprintf +#else +#define CONSOLE_BUFFER_SIZE 100 +#define CONSOLE_COMMAND_SIZE 25 +#define VSNPRINTF vsnprintf_P +#endif + + +#define CONSOLE_TYPE_NMEA_ID 1 +#define CONSOLE_TYPE_NMEA_C0 '$' +#define CONSOLE_TYPE_NMEA_NAME "NMEA" + +#define CONSOLE_TYPE_CSV_ID 2 +#define CONSOLE_TYPE_CSV_C0 '@' +#define CONSOLE_TYPE_CSV_NAME "CSV" +#define CONSOLE_TYPE_CSV_LEN 10 + +#define CONSOLE_TYPE_BIN_ID 3 +#define CONSOLE_TYPE_BIN_C0 '!' +#define CONSOLE_TYPE_BIN_NAME "Binary" +#define CONSOLE_TYPE_BINDATA_ID 4 +#define CONSOLE_TYPE_BINDATA_NAME "BinaryData" + +#define CONSOLE_TYPE_IGNORE_ID 97 +#define CONSOLE_TYPE_DESTROY_ID 98 + +#define CONSOLE_TYPE_OTHER_ID 99 +#define CONSOLE_TYPE_OTHER_C0 '' +#define CONSOLE_TYPE_OTHER_NAME "Standard" + +// NOTE: Make sure all of these are wrapped in F() for prints +#define CONSOLE_PRE_INFO "# INFO: " +#define CONSOLE_PRE_WARN "# WARN: " +#define CONSOLE_PRE_ERROR "# ERROR: " +#define CONSOLE_PRE_DEBUG "# DEBUG: " +// Consider better name +#define CONSOLE_PRE_FATAL "# IMPORTANT/NOTSTORED: " +#define CONSOLE_POST_TIME " - " + +typedef void (*CallbackFunction) (uint8_t l, char *buf); + +// 10 seconds. Really 1 is enough, but slow type debug @ commands maybe +#define CONSOLE_TIMEOUT 30000 + +/** + * @brief Console abstraction over multiple Stream objects. + * + * Routes formatted output (printf, log) and parses incoming command frames + * across one or more Stream sources. Recognises three frame syntaxes: + * - NMEA ($-prefixed, CRC-checked) + * - CSV (@-prefixed, comma-separated) + * - Binary (!-prefixed, length+CRC framed) + * + * Most code in this firmware logs through `console` rather than calling + * `Serial.print` directly so that output goes wherever the board is + * configured for. + */ +class WII5Sh3dConsole { + public: + /** @brief One-time bring-up. Allocates internal print + command buffers. */ + void begin(uint16_t bufSize = CONSOLE_BUFFER_SIZE, uint16_t cmdSize = CONSOLE_COMMAND_SIZE); + /** @brief One-time bring-up using caller-supplied buffers. */ + void begin(char* buf, uint16_t bufSize, char* cmdBuf, uint16_t cmdSize); + + /** @brief Add a Stream source to the console (typically a Serial port). */ + bool add(Stream *s, uint8_t direction = CONSOLE_DIRECTION_BOTH); + /** @brief Remove a previously-added Stream. */ + bool remove (Stream *s); + + /** @brief Enable Binary frame parsing (caller must provide a buffer). */ + bool enableBinary(); + /** @brief Disable Binary frame parsing. */ + bool disableBinary(); + /** @brief Set the buffer used to receive Binary frames. */ + void setBinaryBuffer(char *buf, uint16_t len); + + /** @brief Discard any in-flight command. */ + void clearCommand(); + + /** @brief Remove the registered logging callback. */ + void clearCallback(); + /** @brief Register a callback invoked on log output. */ + void setCallbackFunction(CallbackFunction f); + /** @brief Use the default storage-callback (writes to the SD log). */ + void setCallbackStorage(); + /** @brief Only invoke the callback for messages at this level or above. */ + void setCallbackLevel(uint8_t l); + /** @brief Current callback log-level threshold. */ + uint8_t getCallbackLevel(); + /** @brief Manually fire the callback for a given level. */ + void doCallback(uint8_t l); + + /** @brief Set the console log-level threshold. */ + void setLevel (uint8_t in); + /** @brief Current console log-level threshold. */ + int8_t getLevel() { return level; } + + /** @brief Tick: read from streams, parse frames, run timeouts. */ + void loop(); + /** @brief Flush all output streams. */ + void flush(); + /** @brief True once a complete command frame has been received. */ + bool available(); + + /** @brief First byte of the most recent "X;" standard-style command. */ + byte getCommand() {return cmd;} + /** @brief Numeric argument from the most recent "X;" command. */ + uint32_t getVal() {return val;} + /** @brief Raw command-buffer pointer. */ + char* getBuffer() { return cmdBuffer; } + /** @brief Feed a byte into the standard-style command parser. */ + void processCmd(char in); + + /** @brief Finalize parsing of a CSV (@-prefixed) command frame. */ + void processCsv(); + /** @brief Number of CSV fields parsed in the current frame. */ + uint8_t getCsvCount() {return csv_count;} + /** @brief CSV field `n` interpreted as uint32. */ + uint32_t getCsvVal(uint8_t n) {return csv_uint32[n];} + /** @brief CSV field `n` as the original string. */ + char* getCsvBuffer(uint8_t n) { return csv_str[n]; } + + /** @brief Enable date-time prefix on log output. */ + void setDateTime() {displayDateTime = true;} + /** @brief Disable date-time prefix on log output. */ + void clearDateTime() {displayDateTime = false;} + /** @brief Print a formatted date-time prefix (current time if t==0). */ + void printDateTime(uint32_t t = 0); + + /** @brief Print a string to all output streams. */ + void print(char *out); + + /** @brief printf to a single stream rather than all of them. */ + void sprintf(Stream *s, const __FlashStringHelper *out, ... ); + + /** @brief printf to all output streams (RAM format string). */ + void printf(char* out, ...); + /** @brief printf to all output streams (PROGMEM format string). */ + void printf(const __FlashStringHelper *out, ... ); + #ifdef CONSOLE_USE_STRINGS + void printf(String& out, ...); + #endif + + /** @brief Emit the level-prefix string ("# INFO: " etc) for a level. */ + void printPrefix(uint8_t level); + + /** @brief Log a message at level `l` (RAM format string). */ + void log(uint8_t l, char* out, ...); + /** @brief Log a message at level `l` (PROGMEM format string). */ + void log(uint8_t l, const __FlashStringHelper *out, ... ); + #ifdef CONSOLE_USE_STRINGS + void log(uint8_t l, String& out, ...); + #endif + + // Special handlers + // void logReduceNumStr(uint8_t l, uint16 num, const __FlashStringHelper *out, ... ); + // void logReduceNum(uint8_t l, uint16 num); + + void safeLog(uint8_t l, char* out, ...); + void safeLog(uint8_t l, const __FlashStringHelper *out, ... ); + #ifdef CONSOLE_USE_STRINGS + void safeLog(uint8_t l, String& out, ...); + #endif + + void nmeaSend(const __FlashStringHelper *out, ... ); + void nmeaCRCSend(); + + void printNewLine(); + + char *printBuffer; // [CONSOLE_BUFFER_SIZE]; + uint16_t bufferMax; + + bool availableBin() { return binAvailable; } + char* getBinBuffer() { return binBuffer; } + uint16_t getBinLength() { return binReceiveCount; } + void printBinary(char* buf, uint16_t len); + void printBits(void* buf, uint16_t len); + + WII5Sh3dConsole() { + level = LOG_ALL; + callback_level = LOG_NONE; + } + + void setEcho(bool in); + bool getEcho(); + + protected: + + bool echo; + + void internalErrorNotEnoughMemory(); + void internalErrorNoBufferStream(); + bool check(); + + // Timeouts for new lines for safety + elapsedMillis waitInput; + + CallbackFunction callback; + int8_t callback_level; + bool callback_default; + + // Maximum number of streams? + Stream* streams[CONSOLE_STREAMS]; + uint8_t streamsDirection[CONSOLE_STREAMS]; + uint8_t streamCount = 0; + + // Level of debugging ? + int8_t level; + bool displayDateTime; + + // TODO Storage? + // func callback for storage ? + + // Command data + uint8_t cmdType; + uint8_t cmdCount; + byte cmd; + uint32_t val; + + // Big buffers - only created at start + // Noite - moved public temporary - char *printBuffer; // [CONSOLE_BUFFER_SIZE]; + char *cmdBuffer; // [CONSOLE_COMMAND_SIZE]; + + char *csv_str[CONSOLE_TYPE_CSV_LEN]; + uint32_t csv_uint32[CONSOLE_TYPE_CSV_LEN]; + uint8_t csv_count; // How many CSV fields do we have so far + uint8_t csv_last; // What location in the buffer is it + + // Binary Data external buffers, counts and status + bool binAvailable; + char *binBuffer; + uint16_t binMax; + bool binEnable; + uint16_t binReceiveCount; + uint16_t binReceiveExpect; + uint16_t binReceiveCRC; + + // Actual allocated memory + // Note - moved public temporary - uint16_t bufferMax; + uint16_t consoleMax; + + uint16_t i; +}; + +// TODO Better name? +extern WII5Sh3dConsole console; + +#endif diff --git a/WII5Sh3dIO.cpp b/WII5Sh3dIO.cpp new file mode 100644 index 0000000..2bfd8c7 --- /dev/null +++ b/WII5Sh3dIO.cpp @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Sh3dIO.cpp + * @brief I/O abstraction: buttons, LEDs, and the IO loop tick. + */ + +/* + +TODO 2024 - Check this is only valid IO for Buttons and LEDs + +*/ + +#include //assumes Arduino IDE v1.0 or greater +#include "WII5Sh3dIO.h" +#include "WII5Sh3dConsole.h" +#include // Include the PushButton library +#include + +/* ---------------------------------------------------------------------- + +*/ + +// TODO Can we use NUM_PINS +#define LED_MAX 100 +#define BUTTON_MAX 100 + +void WII5Sh3dIO::begin(uint8_t led1, uint8_t led2, uint8_t btn1, uint8_t btn2) { + begin(led1, led2, 0, 0, btn1, btn2, 0, 0); +} + +// Quick method to save buttons before begin +void WII5Sh3dIO::preButton(PushButton* btn1, PushButton* btn2, PushButton* btn3, PushButton* btn4) { + button1 = btn1; + button2 = btn2; + button3 = btn3; + button4 = btn4; +} + +void WII5Sh3dIO::begin(uint8_t led1, uint8_t led2, uint8_t led3, uint8_t led4, uint8_t btn1, uint8_t btn2, uint8_t btn3, uint8_t btn4) { + console.log(LOG_DEBUG, F("LEDS: %d,%d,%d,%d BUTTONS: %d,%d,%d,%d"), int(led1), int(led2), int(led3), int(led4), int(btn1), int(btn2), int(btn3), int(btn4)); + if (_begin) + return; + _begin = true; + + if (led1 > 0 && led1 < LED_MAX) { + led1_pin = led1; + pinMode(led1_pin, OUTPUT); + } + if (led2 > 0 && led2 < LED_MAX) { + led2_pin = led2; + pinMode(led2_pin, OUTPUT); + } + if (led3 > 0 && led3 < LED_MAX) { + led3_pin = led2; + pinMode(led3_pin, OUTPUT); + } + if (led4 > 0 && led4 < LED_MAX) { + led4_pin = led2; + pinMode(led4_pin, OUTPUT); + } + + if (btn1 > 0 && btn1 < BUTTON_MAX) { + pinMode(btn1, INPUT_PULLUP); + if (!button1) + button1 = new PushButton(btn1); + button1_pin = btn1; + // TODO + button1_activelow = true; + button1->setActiveLogic(LOW); + } + + if (btn2 > 0 && btn2 < BUTTON_MAX) { + pinMode(btn2, INPUT_PULLUP); + if (!button2) + button2 = new PushButton(btn2); + button2->setActiveLogic(LOW); + button2_pin = btn2; + button2_activelow = true; + } + + if (btn3 > 0 && btn3 < BUTTON_MAX) { + pinMode(btn3, INPUT_PULLUP); + if (!button3) + button3 = new PushButton(btn3); + button3_pin = btn3; + button3_activelow = true; + button3->setActiveLogic(LOW); + } + + if (btn4 > 0 && btn4 < BUTTON_MAX) { + pinMode(btn4, INPUT_PULLUP); + if (!button4) + button4 = new PushButton(btn4); + button4_pin = btn4; + button4_activelow = true; + button4->setActiveLogic(LOW); + } + + return; +} + +void WII5Sh3dIO::loop() { + if (lastLedLoop > 100) { + if (led1_pin > 0) { + // Max time and not default mode + if ((led1_max > 0) && (led1_mode != led1_default) && (led1_timeon > led1_max)) + led1Set(led1_default); + if (!updateLED(led1_pin, led1_mode, led1_state)) { + led1Set(led1_default); + } + led1_state++; + } + if (led2_pin > 0) { + if ((led2_max > 0) && (led2_mode != led2_default) && (led2_timeon > led2_max)) + led2Set(led2_default); + if (!updateLED(led2_pin, led2_mode, led2_state)) { + led2Set(led2_default); + } + led2_state++; + } + if (led3_pin > 0) { + if ((led3_max > 0) && (led3_mode != led3_default) && (led3_timeon > led3_max)) + led3Set(led3_default); + if (!updateLED(led3_pin, led3_mode, led3_state)) { + led3Set(led3_default); + } + led3_state++; + } + if (led4_pin > 0) { + if ((led4_max > 0) && (led4_mode != led4_default) && (led4_timeon > led4_max)) + led4Set(led4_default); + if (!updateLED(led4_pin, led4_mode, led4_state)) { + led4Set(led4_default); + } + led4_state++; + } + lastLedLoop = 0; + } + + if (button1) + button1->update(); + if (button2) + button2->update(); + if (button3) + button3->update(); + if (button4) + button4->update(); +} + +// Return 0 - nothing, or button number, 1,2,3 +// TODO - Improve the performance of this to be tiny, so it does not have to be run every singel 8 seconds +uint8_t WII5Sh3dIO::buttonSleepCheck() { + if (button1) { + // pinMode(button1_pin, button1_activelow ? INPUT_PULLUP : INPUT); + if (digitalRead(button1_pin) == (button1_activelow ? LOW : HIGH)) { + return 1; + } + } + + if (button2) { + // pinMode(button2_pin, button2_activelow ? INPUT_PULLUP : INPUT); + if (digitalRead(button2_pin) == (button2_activelow ? LOW : HIGH)) { + return 2; + } + } + + return 0; +} + +// TODO - Consider adding automatic getting Temperature at timed internval (except for blocking issue) +// TODO - Consider adding ability to read ALL sensors, not just one + +void WII5Sh3dIO::sleep() { + // TODO - Toun off 18B20 + // TODO - Turn off GPS + // TODO - Turn off LED (which may mean make them float/pull high) + + // TODO - Make sure the LEDs are actually off + // digitalWrite(LED1, HIGH); + // digitalWrite(LED2, HIGH); +} + +void WII5Sh3dIO::wake() { +} + +#define inverted false + +// Called once per 100ms +bool WII5Sh3dIO::updateLED(uint8_t pin, uint8_t mode, uint8_t state) { + // console.log(LOG_INFO, F("Updating LED %d, %d, %d"), pin, mode, state); + switch (mode) { + case LED_OFF: // Off + digitalWrite(pin, inverted ? HIGH : LOW); + break; + case LED_ON: // On + digitalWrite(pin, inverted ? LOW : HIGH); + break; + case LED_SLOW: // Slow + if ((state % 4) == 0) + digitalWrite(pin, !digitalRead(pin)); + break; + case LED_FAST: // Fast + if ((state % 2) == 0) + digitalWrite(pin, !digitalRead(pin)); + break; + case LED_SHORT: // Short - on for a short period + if ((state % 6) == 0) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + case LED_DOUBLE: // Double - 2 short, long off + if ( ((state % 8) == 0) || ((state % 8) == 2) ) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + case LED_TRIPPLE: // Trpple - 3 short, long off + if ( ((state % 12) == 0) || ((state % 12) == 2) || ((state % 12) == 4) ) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + case LED_SHORT_LONG: // Long - very slow long on and off + if ((state % 12) == 0) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + case LED_DOUBLE_LONG: + if ( ((state % 16) == 0) || ((state % 16) == 2) ) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + case LED_TRIPPLE_LONG: + if ( ((state % 20) == 0) || ((state % 20) == 2) || ((state % 20) == 4) ) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + + case LED_SHORT_GAP: // Long - very slow long on and off + if ((state % 12) == 0) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + case LED_DOUBLE_GAP: + if ( ((state % 48) == 0) || ((state % 48) == 2) ) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + case LED_TRIPPLE_GAP: + if ( ((state % 50) == 0) || ((state % 50) == 2) || ((state % 50) == 4) ) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + + case LED_ONE: + if ((state % 12) == 0) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + case LED_TWO: + if ( ((state % 16) == 0) || ((state % 16) == 2) ) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + case LED_THREE: + if ( ((state % 20) == 0) || ((state % 20) == 2) || ((state % 20) == 4) ) + digitalWrite(pin, inverted ? LOW : HIGH); + else + digitalWrite(pin, inverted ? HIGH : LOW); + break; + case LED_FOUR: + // TODO Simplify + break; + case LED_FIVE: + break; + case LED_SIX: + break; + case LED_SEVEN: + break; + case LED_EIGHT: + break; + case LED_NINE: + break; + case LED_TEN: + break; + case LED_ONCE: + if (state < 10) + digitalWrite(pin, inverted ? LOW : HIGH); + else if (state < 20) + digitalWrite(pin, inverted ? HIGH : LOW); + else { + return false; + } + break; + // TODO seemed to be just one long beep + case LED_TWICE: + if (state < 10) + digitalWrite(pin, inverted ? LOW : HIGH); + else if (state < 20) + digitalWrite(pin, inverted ? HIGH : LOW); + else if (state < 30) + digitalWrite(pin, inverted ? LOW : HIGH); + else if (state < 40) + digitalWrite(pin, inverted ? HIGH : LOW); + else { + return false; + } + break; + } + return true; +} + +WII5Sh3dIO sh3dNodeIO; diff --git a/WII5Sh3dIO.h b/WII5Sh3dIO.h new file mode 100644 index 0000000..2fa3e36 --- /dev/null +++ b/WII5Sh3dIO.h @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Sh3dIO.h + * @brief I/O abstraction: buttons, LEDs, and the IO loop tick. + */ + +#ifndef WII5Sh3dIO_h +#define WII5Sh3dIO_h + +#include //assumes Arduino IDE v1.0 or greater +#include + +// Standard Hardware Locations +#ifdef ISMEGA + #ifndef SH3D_NODE_LED_1 + #define SH3D_NODE_LED_1 15 + #endif + #ifndef SH3D_NODE_LED_2 + #define SH3D_NODE_LED_2 14 + #endif + +// does not work +// #elifdef SH3D_DEVICE_HANDHELD_1 +// #define SH3D_NODE_LED_1 17 +// #define SH3D_NODE_LED_2 16 + +#else + // #define LED1 9 + // #define LED2 6 + + // TODO Talk2 + #ifndef SH3D_NODE_LED_1 + #define SH3D_NODE_LED_1 6 + // T2_WPN_LED_1 + #endif + #ifndef SH3D_NODE_LED_2 + #define SH3D_NODE_LED_2 9 + // T2_WPN_LED_2 + #endif +#endif + +// Moteino Mega = 16, 15 +// Moteino Standard = X, Y +// Talk2 = T2_WPN_BTN_1, T2_WPN_BTN_2 +#define SH3D_NODE_BUTTON_1 4 +// T2_WPN_BTN_1 +#define SH3D_NODE_BUTTON_2 5 +// T2_WPN_BTN_2 + +// Temperature Sensor +// TODO Temp remove for Simon #define SH3D_NODE_18B20 +//#ifdef ISMEGA +// #define SH3D_NODE_ONE_WIRE_BUS 24 +//#else +// #define SH3D_NODE_ONE_WIRE_BUS 14 +//#endif +#define SH3D_NODE_ONE_WIRE_BUS 16 + +#ifdef SH3D_NODE_18B20 +#include +#include +#endif + +#include + +/* + Noticed: T2Led - looks like it might have better features. +*/ +#include + +// ENUM !!! +enum { + LED_OFF, + LED_ON, + LED_SLOW, + LED_FAST, + LED_SHORT, + LED_DOUBLE, + LED_TRIPPLE, + LED_SHORT_LONG, + LED_DOUBLE_LONG, + LED_TRIPPLE_LONG, + LED_SHORT_GAP, + LED_DOUBLE_GAP, + LED_TRIPPLE_GAP, + LED_ONE, + LED_TWO, + LED_THREE, + LED_FOUR, + LED_FIVE, + LED_SIX, + LED_SEVEN, + LED_EIGHT, + LED_NINE, + LED_TEN, + LED_ONCE, + LED_TWICE, +}; + +class WII5Sh3dIO { + public: + + void preButton(PushButton* btn1 = NULL, PushButton* btn2 = NULL, PushButton* btn3 = NULL, PushButton* btn4 = NULL); + void begin( + uint8_t led1 = SH3D_NODE_LED_1, + uint8_t led2 = SH3D_NODE_LED_2, + uint8_t btn1 = SH3D_NODE_BUTTON_1, + uint8_t btn2 = SH3D_NODE_BUTTON_2 + ); + void begin( + uint8_t led1 = SH3D_NODE_LED_1, + uint8_t led2 = SH3D_NODE_LED_2, + uint8_t led3 = 0, + uint8_t led4 = 0, + uint8_t btn1 = SH3D_NODE_BUTTON_1, + uint8_t btn2 = SH3D_NODE_BUTTON_2, + uint8_t btn3 = 0, + uint8_t btn4 = 0 + ); + void loop(); + + void led1Set(uint8_t m) { led1_mode = m; led1_state = 0; led1_timeon = 0; } + void led1SetTimeout(uint32_t m) { led1_max = m; led1_timeon = 0; } + void led1SetDefault(uint8_t m) { led1_default = m; } + uint8_t led1Get() { return led1_mode; } + + void led2Set(uint8_t m) { led2_mode = m; led2_state = 0; led2_timeon = 0; } + void led2SetTimeout(uint32_t m) { led2_max = m; led2_timeon = 0; } + void led2SetDefault(uint8_t m) { led2_default = m; } + uint8_t led2Get() { return led2_mode; } + + void led3Set(uint8_t m) { led3_mode = m; led3_state = 0; led3_timeon = 0; } + void led3SetTimeout(uint32_t m) { led3_max = m; led3_timeon = 0; } + void led3SetDefault(uint8_t m) { led3_default = m; } + uint8_t led3Get() { return led3_mode; } + + void led4Set(uint8_t m) { led4_mode = m; led4_state = 0; led4_timeon = 0; } + void led4SetTimeout(uint32_t m) { led4_max = m; led4_timeon = 0; } + void led4SetDefault(uint8_t m) { led4_default = m; } + uint8_t led4Get() { return led4_mode; } + + // Temporary HACK version with individual - see array branch for single function for unlimited LED and Buttons + + PushButton *button1; + PushButton *button2; + PushButton *button3; + PushButton *button4; + + uint8_t buttonSleepCheck(); + + void sleep(); + void wake(); + + protected: + elapsedMillis lastLedLoop; + + // Validity etc of most recent data + uint8_t validPacketType; + + bool _begin; + + // Led udpate + bool updateLED(uint8_t pin, uint8_t mode, uint8_t state); + + uint16_t battery_voltage; + + // LED1 + uint8_t led1_pin; + bool led1_activelow; + uint8_t led1_mode; + uint8_t led1_default; + uint8_t led1_state; + elapsedMillis led1_timeon; + uint32_t led1_max; + + // LED2 + uint8_t led2_pin; + bool led2_activelow; + uint8_t led2_mode; + uint8_t led2_default; + uint8_t led2_state; + elapsedMillis led2_timeon; + uint32_t led2_max; + + // LED3 + uint8_t led3_pin; + uint8_t led3_mode; + uint8_t led3_default; + uint8_t led3_state; + elapsedMillis led3_timeon; + uint32_t led3_max; + + // LED4 + uint8_t led4_pin; + uint8_t led4_mode; + uint8_t led4_default; + uint8_t led4_state; + elapsedMillis led4_timeon; + uint32_t led4_max; + + // Button1 + uint8_t button1_pin; + bool button1_activelow; + + // Button2 + uint8_t button2_pin; + bool button2_activelow; + + // Button3 + uint8_t button3_pin; + bool button3_activelow; + + // Button4 + uint8_t button4_pin; + bool button4_activelow; +}; + +extern WII5Sh3dIO sh3dNodeIO; + +#endif diff --git a/WII5Sh3dUtil.cpp b/WII5Sh3dUtil.cpp new file mode 100644 index 0000000..9341c4c --- /dev/null +++ b/WII5Sh3dUtil.cpp @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Sh3dUtil.cpp + * @brief Sh3d utility helpers: sleep, reset, and platform glue. + */ + +/* + +Sh3dNode - Utils / Utilities: + +TODO 2024 - Check this is the main code for sleep + +* Date Time - Sync and format +* Sleep - Management +* Software reset +* WatchDog Timer (internal and external) + +*/ + +#include +#include +#include "WII5Sh3dUtil.h" +#include "WII5Sh3dIO.h" +#include + +#ifdef KINETISK +// TODO See Snooze - https://github.com/duff2013/Snooze +#else + +#ifdef WII5_LOWPOWERSLEEP +#include +#endif + +#endif +#include +#include + +// Just in case +void WII5Sh3dUtil::begin() { +} + +/* + sleep - Put the whole device to sleep for "n" seconds. + + TODO: Wake on Radio option +*/ +uint8_t WII5Sh3dUtil::sleep(uint32_t seconds) { + // TODO - Make sure the LEDs are actually off + // digitalWrite(LED1, HIGH); + // digitalWrite(LED2, HIGH); + + // Deep sleep mode ! (NOTE: NMEA out?) + console.printf(F("# SLEEP %lu sec\r\n"), seconds); + console.flush(); + + // TODO these should be registered? and not used if not required + // sh3dNodeRadio.sleep(); + // sh3dNodeIO.sleep(); + // sh3dNodeStorage.sleep(); + + sleepLastTime = now(); + volatile uint32_t sleepCount = 0; + pinMode(LED_1, OUTPUT); + while ( (sleepCount * 8) < seconds) { + digitalWrite(LED_1, LOW); + #ifdef WDT_RESET + pinMode(WDT_RESET, OUTPUT); + digitalWrite(WDT_RESET, (sleepCount % 2) == 0 ? HIGH : LOW); + #endif + + // Compare LowPower with work on Talk2 + #ifdef KINETISK + // TODO look up snooze + #endif + + // List boards supported + #ifdef WII5_LOWPOWERSLEEP + LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); + #endif + + sleepCount++; + + uint8_t btn = sh3dNodeIO.buttonSleepCheck(); + if (btn > 0) { + console.flush(); + console.printf(F("# !SLEEP Button %d %lu sec, %lu c\r\n"), btn, seconds, sleepCount); + sleepLastSeconds = sleepCount * 8; + sleepLastReason = 10 + btn; + return 10 + btn; + } + digitalWrite(LED_1,HIGH); + } + + // TODO Fix time + // cli(); + // timer0_millis += 64000; //TODO should use multiplier + // sei() + + // TODO these should be registered? + // sh3dNodeRadio.wake(); + // sh3dNodeIO.wake(); + // sh3dNodeStorage.wake(); + + // Flsuh devices again + console.flush(); + console.printf(F("# SLEEP END %lu sec, %lu c\r\n"), seconds, sleepCount); + sleepLastSeconds = sleepCount * 8; + sleepLastReason = 1; + return 1; +} + +void WII5Sh3dUtil::reset() { + wdt_enable(WDTO_15MS); + // TODO 2024 - This may need - WDTCSR |= (1 << WDE); // Watchdog System Reset Enable + // TODO or maybe not + while(1); + /* + #ifdef WATCHDOG + wdt_enable(WDTO_15MS); + #else + // Teensy 3 + #ifndef SCB_AIRCR_SYSRESETREQ_MASK + #define SCB_AIRCR_SYSRESETREQ_MASK ((unsigned int) 0x00000004) + #endif + cli(); + delay(100); + SCB_AIRCR = 0x05FA0000 | SCB_AIRCR_SYSRESETREQ_MASK; + while(1); + #endif + delay(1000); + */ +} + +WII5Sh3dUtil sh3dNodeUtil; diff --git a/WII5Sh3dUtil.h b/WII5Sh3dUtil.h new file mode 100644 index 0000000..f4af7d6 --- /dev/null +++ b/WII5Sh3dUtil.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Sh3dUtil.h + * @brief Sh3d utility helpers: sleep, reset, and platform glue. + */ + +#ifndef WII5Sh3dUtil_h +#define WII5Sh3dUtil_h + +#include //assumes Arduino IDE v1.0 or greater + +#include +#include +#include // Codependence GAH ! TODO +#include +#include + +class WII5Sh3dUtil { + public: + + void begin(); + void loop(); + + // Reset - and other low level hacks + void reset(); + + // Sleep Stuff + uint8_t sleep(uint32_t seconds); + uint32_t sleepLastSeconds; // How long did we last sleep for - replaced after sleep + uint8_t sleepLastReason; // Why did we leave sleep (button, time up etc) + time_t sleepLastTime; // When did the sleep occur + + protected: + +}; + +extern WII5Sh3dUtil sh3dNodeUtil; + +#endif diff --git a/WII5Sparton.cpp b/WII5Sparton.cpp new file mode 100644 index 0000000..cc5eac1 --- /dev/null +++ b/WII5Sparton.cpp @@ -0,0 +1,1076 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Sparton.cpp + * @brief Sparton AHRS-M1/M2 IMU driver: capture timing, NorthTek configuration. + */ + +/* + +WII5Sparton + +TODO 2024 - Review code, check TODO and add some Command functions for testing +TODO 2024 - Look at getting debug working with serial passthrough in text stage not binary +TODO 2024 - When getting no data - stop capture and report failure for Iridium +TODO 2024 - How to send an error - look at Command return string +TODO 2024 - Debugging text modes? + +BINARY File + https://forum.pjrc.com/threads/55114-SD-Datalogging-Best-Practice-in-2019 + Datalogging is a problem I've been trying to brute-force for half a year now. + + My solution (that I hope to release in a couple more months of testing) runs on top of the SdFs library for the purpose of low-latency high-frequency binary logging. I'm currently able to (somewhat reliably) maintain a logging speed of 925bytes at 500Hz (452KB/s). So in short, it is possible to have a good logging system, although it's highly dependent on how you interact with your data. + + My current best practises: + + 1. Initialize the SdFs on SdioConfig(FIFO_SDIO) mode. + 2. Preallocate the maximum length of your log file using the .preAllocate(size_t size) method. + 3. Important: Pre-erase the entire file with zeroes. + 4. Before you start logging (to ensure the file is committed fully to the card), call sync(). + 5. Write in chunks of a multiple of 512bytes only. (512, 1024, 2048 byte chucks per write() command) + 6. To minimize blocking, try to write only if .card()->isBusy() returns false. This seems to be related to the internal card data management (I'm guessing things like TRIM and what not). Step 3 helps ensure that the card spends as little time as possible in this busy time. + 7. Use a SLC-type SD card. I cannot stress enough the difference this makes. Before buying this card, I had write() operations take up to 10ms on a SanDisk card, but with this expensive card they 99.9% of the time stayed below 500us. + 8. Call truncate() and then sync() after you've finished writing your entire file. The first function removes any extra pre-allocation and the second makes sure that it's a proper FAT32 file. Note that if you're using an exFAT partition your file will not be saved until you call sync() or close(). + 7. On that note, FAT32 is always faster than exFAT, but you do have less capacity. + + I'm still learning more about best practises for logging. My current hang-ups are (I think) related to heap-overflow because of the many sensors I have attached interrupting one another during a write() operation. I'm a very fresh programmer so it's taking me a while but I hope the tips above are helpful enough for you to get started on your 100Hz project. + + Good luck! Let me know if you find any new practises that I can also test. + + +64Hz Performance Issue: + +1 - Version of code that uses standard library loge to disk. No parsing at all. + This will not allow us to show the 1 Hz update. + + Status: On Hold + +2 - Version of the code that uses new library to buffer and then parse int the loops + + Status: WIP + +3 - Binary Version: Most likely in interrupt + +TODO - Cycle through all the baud rates until we get an OK/Huh so we can +work out it is wokring. Send the stop command as the test. Clear wii5BufferString. +Send twice, that sort of thing. If not our correct baud rate, then +send the program command change to new baud. + +PINS: + + * 1 - Ground + * 2 - TX (actually TX from AVR, so Receive) + * 3 - RX + * 4 - Power (+4 volts) + * 5 - NC + * 6 - RESET (low is reset, keep high, has weak pullup) + +Special Notes: + + * 5 millisecond line delay + * + +baud 4 set drop + + 4 = 9600 + 5 = 19200 + 6 = 38400 + 7 = 57600 + 8 = 115200 + +Text CRC + + TODO - We should at least check we have not lost characters + +Binary. + + Try RFS (Remote Function Select) Sparton Binary format with CRC etc + + STATS:Sparton:loops=128008 ERROR:time=512 accel=96 + Standard wii5BufferStrings + 64Hz + +# INFO: 2024-03-13T23:49:09 - SPARTON: Records received = 1001 +STATS:Sparton:Status=unknown:age=165s +STATS:Sparton:Time:Total=0 +STATS:Sparton:SerialManager:TODO +STATS:Sparton:loops=587117 ERROR:time=119 accel=3 + 8Hz, SD Card etc + +# INFO: 2024-03-13T23:50:32 - SPARTON: Records received = 1001 +STATS:Sparton:Status=unknown:age=248s +STATS:Sparton:Time:Total=0 +STATS:Sparton:SerialManager:TODO +STATS:Sparton:loops=44851 ERROR:time=199 accel=32 + 64Hz, SD Card etc + +*/ + +#include +#include + +// Careful - loss of serial data with this on +// #ifdef SPARTON_SERIAL_DEBUG + +// #define WII5_ZONLY +// #define WII5_STRINGUTIL +// #ifdef WII5_STRINGUTIL +// #include +// using namespace StringUtil; +// #endif + +// INTERRUPT - Keep fast and use volatile varialbes. +void WII5Sparton::handleRxChar( uint8_t c ) { + // Which record we playing with? + currentRecord = (recordCount % SPARTON_RECORDS); + + // \r and \n for records is an issue... + + // START of Binary + if (c == 0x1) { + processMode = 2; + currentCount = 0; + startWhen = 0; + binEscape = false; + return; + } + // END of binary + else if (c == 0x3) { + // Record too small ! + if (currentCount < sizeof(WII5_DATA_SpartonBinary)) { + serialSizeError++; + } + + // Other validity + if ( + (binRecords[currentRecord].channel != 0) // 0 - yep - valid + || (binRecords[currentRecord].status != 105) // TODO 105, what is thta? + ) { + // TODO Not really time, record data? + statsTimeError++; + } + + // Status / Mode + lastValidRecord = currentRecord; + recordCount++; + processMode = 0; + + // TODO consider logging time here for startWhen + return; + } + + // Escape character - take off BIN next time + else if (c == 0x10) { + binEscape = true; + } + + // Process a Binary row + else if (processMode == 2) { + if (binEscape) { + // Escape characters need to drop MSB ! (to protect 0x1, 0x3 and 0x10) + binEscape = false; + c = c & B01111111; + } + if (currentCount < sizeof(WII5_DATA_SpartonBinary)) { + ((char*)&binRecords[currentRecord])[currentCount] = c; + currentCount++; + } + + // Record too large + else { + serialSizeError++; + } + } + /* + TODO 2024 - passthrough mode? + else if (processMode == 0) { + console.log(LOG_DEBUG, F("SPARTON: char=%c"), c); + } + */ +} + +void WII5Sparton::begin() { + // TODO Use ? + pinMode(SPARTON_RESET, OUTPUT); + digitalWrite(SPARTON_RESET, HIGH); + + // Power (V2) + #if POWER_IMU_SPARTON_PIN>0 + powerSetPin(POWER_IMU_SPARTON_PIN, POWER_IMU_SPARTON_ON); + powerOff(true); // Forced off at boot + #endif + + debug = false; + running = false; + cancel = false; + step = WII5SPARTON_OFF; + + consoleDirect = false; + rawCapture = false; // TODO NOTE: Debugging - probs should make this false by default + + // Defaults + HZ = 8; + binMode = true; + + // TODO - what should these be? + // recordCalc.setRejectionSigma(7.0); + +}; + +uint16_t WII5Sparton::getHz() { + return HZ; +} +uint16_t WII5Sparton::getMs() { + return (1000 / getHz()); +} +void WII5Sparton::setHz(uint16_t h) { + HZ = h; +} +void WII5Sparton::setBinary(bool in) { + binMode = in; +} + +void WII5Sparton::setPassthrough(bool in) {} // {pt = in;} +bool WII5Sparton::getPassthrough() {} // {return pt;} +void WII5Sparton::setDebug(bool in) {debug = in;} +bool WII5Sparton::getDebug() {return debug;} + +void WII5Sparton::setCapture(bool in) { + // TODO Capture disabled + capture = false; return; + // closeFile(); + capture = in; +} + +bool WII5Sparton::getCapture() {return capture;} +void WII5Sparton::setRecords(uint32_t in) { recordTotal = in; } +uint32_t WII5Sparton::getRecords() {return recordTotal;} + +void WII5Sparton::automatic(uint8_t hz, uint32_t records, bool capture) { + setHz(hz); + setRecords(records); + // (leave as is) setPassthrough(0); + // Do not change debug + setCapture(capture); + start(); +} + +void WII5Sparton::start() { + console.log(LOG_DEBUG, F("SPARTON: start current=%d"), step); + + pinMode(SPARTON_RESET, OUTPUT); + digitalWrite(SPARTON_RESET, HIGH); + + if (step != WII5SPARTON_OFF) { + console.log(LOG_ERROR, F("SPARTON: ERROR - Start called when Sparton already running. Cleaning up.")); + // closeFile(); + sendLine(0); + off(true); + // TODO 2024 - delay? Consider safeDelay - although this is a rare occurecne (aka already running) + delay(500); + } + + resetData(); + step = WII5SPARTON_BOOT; stepWait = 0; + startWhen = 0; // Clear when binary start + startCapture = 0; // When we started this capture + lastWrite = 0; // Last time we wrote to SD Card successfully + // statsHighClear(); + + on(true); +} + +void WII5Sparton::resetData() { + // Values used in collecting + serialSizeError = 0; + statsTimeError = 0; + + blockNext = 0; + captureWriteMax = 0; + captureWriteMin = 1000; + captureWriteOver = 0; + + // Current record. + #ifdef SPARTON_SIZES + for (uint8_t t = 0; t < SIZES_LENGTH; t++) { + sizes[t] = 0; + } + #endif + recordCount = 0; sendCount = 0; + + // Averages + // avgAccelX.reset(); + // avgAccelY.reset(); + // avgAccelZ.reset(); + // avgPoseX.reset(); + // avgPoseY.reset(); + // avgPoseZ.reset(); + + // Internal storage + #ifdef SPRTON_RECORD + recordReady = -1; + record[0].rec = 0; record[0].stamp = 0; record[0].stampActual = 0; record[0].stampErr = 0; + record[0].pose_x = NAN; record[0].pose_y = NAN; record[0].pose_z = NAN; + record[0].mag_x = NAN; record[0].mag_y = NAN; record[0].mag_z = NAN; + record[0].gyro_x = NAN; record[0].gyro_y = NAN; record[0].gyro_z = NAN; + record[0].accel_x = NAN; record[0].accel_y = NAN; record[0].accel_z = NAN; + record[1].rec = 0; record[0].stamp = 0; record[0].stampActual = 0; record[0].stampErr = 0; + record[1].pose_x = NAN; record[1].pose_y = NAN; record[1].pose_z = NAN; + record[1].mag_x = NAN; record[1].mag_y = NAN; record[1].mag_z = NAN; + record[1].gyro_x = NAN; record[1].gyro_y = NAN; record[1].gyro_z = NAN; + record[1].accel_x = NAN; record[1].accel_y = NAN; record[1].accel_z = NAN; + recordPrev.rec = 0; recordPrev.stamp = 0; recordPrev.stampActual = 0; recordPrev.stampErr = 0; + recordPrev.pose_x = NAN; recordPrev.pose_y = NAN; recordPrev.pose_z = NAN; + recordPrev.mag_x = NAN; recordPrev.mag_y = NAN; recordPrev.mag_z = NAN; + recordPrev.gyro_x = NAN; recordPrev.gyro_y = NAN; recordPrev.gyro_z = NAN; + recordPrev.accel_x = NAN; recordPrev.accel_y = NAN; recordPrev.accel_z = NAN; + #endif + + console.log(LOG_INFO, F("SPARTON: Reset Sparton Data 0 - %d"), sizeof(binRecords)); + memset(&binRecords, 0, sizeof(binRecords)); +} + +void WII5Sparton::info() { + if (debug) + console.log(LOG_DEBUG, F("SPARTON: info current=%d"), step); + recordCount = 0; sendCount = 0; + step = WII5SPARTON_INFO; stepWait = 0; + on(); +} + +void WII5Sparton::captureRun() { + // processMode = WII5SERIALPARSER_RECORD; // Capture fields + // last = WII5SERIALLAST_NONE; + step = WII5SPARTON_CAPTURE; stepWait = 0; +} + +bool WII5Sparton::captureRunning() { + return (step == WII5SPARTON_CAPTURE); +} + +bool WII5Sparton::captureFinished() { + return ( + (step == WII5SPARTON_OFF) + ); +} + +// WAITING - It will wait here, basically forever, until you call captureRun() +// this is to allow preparation followed by remote trigger at a certain time. +// However - if it is past this - e.g. nowait - you could deadlock +bool WII5Sparton::captureWaiting() { + return (step == WII5SPARTON_PROGRAM_START); +} + +void WII5Sparton::stop(bool force) { + if (debug) + console.log(LOG_DEBUG, F("SPARTON: cancel current=%d"), step); + if (step != WII5SPARTON_OFF) { + step = WII5SPARTON_CANCEL; stepWait = 0; + } + + // Force to off state - done by sleep + if (force) { + powerOff(true); + step = WII5SPARTON_OFF; + } +} + +void WII5Sparton::setRawCapture(bool in) { + rawCapture = in; +} +bool WII5Sparton::getRawCapture() { + return rawCapture; +} + +void WII5Sparton::setConsoleDirect(bool in) { + consoleDirect = in; +} +bool WII5Sparton::getConsoleDirect() { + return consoleDirect; +} + +bool WII5Sparton::isRunning() { + return running; +} + +// on +void WII5Sparton::on(bool force) { + if (!running || force) { + if (debug) + console.log(LOG_DEBUG, F("SPARTON: power ON, baud=%lu"), (uint32_t)SerialIMU_Baud); + running = true; + cancel = false; + wii5Controller.shared5On(); + #if POWER_IMU_SPARTON_PIN>0 + powerOn(); + #endif + // TODO: investigate whether pinMode(15, INPUT_PULLUP) was needed here + SerialIMU.begin(SerialIMU_Baud); + // SerialIMU.attachInterrupt(handleRxChar); + + // stream = &SerialIMU; + // beginSerialManager(); + } +} + +void WII5Sparton::setBaudrate(uint32_t baud) { + console.log(LOG_DEBUG, F("SPARTON: Debug set baud=%lu"), baud); + SerialIMU.begin(baud); +} + +// off +void WII5Sparton::off(bool force) { + if (running || force) { + if (debug) + console.log(LOG_DEBUG, F("SPARTON: power off")); + running = false; + SerialIMU.end(); + // stream = NULL; + // endSerialManager(); + displaySTATS(); + #if POWER_IMU_SPARTON_PIN>0 + powerOff(); + #endif + wii5Controller.shared5Off(); + } +} + +// Sparton Things: +// - Turn Off +// - Start now +// - Start at X + +bool firstBoot = true; + +void WII5Sparton::sendLine(uint16_t l) { + programLine(l); + SerialIMU.println(wii5BufferString); + // TODO Configurable? + console.log(LOG_DEBUG, F("SPARTON: > %s"), wii5BufferString); +} + +char c; +bool first; +uint8_t startReady; +void WII5Sparton::loop() { + first = (step != stepLast); + stepLast = step; + + while (SerialIMU.available()) + handleRxChar(SerialIMU.read()); + + // WII5SerialManager::loop(); + switch (step) { + case WII5SPARTON_OFF: + off(); + break; + + case WII5SPARTON_BOOT: + on(); + // Wait half a second then spin on + if (stepWait > 500) { + console.log(LOG_INFO, F("SPARTON: Startup")); + if (firstBoot) { + step = WII5SPARTON_BOOT_BAUD; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step boot->boot_baud")); + } + else { + step = WII5SPARTON_BOOT_STOP; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step boot->boot_stop")); + } + } + break; + + // TODO Reset !!! + // - first - high, 500ms later, low, or reversed ! + + // First time boot - so lets check the baud rate is valid + case WII5SPARTON_BOOT_BAUD: + step = WII5SPARTON_BOOT_STOP; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step boot_baud->boot_stop")); + firstBoot = false; + break; + + case WII5SPARTON_BOOT_STOP: + // First line is STOP + // processMode = WII5SERIALPARSER_NONE; + sendLine(0); + sendLine(0); + step = WII5SPARTON_BOOT_WAIT; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step boot_stop->boot_wait")); + break; + + case WII5SPARTON_BOOT_WAIT: + // First time - clear the captture file + // Give it a full second to absorb all the junk + if (stepWait > 1000) { + SerialIMU.flush(); + // processMode = WII5SERIALPARSER_NONE; + step = WII5SPARTON_READY; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step boot_wait->ready")); + } + break; + + case WII5SPARTON_READY: + // Short Params (no gyro or mag) + programStart = 20; programEnd = 30; sendCount = 0; + // Experiment + // programStart = 50; programEnd = 60; sendCount = 0; + step = WII5SPARTON_PROGRAM_BASE; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step ready->program_base")); + break; + + case WII5SPARTON_PROGRAM_BASE: + if (stepWait > 250) { + if (sendCount <= (programEnd - programStart)) { + // Send next line, inremement counter, and wait another 10 ms + sendLine(programStart + sendCount); + sendCount++; + stepWait = 0; + } + else { + step = WII5SPARTON_PROGRAM_START; stepWait = 0; + } + } + break; + + case WII5SPARTON_PROGRAM_START: + // // processMode = WII5SERIALPARSER_RECORD; // Capture fields + // // last = WII5SERIALLAST_NONE; + // step = WII5SPARTON_CAPTURE; stepWait = 0; + // Sits here, waiting forever, until we move it on manually when ready to run. + // But we stillshould add a timeout + /// SKIPT to capture + if (true) { + // processMode = WII5SERIALPARSER_RECORD; // Capture fields + // last = WII5SERIALLAST_NONE; + step = WII5SPARTON_CAPTURE; stepWait = 0; + } + break; + + case WII5SPARTON_CAPTURE: + // Reset counters, so as not to loose first lot + if (first) { + console.log(LOG_INFO, F("SPARTON: Capturing")); + resetData(); + timeStart = now(); + } + + // We do everything else in the gap between the records (TBC) + // TODO This has not been tested + else if (startWhen > 5) { + + // TODO Fix hardcoded time 5 above + + // TODO Fix hard coded 15 recods per block + + // Do we need to wrtie a block of data + if ( (recordCount / 15) > blockNext) { + + // TODO test if block too far + if ( (recordCount / 15) > (blockNext + 1) ) { + console.log(LOG_FATAL, F("Sparton: Block too far, all data probably lost. block=%lu, expected=%lu, records=%lu"), + blockNext, + recordCount / 15, + recordCount + ); + } + + captureWriteTime = 0; + if (sdBlock.dataIsOpen()) { + if (sdBlock.dataWrite( + &binRecords[(blockNext % 2) * 15], + 15 * 33 + )) { + lastWrite = 0; + // TODO No, slow + /* + console.log(LOG_INFO, F("Sparton: Block record write OK: block=%lu, records=%lu, binRecords=%d, size=%d"), + blockNext, recordCount, + int(&binRecords[(blockNext % 2) * 15]), + int(15 * 33) + ); + */ + } + else { + // TODO No, slow + console.log(LOG_ERROR, F("Sparton: Block record write FAILED: %lu"), blockNext); + // At this point - we stop processing ! But need to log that. + console.log(LOG_ERROR, F("Sparton: CANCEL CAPTURE - Can't write to SD Card")); + stop(); + } + } + else { + console.log(LOG_ERROR, F("Sparton: Block Data not open")); + // At this point - we stop processing ! But need to log that. + console.log(LOG_ERROR, F("Sparton: CANCEL CAPTURE - Can't write to SD Card")); + stop(); + } + + if (captureWriteTime > captureWriteMax) + captureWriteMax = captureWriteTime; + if (captureWriteTime < captureWriteMin) + captureWriteMin = captureWriteTime; + if (captureWriteTime > 5) + captureWriteOver++; + + // TODO Catch errors? + + blockNext++; + } + + else if ((stepWait > 700) && ((recordCount % HZ) == 0) && (lastValidRecord < SPARTON_RECORDS) ) { + // console.printBinary((char*)&binBuffer[binBufferReady], sizeof(binBuffer[0])); + wii5Display.atDataSend( + // NOTE: Must not add the "@" - it isn't used in CRC calculation + // line,stamp,err,8000,0,0,accel:x,y,z,gyro:x,y,z,mag:x,y,z,pos:x,y,z + F("CaptureSummary"), + F("%lu/%lu,%d,%d,%d,%d,%d,%d,block=%lu/%lu"), + recordCount, recordTotal, + int(binRecords[lastValidRecord].accel_x), + int(binRecords[lastValidRecord].accel_y), + int(binRecords[lastValidRecord].accel_z), + int(binRecords[lastValidRecord].pose_x), + int(binRecords[lastValidRecord].pose_y), + int(binRecords[lastValidRecord].pose_z), + blockNext, (recordTotal / 15) + ); + /* + Not recommended - 1 per second at 9600 + wii5Display.statusSend( + F("Sparton"), + F("record=%lu/%lu,z=%d"), + recordCount, recordTotal, + int(binRecords[lastValidRecord].accel_z) + ); + */ + stepWait = 0; + + } + + // New 2024 - Check if recorsds never increasing and report errors ! + // No records but startTime > 2 minutes - FAILURE ! + // TODO 2024 - This does NOT capture if the sparton fails half way through.... + // What we want is no records for more than 2 minutes. + // Change this code so startCapture is something like lastWriteTime or lastRecordTime and check that isn't 2 minutes old + else if ( lastWrite > 120000) { + console.log(LOG_INFO, F("Sparton: Failure ! No writes for 2 minutes")); + snprintf_P( + wii5Commands.lastCommandMessage, sizeof(wii5Commands.lastCommandMessage), + PSTR("@Err,sparton,norecords") + ); + wii5Commands.lastCommandCmd = 0; + wii5Communications.sendError(); + step = WII5SPARTON_PROGRAM_STOP; stepWait = 0; + } + + // Do we have enough? + else if (recordCount >= recordTotal) { + // If we are new block. OR half way through a block... + if ( + sdBlock.dataIsOpen() + && ((recordCount / 15) >= blockNext) + && ((recordCount % 15) > 0) + ) { + + // If half way, blank the rest + if ((recordCount % 15) > 0) { + // Blank the other records - 0 or 15 start + left over to end + memset(&binRecords[((blockNext % 2) * 15) + (recordCount % 15)], 0, 33 * (15 - (recordCount % 15))); + } + + if (!sdBlock.dataWrite( + &binRecords[(blockNext % 2) * 15], + 15 * 33 + )) { + console.log(LOG_ERROR, F("Sparton: Block record write FAILED: %lu"), blockNext); + } + + blockNext++; + } + + timeEnd = now(); + console.log(LOG_INFO, F("SPARTON: Finished Capture. Records=%lu of %lu, block written=%lu"), recordCount, recordTotal, blockNext); + // processMode = WII5SERIALPARSER_NONE; + // last = WII5SERIALLAST_NONE; + // Display high speed stats + // statsHighDisplay(); + if (debug) + console.log(LOG_DEBUG, F("step capture->program_stop")); + step = WII5SPARTON_PROGRAM_STOP; stepWait = 0; + } + } + break; + + case WII5SPARTON_PROGRAM_STOP: + // TODO send the stop command + // programStart = 0; programEnd = 1; + // step = WII5SPARTON_PROGRAM_BASE; stepWait = 0; + // processMode = WII5SERIALPARSER_NONE; + sendLine(0); + step = WII5SPARTON_FINISH; stepWait = 0; + // last = WII5SERIALLAST_NONE; + if (debug) + console.log(LOG_DEBUG, F("step program_stop->finish")); + break; + + case WII5SPARTON_CANCEL: + // Shut it down., power will go down at OFF + // closeFile(); + sendLine(0); + step = WII5SPARTON_FINISH; stepWait = 0; + cancel = true; + if (debug) + console.log(LOG_DEBUG, F("step cancel->finish")); + break; + + case WII5SPARTON_FINISH: + step = WII5SPARTON_OFF; stepWait = 0; + if (debug) + console.log(LOG_DEBUG, F("step finish->off")); + break; + + // TODO DEBUG THIS ! + case WII5SPARTON_INFO: + if (first) { + sendCount = 0; + } + else if (stepWait > 500) { + // Standard setup commands + if (sendCount <= 12) { + // Send next line, inremement counter, and wait another 10 ms + sendLine(sendCount); + sendCount++; + stepWait = 0; + } + + // 13 = 30 = Version + else if (sendCount == 13) { + sendLine(80); + sendCount++; + stepWait = 0; + } + + // 14 = 31 = Config XML + else if (sendCount == 14) { + if (stepWait > 3000) { + sendLine(81); + sendCount++; + stepWait = 0; + } + } + + // Wait 5 seconds to end + else if (stepWait > 60000) { + step = WII5SPARTON_OFF; stepWait = 0; + } + } + break; + + default: + console.log(LOG_FATAL, F("SPARTON: Default step... step=%d"), step); + step = WII5SPARTON_OFF; + // Power off + break; + } + + while (SerialIMU.available()) + handleRxChar(SerialIMU.read()); + + return; +} + +void WII5Sparton::repl() { + uint8_t lastDot = 0; + console.log(LOG_FATAL, F("SPARTON: REPL. '...' to exit.")); + + start(); + // SerialIMU.detachInterrupt(); + while (1) { + if (SerialConsole.available()) { + char c = SerialConsole.read(); + if (c == '.') + lastDot++; + else + lastDot = 0; + if (lastDot >= 3) { + SerialConsole.flush(); + SerialIMU.flush(); + stop(); + console.log(LOG_FATAL, F("SPARTON: Completed REPL. User exit.")); + return; + } + SerialIMU.write(c); + } + + if (SerialIMU.available()) { + char c = SerialIMU.read() ; + SerialConsole.write(c); + } + } +} + +void WII5Sparton::displaySTATS() { + wii5Display.atStatsSend( + F("Sparton"), + F("errorTime=%lu,errorSize=%lu,writeMax=%lu,writeMin=%lu,writeOver=%lu"), + statsTimeError, serialSizeError, + captureWriteMax, captureWriteMin, captureWriteOver + ); + #ifdef SPARTON_SIZES + for (uint8_t t = 0; t < SIZES_LENGTH; t++) { + if (sizes[t] > 0) { + if (t == 0) + console.printf(F("# SPARTON Record Size: others = %d\r\n"), sizes[t]); + else + console.printf(F("# SPARTON Record Size: %d = %d\r\n"), t + SIZES_START, sizes[t]); + } + } + #endif +} + +// TODO Totaly - this can be common wii5BufferString for all Serial Devices or even the Console +void WII5Sparton::programLine(uint16_t l) { + memset(wii5BufferString, 0, WII5_BUFFER_STRING); + + // Main Programming + switch (l) { + + // CODE = Wave Tank and original code using all things, including magnatometer + // PROGRAM=0..12, START=13..13, PROGRAM+START=0..13, STOP=0..0 + case 0: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 0 set drop")); + break; + case 1: +// TODO: confirm whether the reset+wait sequence here is actually required + strcpy_P(wii5BufferString ,(PGM_P) F("accelrange 2 set drop")); + break; + case 2: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Enables array[ 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]array set drop")); + break; + case 3: + if (binMode) + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 2 set drop")); + else + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 3 set drop")); + break; + case 4: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit cputime dvid@ set drop")); + break; + case 5: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit magp dvid@ set drop")); + break; + case 6: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit accelp dvid@ set drop")); + break; + case 7: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit roll dvid@ set drop")); + break; + case 8: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit pitch dvid@ set drop")); + break; + case 9: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit yaw dvid@ set drop")); + break; + case 10: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit gyrop dvid@ set drop")); + break; + case 11: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Trigger 6 set drop")); + break; + case 12: + if (HZ == 64) { + // 64Hz + strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 156 set drop")); + console.log(LOG_DEBUG, F("HZ 64, setting 100us timer to 156")); + } + else if (HZ == 8) { + // 8Hz + // TODO + // strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop")); + strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 1250 set drop")); + console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250")); + } + else if (HZ == 1) { + strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop")); + console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250")); + } + else { + console.log(LOG_FATAL, F("Invalid HZ for Sprton = %d"), HZ); + } + break; + case 13: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 1 set drop")); + break; + + // CODE = Cut down code for WII5.1 Tas Delivery - no Gyro or Mag + // PROGRAM=20..29 + case 20: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 0 set drop")); + break; + case 21: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Enables array[ 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]array set drop")); + break; + case 22: + if (binMode) + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 2 set drop")); + else + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 3 set drop")); + break; + case 23: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit cputime dvid@ set drop")); + break; + case 24: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit accelp dvid@ set drop")); + break; + case 25: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit roll dvid@ set drop")); + break; + case 26: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit pitch dvid@ set drop")); + break; + case 27: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit yaw dvid@ set drop")); + break; + case 28: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Trigger 6 set drop")); + break; + case 29: + if (HZ == 64) { + // 64Hz + strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 156 set drop")); + console.log(LOG_DEBUG, F("HZ 64, setting 100us timer to 156")); + } + else if (HZ == 8) { + // 8Hz + // TODO + // strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop")); + strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 1250 set drop")); + console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250")); + } + else if (HZ == 1) { + strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop")); + console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250")); + } + else { + console.log(LOG_FATAL, F("Invalid HZ for Sprton = %d"), HZ); + } + break; + case 30: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 1 set drop")); + break; + + // PROGRAM=0..12, START=13..13, PROGRAM+START=0..13, STOP=0..0 + case 50: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 0 set drop")); + break; + case 51: + strcpy_P(wii5BufferString ,(PGM_P) F("accelrange 2 set drop")); + break; + case 52: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Enables array[ 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]array set drop")); + break; + case 53: + if (binMode) + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 2 set drop")); + else + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 3 set drop")); + break; + case 54: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit cputime dvid@ set drop")); + break; + case 55: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit roll dvid@ set drop")); + break; + case 56: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit pitch dvid@ set drop")); + break; + case 57: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit yaw dvid@ set drop")); + break; + case 58: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0Trigger 6 set drop")); + break; + case 59: + if (HZ == 64) { + // 64Hz + strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 156 set drop")); + console.log(LOG_DEBUG, F("HZ 64, setting 100us timer to 156")); + } + else if (HZ == 8) { + // 8Hz + // TODO + // strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop")); + strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 1250 set drop")); + console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250")); + } + else if (HZ == 1) { + strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop")); + console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250")); + } + else { + console.log(LOG_FATAL, F("Invalid HZ for Sprton = %d"), HZ); + } + break; + case 60: + strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 1 set drop")); + break; + + + // Debugging / Logging + // 14-20 + case 80: + strcpy_PF(wii5BufferString ,F("chan0TriggerDivisor 0 set drop")); + break; + case 81: + strcpy_P(wii5BufferString, (PGM_P) F("name di.")); + break; + case 82: + strcpy_P(wii5BufferString, (PGM_P) F("serialnumber di.")); + break; + case 83: + strcpy_P(wii5BufferString, (PGM_P) F("VERSION di.")); + break; + case 84: + strcpy_P(wii5BufferString, (PGM_P) F("VERSION_M4 di.")); + break; + case 85: + strcpy_P(wii5BufferString, (PGM_P) F("gyroSampleRate di.")); + break; + case 86: + strcpy_P(wii5BufferString, (PGM_P) F("save_mask d.on")); + break; + case 87: + strcpy_P(wii5BufferString, (PGM_P) F("db.print")); + break; + + case 90: + strcpy_P(wii5BufferString, (PGM_P) F("VERSION di.")); + break; + + case 91: + strcpy_P(wii5BufferString, (PGM_P) F("chan0Enable.")); + break; + + case 100: + strcpy_P(wii5BufferString, (PGM_P) F("baud 8 set drop")); + break; + + default: + console.log(LOG_FATAL, F("Sparton: request invalid programming string")); + wii5BufferString[0] = '\0'; + break; + } +} + +WII5Sparton wii5Sparton; diff --git a/WII5Sparton.h b/WII5Sparton.h new file mode 100644 index 0000000..34ca8bd --- /dev/null +++ b/WII5Sparton.h @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Sparton.h + * @brief Sparton AHRS-M1/M2 IMU driver: capture timing, NorthTek configuration. + */ + +#ifndef WII5Sparton_h +#define WII5Sparton_h + +#include +#include +#include +#include +#include + +#define SPARTON_RECORDS 30 + +/* + +Sparton format + +// $CUS0, +// POSE * 1000 +// 5.007294e-01,-3.079974,294.518433, +// MAG * 1000 +// 70.337479,174.480072,-450.354431, +// ACCEL * 10,000 +// 3.233985,-53.680248,1007.986023, +// GYRO * 1000 +// 2.710441e-02,-2.003428e-02,7.615486e-02, +// 75988968,* + +float pose_x; +float pose_y; +float pose_z; +float mag_x; +float mag_y; +float mag_z; +float accel_x; +float accel_y; +float accel_z; +float gyro_x; +float gyro_y; +float gyro_z; +uint32_t stamp; + +chan0Enable. + + 8 + 0 + Float32 + + + 9 + 4 + Float32 + + + 10 + 8 + Float32 + + + 23 + 12 + Float32[3] + + + 25 + 24 + Float32[3] + + + 27 + 36 + Float32[3] + + + 249 + 48 + Int32 + + + +* 52 = Bytes of record size +* But records appear to be about 70+ Bytes + +New Record Types: + + +*/ + +enum WII5SPARTON_STEPS { + WII5SPARTON_OFF, + WII5SPARTON_BOOT, + WII5SPARTON_BOOT_BAUD, + WII5SPARTON_BOOT_STOP, + WII5SPARTON_BOOT_WAIT, + WII5SPARTON_READY, + WII5SPARTON_PROGRAM_BASE, + WII5SPARTON_PROGRAM_START, + WII5SPARTON_CAPTURE, + WII5SPARTON_PROGRAM_STOP, + WII5SPARTON_INFO, + WII5SPARTON_INFO_WAIT, + WII5SPARTON_CANCEL, + WII5SPARTON_FINISH +}; + +/** + * @brief Driver for the Sparton AHRS-M1 / AHRS-M2 IMU. + * + * Programs the AHRS via NorthTek-style commands over a UART, captures + * binary records (pose / mag / accel / gyro / cputime) at a configured + * sample rate, and writes them either straight to SD or via the console + * passthrough. State machine in WII5SPARTON_STEPS; record layout in + * WII5_DATA_SpartonBinary. + */ +class WII5Sparton : public WII5Power { + public: + WII5Sparton() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;} + virtual WII5_DRIVERS driverId() {return WII5DRIVER_SPARTON;} + + /** @brief One-time bring-up. */ + void begin(); + /** @brief State-machine tick: parses incoming records, advances steps. */ + void loop(); + + /** @brief Begin a programming + capture cycle. */ + virtual void start(); + /** @brief Query the AHRS for firmware/version info. */ + void info(); + /** @brief Abort the current cycle. */ + void stop(bool force = false); + + /** @brief Run an automated capture for `records` samples at `hz` Hz. */ + void automatic(uint8_t hz, uint32_t records, bool capture); + + /** @brief Reconfigure the IMU UART baud rate. */ + virtual void setBaudrate(uint32_t baud); + + /** @brief Apply power to the Sparton. */ + void on(bool forced = false); + /** @brief Cut power to the Sparton. */ + void off(bool forced = false); + + /** @brief Is a programming/capture cycle in flight? */ + bool isRunning(); + /** @brief Begin a capture (after programming completes). */ + void captureRun(); + /** @brief Are we currently capturing samples? */ + bool captureRunning(); + /** @brief Are we waiting for a capture trigger? */ + bool captureWaiting(); + /** @brief Has the most recent capture completed? */ + bool captureFinished(); + bool capture; + + time_t lastStartTime; + uint32_t lastRunTime; + + /** @brief Send a single NorthTek programming line to the IMU. */ + void sendLine(uint16_t l); + + /** @brief Enable/disable raw-record passthrough to the capture file. */ + void setRawCapture(bool in); + /** @brief Current raw-capture flag. */ + bool getRawCapture(); + + /** @brief Enable/disable console-direct output (used when no SD card is present). */ + void setConsoleDirect(bool in); + /** @brief Current console-direct flag. */ + bool getConsoleDirect(); + /** @brief Print capture statistics to the console. */ + virtual void displaySTATS(); + + /** @brief Current sample rate in Hz. */ + uint16_t getHz(); + /** @brief Current sample period in milliseconds. */ + uint16_t getMs(); + /** @brief Reconfigure the sample rate (Hz). */ + void setHz(uint16_t h); + /** @brief Enable/disable binary record format. */ + void setBinary(bool in); + + /** @brief Emit a single NorthTek programming line by index. */ + virtual void programLine(uint16_t l); + + /** @brief Enable/disable raw serial passthrough to the console. */ + void setPassthrough(bool in); + /** @brief Current passthrough flag. */ + bool getPassthrough(); + /** @brief Enable/disable verbose debug output. */ + void setDebug(bool in); + /** @brief Current debug flag. */ + bool getDebug(); + /** @brief Enable/disable capture-on-start. */ + void setCapture(bool in); + /** @brief Current capture flag. */ + bool getCapture(); + /** @brief Set the number of records to capture. */ + void setRecords(uint32_t in); + /** @brief Current record count target. */ + uint32_t getRecords(); + + /** @brief Drop into a NorthTek REPL for interactive configuration. */ + void repl(); + + /** @brief Reset internal capture buffers and counters. */ + void resetData(); + + // TODO int ? float, long ? + /* + int lastAccelX; + int lastAccelY; + int lastAccelZ; + int lastPoseX; + int lastPoseY; + int lastPoseZ; + */ + + // Last capture errors etc + uint32_t captureWriteMax; + uint32_t captureWriteMin; + uint32_t captureWriteOver; + uint32_t statsTimeError; + uint32_t serialSizeError; + + protected: + bool running; + bool cancel; + bool rawCapture; // Do we want to write raw data to capture file + bool consoleDirect; // Direct access + uint16_t HZ; + bool binMode; + bool debug; + + uint32_t recordTotal; + elapsedMillis elapsedRequest; + + // Inclusive + uint32_t sendCount; // Where are we up to in program data + uint16_t programStart; + uint16_t programEnd; + + // Internal steps + WII5SPARTON_STEPS step; + WII5SPARTON_STEPS stepLast; + elapsedMillis stepWait; + + void WII5Sparton::handleRxChar( uint8_t c ); + + uint32_t recordCount; + uint8_t lastValidRecord; + + // Binary Records + WII5_DATA_SpartonBinary binRecords[SPARTON_RECORDS]; + uint8_t currentRecord; + uint8_t currentCount; + + bool binEscape; + uint8_t processMode; + + + elapsedMillis startCapture; + elapsedMillis startWhen; + elapsedMillis lastWrite; + // elapsedMillis endWhen = 0; + uint32_t blockNext; + + elapsedMillis captureWriteTime; + + time_t timeStart; + time_t timeEnd; + +}; + +extern WII5Sparton wii5Sparton; + +#endif diff --git a/WII5Strings.cpp b/WII5Strings.cpp new file mode 100644 index 0000000..74e6717 --- /dev/null +++ b/WII5Strings.cpp @@ -0,0 +1,788 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Strings.cpp + * @brief String-formatting helpers: strDriver, strStatus, strError, etc. + */ + +/* + +WII5Strings - Parsing and Generating larger text strings + +*/ + +#include +#include + +// TODO This is hard coded to console +/* + + Parse Console commands + + +TODO: + * Lower case - but only as compared + * NOW = lower case only + * + * Move literals ("hellow") etc into #DEFINE or similar + * Consider shorter strings - e.g allow "he" instead of hello + +*/ +// TODO 3 inputs - not using Console !!! +// TODO Returns command (could have consumed 1..3 entries) +// TODO Returns params (could have converted strings to numbers, but numbers returned) +// - Consisiting of the rest of the params consumed +// TODO how? +WII5_COMMANDS WII5Strings::parseCommand(char* in1, char* in2, char* in3) { + // 1 - One level entries + if (strcmp_P(in1,(PGM_P) F("hello")) == 0) { + return WII5COMMAND_HELLO; + } + else if (strcmp_P(in1,(PGM_P) F("hello!")) == 0) { + return WII5COMMAND_HELLOACK; + } + else if (strcmp_P(in1,(PGM_P) F("dump")) == 0) { + return WII5COMMAND_DUMP; + } + else if (strcmp_P(in1,(PGM_P) F("status")) == 0) { + return WII5COMMAND_STATUS; + } + else if (strcmp_P(in1,(PGM_P) F("waitx")) == 0) { + return WII5COMMAND_WAITX; + } + else if (strcmp_P(in1,(PGM_P) F("waity")) == 0) { + return WII5COMMAND_WAITY; + } + else if (strcmp_P(in1,(PGM_P) F("waitz")) == 0) { + return WII5COMMAND_WAITZ; + } + else if (strcmp_P(in1,(PGM_P) F("waitq")) == 0) { + return WII5COMMAND_WAITQ; + } + else if (strcmp_P(in1,(PGM_P) F("waits")) == 0) { + return WII5COMMAND_WAITS; + } + else if (strcmp_P(in1,(PGM_P) F("help")) == 0) { + return WII5COMMAND_HELP; + } + else if (strcmp_P(in1,(PGM_P) F("reset")) == 0) { + return WII5COMMAND_RESET; + } + else if (strcmp_P(in1,(PGM_P) F("echo")) == 0) { + return WII5COMMAND_ECHO; + } + else if (strcmp_P(in1,(PGM_P) F("people")) == 0) { + return WII5COMMAND_PEOPLE; + } + else if (strcmp_P(in1,(PGM_P) F("set")) == 0) { + return WII5COMMAND_SET; + } + else if (strcmp_P(in1,(PGM_P) F("send")) == 0) { + return WII5COMMAND_SEND; + } + + // GPS + else if (strcmp_P(in1,(PGM_P) F("gps")) == 0) { + if (strcmp_P(in2,(PGM_P) F("dump")) == 0) + return WII5COMMAND_GPS_DUMP; + else if (strcmp_P(in2,(PGM_P) F("passthrough")) == 0) + return WII5COMMAND_GPS_PASSTHROUGH; + else if (strcmp_P(in2,(PGM_P) F("debug")) == 0) + return WII5COMMAND_GPS_DEBUG; + else if (strcmp_P(in2,(PGM_P) F("start")) == 0) + return WII5COMMAND_GPS_START; + else if (strcmp_P(in2,(PGM_P) F("stop")) == 0) + return WII5COMMAND_GPS_STOP; + else if (strcmp_P(in2,(PGM_P) F("time")) == 0) + return WII5COMMAND_GPS_TIME; + else if (strcmp_P(in2,(PGM_P) F("pos")) == 0) + return WII5COMMAND_GPS_POS; + else if (strcmp_P(in2,(PGM_P) F("accurate")) == 0) + return WII5COMMAND_GPS_ACCURATE; + } + + // SPARTON + else if (strcmp_P(in1,(PGM_P) F("sparton")) == 0) { + if (strcmp_P(in2,(PGM_P) F("mode")) == 0) + return WII5COMMAND_SPARTON_MODE; + else if (strcmp_P(in2,(PGM_P) F("dump")) == 0) + return WII5COMMAND_SPARTON_DUMP; + else if (strcmp_P(in2,(PGM_P) F("passthrough")) == 0) + return WII5COMMAND_SPARTON_PASSTHROUGH; + else if (strcmp_P(in2,(PGM_P) F("debug")) == 0) + return WII5COMMAND_SPARTON_DEBUG; + else if (strcmp_P(in2,(PGM_P) F("info")) == 0) + return WII5COMMAND_SPARTON_INFO; + } + + // MATHS + else if (strcmp_P(in1,(PGM_P) F("maths")) == 0) { + if (strcmp_P(in2,(PGM_P) F("start")) == 0) + return WII5COMMAND_MATHS_START; + else if (strcmp_P(in2,(PGM_P) F("stop")) == 0) + return WII5COMMAND_MATHS_STOP; + else if (strcmp_P(in2,(PGM_P) F("beep")) == 0) + return WII5COMMAND_MATHS_BEEP; + else if (strcmp_P(in2,(PGM_P) F("restart")) == 0) + return WII5COMMAND_MATHS_RESTART; + else if (strcmp_P(in2,(PGM_P) F("flip")) == 0) + return WII5COMMAND_MATHS_FLIP; + else if (strcmp_P(in2,(PGM_P) F("hold")) == 0) + return WII5COMMAND_MATHS_HOLD; + else if (strcmp_P(in2,(PGM_P) F("until")) == 0) + return WII5COMMAND_MATHS_UNTIL; + else if (strcmp_P(in2,(PGM_P) F("info")) == 0) + return WII5COMMAND_MATHS_INFO; + else if (strcmp_P(in2,(PGM_P) F("halt")) == 0) + return WII5COMMAND_MATHS_HALT; + else if (strcmp_P(in2,(PGM_P) F("do")) == 0) + return WII5COMMAND_MATHS_DO; + else if (strcmp_P(in2,(PGM_P) F("make")) == 0) + return WII5COMMAND_MATHS_MAKE; + else if (strcmp_P(in2,(PGM_P) F("script")) == 0) + return WII5COMMAND_MATHS_SCRIPT; + else if (strcmp_P(in2,(PGM_P) F("booted")) == 0) + return WII5COMMAND_MATHS_BOOTED; + else if (strcmp_P(in2,(PGM_P) F("return")) == 0) + return WII5COMMAND_MATHS_RETURN; + } + + // LOG + // @WII5,log,debug|deafult|fatal + else if (strcmp_P(in1,(PGM_P) F("log")) == 0) { + if (strcmp_P(in2,(PGM_P) F("debug")) == 0) + return WII5COMMAND_LOG_DEBUG; + else if (strcmp_P(in2,(PGM_P) F("default")) == 0) + return WII5COMMAND_LOG_DEFAULT; + else if (strcmp_P(in2,(PGM_P) F("error")) == 0) + return WII5COMMAND_LOG_ERROR; + else if (strcmp_P(in2,(PGM_P) F("fatal")) == 0) + return WII5COMMAND_LOG_FATAL; + else if (strcmp_P(in2,(PGM_P) F("test")) == 0) + return WII5COMMAND_LOG_TEST; + } + + // SETTINGS + // @WII5,setting,deviceid,10045 + else if (strcmp_P(in1,(PGM_P) F("setting")) == 0) { + if (strcmp_P(in2,(PGM_P) F("list")) == 0) + return WII5COMMAND_SETTING_LIST; + else if (strcmp_P(in2,(PGM_P) F("deviceid")) == 0) + return WII5COMMAND_SETTING_DEVICEID; + else if (strcmp_P(in2,(PGM_P) F("defaults")) == 0) + return WII5COMMAND_SETTING_DEFAULTS; + else if (strcmp_P(in2,(PGM_P) F("gpstimeout")) == 0) + return WII5COMMAND_SETTING_GPSTIMEOUT; + else if (strcmp_P(in2,(PGM_P) F("mode")) == 0) + return WII5COMMAND_SETTING_MODE; + else if (strcmp_P(in2,(PGM_P) F("time")) == 0) + return WII5COMMAND_SETTING_TIME; + + else if (strcmp_P(in2,(PGM_P) F("disablelowbattery")) == 0) + return WII5COMMAND_SETTING_DISABLELOWBATTERY; + + // FLAGS - all,uint32_t or X,0|1 + else if (strcmp_P(in2,(PGM_P) F("flags")) == 0) + return WII5COMMAND_SETTING_FLAGS; + + // OPTIONS - pass in {PERIOD} and {BINARYTYPE} - 0=ignore + else if (strcmp_P(in2,(PGM_P) F("captureoptions")) == 0) + return WII5COMMAND_SETTING_CAPTUREOPTIONS; + else if (strcmp_P(in2,(PGM_P) F("positionoptions")) == 0) + return WII5COMMAND_SETTING_POSITIONOPTIONS; + else if (strcmp_P(in2,(PGM_P) F("sleepoptions")) == 0) + return WII5COMMAND_SETTING_SLEEPOPTIONS; + else if (strcmp_P(in2,(PGM_P) F("batteryoptions")) == 0) + return WII5COMMAND_SETTING_BATTERYOPTIONS; + } + + // NETWORK + else if (strcmp_P(in1,(PGM_P) F("network")) == 0) { + if (strcmp_P(in2,(PGM_P) F("echo")) == 0) + return WII5COMMAND_NETWORK_ECHO; + else if (strcmp_P(in2,(PGM_P) F("echoack")) == 0) + return WII5COMMAND_NETWORK_ECHOACK; + else if (strcmp_P(in2,(PGM_P) F("fakeecho")) == 0) + return WII5COMMAND_NETWORK_FAKEECHO; + else if (strcmp_P(in2,(PGM_P) F("fakeechoack")) == 0) + return WII5COMMAND_NETWORK_FAKEECHOACK; + } + + // STORAGE + // Storage + else if (strcmp_P(in1,(PGM_P) F("storage")) == 0) { + if (strcmp_P(in2,(PGM_P) F("format")) == 0) + return WII5COMMAND_STORAGE_FORMAT; + else if (strcmp_P(in2,(PGM_P) F("testfile")) == 0) + return WII5COMMAND_STORAGE_TESTFILE; + else if (strcmp_P(in2,(PGM_P) F("setstatus")) == 0) + return WII5COMMAND_STORAGE_SETSTATUS; + else if (strcmp_P(in2,(PGM_P) F("off")) == 0) + return WII5COMMAND_STORAGE_OFF; + else if (strcmp_P(in2,(PGM_P) F("sd1")) == 0) + return WII5COMMAND_STORAGE_SD1; + else if (strcmp_P(in2,(PGM_P) F("sd2")) == 0) + return WII5COMMAND_STORAGE_SD2; + else if (strcmp_P(in2,(PGM_P) F("list")) == 0) + return WII5COMMAND_STORAGE_LIST; + else if (strcmp_P(in2,(PGM_P) F("view")) == 0) + return WII5COMMAND_STORAGE_VIEW; + else if (strcmp_P(in2,(PGM_P) F("metadata")) == 0) + return WII5COMMAND_STORAGE_METADATA; + else if (strcmp_P(in2,(PGM_P) F("results")) == 0) + return WII5COMMAND_STORAGE_RESULTS; + else if (strcmp_P(in2,(PGM_P) F("raw")) == 0) + return WII5COMMAND_STORAGE_RAW; + else if (strcmp_P(in2,(PGM_P) F("debug")) == 0) + return WII5COMMAND_STORAGE_DEBUG; + else if (strcmp_P(in2,(PGM_P) F("status")) == 0) + return WII5COMMAND_STORAGE_STATUS; + } + + // Battery + else if (strcmp_P(in1,(PGM_P) F("battery")) == 0) { + if (strcmp_P(in2,(PGM_P) F("start")) == 0) + return WII5COMMAND_BATTERY_START; + } + + // Weather + else if (strcmp_P(in1,(PGM_P) F("weather")) == 0) { + if (strcmp_P(in2,(PGM_P) F("read")) == 0) + return WII5COMMAND_WEATHER_READ; + else if (strcmp_P(in2,(PGM_P) F("test")) == 0) + return WII5COMMAND_WEATHER_TEST; + } + + // CAPTURE + else if (strcmp_P(in1,(PGM_P) F("capture")) == 0) { + if (strcmp_P(in2,(PGM_P) F("start")) == 0) + return WII5COMMAND_CAPTURE_START; + else if (strcmp_P(in2,(PGM_P) F("stop")) == 0) + return WII5COMMAND_CAPTURE_STOP; + } + + // POSITION + else if (strcmp_P(in1,(PGM_P) F("position")) == 0) { + if (strcmp_P(in2,(PGM_P) F("start")) == 0) + return WII5COMMAND_POSITION_START; + else if (strcmp_P(in2,(PGM_P) F("stop")) == 0) + return WII5COMMAND_POSITION_STOP; + else if (strcmp_P(in2,(PGM_P) F("passthrough")) == 0) + return WII5COMMAND_POSITION_PASSTHROUGH; + } + + // Mode + else if (strcmp_P(in1,(PGM_P) F("mode")) == 0) { + if (strcmp_P(in2,(PGM_P) F("position")) == 0) + return WII5COMMAND_MODE_POSITION; + else if (strcmp_P(in2,(PGM_P) F("manualtest")) == 0) + return WII5COMMAND_MODE_MANUALTEST; + else if (strcmp_P(in2,(PGM_P) F("selftest")) == 0) + return WII5COMMAND_MODE_SELFTEST; + else if (strcmp_P(in2,(PGM_P) F("capture")) == 0) + return WII5COMMAND_MODE_CAPTURE; + else if (strcmp_P(in2,(PGM_P) F("sleep")) == 0) + return WII5COMMAND_MODE_SLEEP; + else if (strcmp_P(in2,(PGM_P) F("manualtest")) == 0) + return WII5COMMAND_MODE_MANUALTEST; + else if (strcmp_P(in2,(PGM_P) F("default")) == 0) + return WII5COMMAND_MODE_DEFAULT; + } + + // Iridium + else if (strcmp_P(in1,(PGM_P) F("iridium")) == 0) { + if (strcmp_P(in2,(PGM_P) F("send")) == 0) + return WII5COMMAND_IRIDIUM_SEND; + } + + // Communication + else if (strcmp_P(in1,(PGM_P) F("communications")) == 0) { + if (strcmp_P(in2,(PGM_P) F("disabled")) == 0) + return WII5COMMAND_COMMUNICATIONS_DISABLED; + else if (strcmp_P(in2,(PGM_P) F("test")) == 0) + return WII5COMMAND_COMMUNICATIONS_TEST; + else if (strcmp_P(in2,(PGM_P) F("signal")) == 0) + return WII5COMMAND_COMMUNICATIONS_SIGNAL; + else if (strcmp_P(in2,(PGM_P) F("start")) == 0) + return WII5COMMAND_COMMUNICATIONS_START; + else if (strcmp_P(in2,(PGM_P) F("stop")) == 0) + return WII5COMMAND_COMMUNICATIONS_STOP; + } + + // Bindata + else if (strcmp_P(in1,(PGM_P) F("bindata")) == 0) { + if (strcmp_P(in2,(PGM_P) F("list")) == 0) + return WII5COMMAND_BINDATA_LIST; + else if (strcmp_P(in2,(PGM_P) F("split")) == 0) + return WII5COMMAND_BINDATA_SPLIT; + } + + return WII5COMMAND_NONE; +} + +WII5_MODES WII5Strings::parseMode(char *in) { + if (strcmp_P(in,(PGM_P) F("boot")) == 0) return WII5MODE_BOOT; + else if (strcmp_P(in,(PGM_P) F("start")) == 0) return WII5MODE_START; + else if (strcmp_P(in,(PGM_P) F("demo")) == 0) return WII5MODE_DEMO; + else if (strcmp_P(in,(PGM_P) F("lowbattery")) == 0) return WII5MODE_LOWBATTERY; + else if (strcmp_P(in,(PGM_P) F("position")) == 0) return WII5MODE_POSITION; + else if (strcmp_P(in,(PGM_P) F("capture")) == 0) return WII5MODE_CAPTURE; + else if (strcmp_P(in,(PGM_P) F("sleep")) == 0) return WII5MODE_SLEEP; + else if (strcmp_P(in,(PGM_P) F("manualtest")) == 0) return WII5MODE_MANUALTEST; + else if (strcmp_P(in,(PGM_P) F("selftest")) == 0) return WII5MODE_SELFTEST; + return WII5MODE_NONE; +} +WII5_SWITCH WII5Strings::parseSwitch(char *in) { + if ( + (strcmp_P(in,(PGM_P) F("on")) == 0) + || (strcmp_P(in,(PGM_P) F("yes")) == 0) + || (strcmp_P(in,(PGM_P) F("true")) == 0) + ) { + return WII5SWITCH_ON; + } + else if ( + (strcmp_P(in,(PGM_P) F("off")) == 0) + || (strcmp_P(in,(PGM_P) F("no")) == 0) + || (strcmp_P(in,(PGM_P) F("false")) == 0) + ) { + return WII5SWITCH_OFF; + } + else if ( + (strcmp_P(in,(PGM_P) F("toggle")) == 0) + ) { + return WII5SWITCH_TOGGLE; + } + return WII5SWITCH_NONE; +} + +// TODO Deprecate? +WII5GPS_MODES WII5Strings::parseGPSMode(char *in) { + if ( + (strcmp_P(in,(PGM_P) F("off")) == 0) + ) + return WII5GPS_OFF; + else if ( + (strcmp_P(in,(PGM_P) F("on")) == 0) + ) + return WII5GPS_ON; + else if ( + (strcmp_P(in,(PGM_P) F("time")) == 0) + ) + return WII5GPS_TIME; + else if ( + (strcmp_P(in,(PGM_P) F("pos")) == 0) + ) + return WII5GPS_POS; + else if ( + (strcmp_P(in,(PGM_P) F("accurate")) == 0) + ) + return WII5GPS_ACCURATE; + else if ( + (strcmp_P(in,(PGM_P) F("repeat")) == 0) + ) + return WII5GPS_REPEAT; + return WII5GPS_UNKNOWN; +} + +WII5_DEVICES WII5Strings::parseDevice(char *in) { + if (strcmp_P(in,(PGM_P) F("wavebuoy")) == 0) return WII5DEVICE_WAVEBUOY; + else if (strcmp_P(in,(PGM_P) F("maths")) == 0) return WII5DEVICE_MATHS; + else if (strcmp_P(in,(PGM_P) F("controller")) == 0) return WII5DEVICE_CONTROLLER; + else if (strcmp_P(in,(PGM_P) F("cli")) == 0) return WII5DEVICE_CLI; + else if (strcmp_P(in,(PGM_P) F("server")) == 0) return WII5DEVICE_SERVER; + return WII5DEVICE_UNKNOWN; +} +WII5_DRIVERS WII5Strings::parseDriver(char *in) { + // TODO + return WII5DRIVER_GPS; +} +WII5_PORTS WII5Strings::parsePort(char *in) { + return WII5PORT_CONSOLE; +} + +void WII5Strings::setBufferUnknown() { + strcpy_P(wii5BufferString ,(PGM_P) F("unknown")); +} + +char* WII5Strings::strCommand(WII5_COMMANDS c) { + /* + switch(d) { + case WII5DEVICE_WAVEBUOY: strcpy_P(wii5BufferString ,(PGM_P) F("wavebuoy")); break; + case WII5DEVICE_MATHS: strcpy_P(wii5BufferString ,(PGM_P) F("maths")); break; + case WII5DEVICE_CONTROLLER: strcpy_P(wii5BufferString ,(PGM_P) F("controller")); break; + case WII5DEVICE_CLI: strcpy_P(wii5BufferString ,(PGM_P) F("cli")); break; + case WII5DEVICE_SERVER: strcpy_P(wii5BufferString ,(PGM_P) F("server")); break; + default: setBufferUnknown(); break; + }; + */ + setBufferUnknown(); + return wii5BufferString; +} +char* WII5Strings::strDevice(WII5_DEVICES d) { + switch(d) { + case WII5DEVICE_WAVEBUOY: strcpy_P(wii5BufferString ,(PGM_P) F("wavebuoy")); break; + case WII5DEVICE_MATHS: strcpy_P(wii5BufferString ,(PGM_P) F("maths")); break; + case WII5DEVICE_CONTROLLER: strcpy_P(wii5BufferString ,(PGM_P) F("controller")); break; + case WII5DEVICE_CLI: strcpy_P(wii5BufferString ,(PGM_P) F("cli")); break; + case WII5DEVICE_SERVER: strcpy_P(wii5BufferString ,(PGM_P) F("server")); break; + default: setBufferUnknown(); break; + }; + return wii5BufferString; +} +char* WII5Strings::strPort(WII5_PORTS p) { + setBufferUnknown(); + return wii5BufferString; +} +char* WII5Strings::strStatus(WII5_STATUS s) { + switch(s) { + case WII5STATUS_OFF: strcpy_P(wii5BufferString ,(PGM_P) F("off")); break; + case WII5STATUS_ON: strcpy_P(wii5BufferString ,(PGM_P) F("on")); break; + case WII5STATUS_ON_VALID: strcpy_P(wii5BufferString ,(PGM_P) F("valid")); break; + case WII5STATUS_BUSY: strcpy_P(wii5BufferString ,(PGM_P) F("busy")); break; + case WII5STATUS_WAITING: strcpy_P(wii5BufferString ,(PGM_P) F("waiting")); break; + case WII5STATUS_ERROR: strcpy_P(wii5BufferString ,(PGM_P) F("error")); break; + default: setBufferUnknown(); break; + } + return wii5BufferString; +} +char* WII5Strings::strError(WII5_ERRORS e) { + switch(e) { + case WII5ERROR_RETRY: strcpy_P(wii5BufferString ,(PGM_P) F("RETRY")); break; + case WII5ERROR_INIT: strcpy_P(wii5BufferString ,(PGM_P) F("INIT")); break; + case WII5ERROR_MEMORY: strcpy_P(wii5BufferString ,(PGM_P) F("MEMORY")); break; + case WII5ERROR_TIMEOUT: strcpy_P(wii5BufferString ,(PGM_P) F("TIMEOUT")); break; + case WII5ERROR_FATAL: strcpy_P(wii5BufferString ,(PGM_P) F("FATAL")); break; + default: setBufferUnknown(); break; + } + return wii5BufferString; +} +char* WII5Strings::strSwitch(WII5_SWITCH s) { + strcpy_P(wii5BufferString ,(PGM_P) F("UnknownSwitch")); + return wii5BufferString; +} + +// TODO instead of strDriver - and getting +// the wii5BufferString set. Return the PROGMEM string +// and then use a helper function toBuffer or similar to change. +// As PROGMEM returns have use. +char* WII5Strings::strDriver(WII5_DRIVERS d) { + switch(d) { + case WII5DRIVER_GPS: strcpy_P(wii5BufferString ,(PGM_P) F("GPS")); break; + case WII5DRIVER_COMMS: strcpy_P(wii5BufferString ,(PGM_P) F("Comms")); break; + case WII5DRIVER_COMMS_IRIDIUM: strcpy_P(wii5BufferString ,(PGM_P) F("CommsIridium")); break; + case WII5DRIVER_MATHS: strcpy_P(wii5BufferString ,(PGM_P) F("Maths")); break; + case WII5DRIVER_POWER: strcpy_P(wii5BufferString ,(PGM_P) F("Power")); break; + case WII5DRIVER_MPU9250: strcpy_P(wii5BufferString ,(PGM_P) F("MPU9250")); break; + case WII5DRIVER_SPARTON: strcpy_P(wii5BufferString ,(PGM_P) F("Sparton")); break; + case WII5DRIVER_STORAGE: strcpy_P(wii5BufferString ,(PGM_P) F("Storage")); break; + case WII5DRIVER_WEATHER_18B20: strcpy_P(wii5BufferString ,(PGM_P) F("Weather_18B20")); break; + case WII5DRIVER_BATTERY: strcpy_P(wii5BufferString ,(PGM_P) F("Battery")); break; + default: setBufferUnknown(); break; + } + return wii5BufferString; +} + +// TODO DEPRECATE +char* WII5Strings::strGPSMode(WII5GPS_MODES g) { + switch(g) { + case WII5GPS_OFF: strcpy_P(wii5BufferString ,(PGM_P) F("Off")); break; + case WII5GPS_ON: strcpy_P(wii5BufferString ,(PGM_P) F("On")); break; + case WII5GPS_TIME: strcpy_P(wii5BufferString ,(PGM_P) F("Time")); break; + case WII5GPS_POS: strcpy_P(wii5BufferString ,(PGM_P) F("Pos")); break; + case WII5GPS_ACCURATE: strcpy_P(wii5BufferString ,(PGM_P) F("Accurate")); break; + case WII5GPS_REPEAT: strcpy_P(wii5BufferString ,(PGM_P) F("Repeat")); break; + default: setBufferUnknown(); break; + } + return wii5BufferString; +} + +char* WII5Strings::strMode(WII5_MODES g) { + switch(g) { + case WII5MODE_BOOT: strcpy_P(wii5BufferString, (PGM_P) F("boot")); break; + case WII5MODE_START: strcpy_P(wii5BufferString, (PGM_P) F("start")); break; + case WII5MODE_DEMO: strcpy_P(wii5BufferString, (PGM_P) F("demo")); break; + case WII5MODE_SLEEP: strcpy_P(wii5BufferString, (PGM_P) F("sleep")); break; + case WII5MODE_LOWBATTERY: strcpy_P(wii5BufferString, (PGM_P) F("lowbattery")); break; + case WII5MODE_POSITION: strcpy_P(wii5BufferString, (PGM_P) F("position")); break; + case WII5MODE_CAPTURE: strcpy_P(wii5BufferString, (PGM_P) F("capture")); break; + case WII5MODE_MANUALTEST: strcpy_P(wii5BufferString, (PGM_P) F("manualtest")); break; + case WII5MODE_SELFTEST: strcpy_P(wii5BufferString, (PGM_P) F("selftest")); break; + default: setBufferUnknown(); break; + } + return wii5BufferString; +} + +char* WII5Strings::strPinMode(uint8_t p) { + if (p == INPUT) strcpy_P(wii5BufferString, (PGM_P) F("INPUT ")); + else if (p == INPUT_PULLUP) strcpy_P(wii5BufferString, (PGM_P) F("INPUT_PULLUP")); + else if (p == OUTPUT) strcpy_P(wii5BufferString, (PGM_P) F("OUTPUT ")); + else strcpy_P(wii5BufferString ,(PGM_P) F("Unknown ")); + return wii5BufferString; +} + +char* WII5Strings::strState(uint8_t p) { + if (p == HIGH) strcpy_P(wii5BufferString, (PGM_P) F("HIGH")); + else if (p == LOW) strcpy_P(wii5BufferString, (PGM_P) F("low ")); + else strcpy_P(wii5BufferString ,(PGM_P) F("????")); + return wii5BufferString; +} + +char* WII5Strings::strPinArduinoName(uint8_t p) { + // Analopg Pin Number + strcpy_P(wii5BufferString ,(PGM_P) F(" ")); + if ((p >= A0) && (p < (A0 + NUM_ANALOG_INPUTS))) { + uint8_t a = p - A0; + if (a < 10) { + wii5BufferString[0] = 'A'; + wii5BufferString[1] = '0'; + wii5BufferString[2] = '0' + a; + } + else { + wii5BufferString[0] = 'A'; + wii5BufferString[1] = '1'; + wii5BufferString[2] = '0' + (a - 10); + } + return wii5BufferString; + } + switch(p) { + // TODO Needs config - not hard coded + + case 0: + strcpy_P(wii5BufferString ,(PGM_P) F("RX0 ")); + break; + case 1: + strcpy_P(wii5BufferString ,(PGM_P) F("TX0 ")); + break; + case 14: + strcpy_P(wii5BufferString ,(PGM_P) F("TX3 ")); + break; + case 15: + strcpy_P(wii5BufferString ,(PGM_P) F("RX3 ")); + break; + /* + case 16: + strcpy_P(wii5BufferString ,(PGM_P) F("TX2 ")); + break; + case 17: + strcpy_P(wii5BufferString ,(PGM_P) F("RX2 ")); + break; + */ + case 18: + strcpy_P(wii5BufferString ,(PGM_P) F("TX1 ")); + break; + case 19: + strcpy_P(wii5BufferString ,(PGM_P) F("RX1 ")); + break; + + case SS: + strcpy_P(wii5BufferString ,(PGM_P) F("SS ")); + break; + case MOSI: + strcpy_P(wii5BufferString ,(PGM_P) F("MOSI ")); + break; + case MISO: + strcpy_P(wii5BufferString ,(PGM_P) F("MISO ")); + break; + case SCK: + strcpy_P(wii5BufferString ,(PGM_P) F("SCK ")); + break; + case SDA: + strcpy_P(wii5BufferString ,(PGM_P) F("SDA ")); + break; + case SCL: + strcpy_P(wii5BufferString ,(PGM_P) F("SCL ")); + break; + } + return wii5BufferString; +} + +char* WII5Strings::strPinWII5Name(uint8_t p) { + strcpy_P(wii5BufferString ,(PGM_P) F(" ")); + switch(p) { + #if LED_1>0 + case LED_1: + strcpy_P(wii5BufferString ,(PGM_P) F("LED1 ")); + break; + #endif + #if LED_2>0 + case LED_2: + strcpy_P(wii5BufferString ,(PGM_P) F("LED2/Buzz ")); + break; + #endif + #if LED_3>0 + case LED_3: + strcpy_P(wii5BufferString ,(PGM_P) F("LED3 ")); + break; + #endif + #if LED_4>0 + case LED_4: + strcpy_P(wii5BufferString ,(PGM_P) F("LED4 ")); + break; + #endif + #if BUTTON_1>0 + case BUTTON_1: + strcpy_P(wii5BufferString ,(PGM_P) F("BTN1 ")); + break; + #endif + #if BUTTON_2>0 + case BUTTON_2: + strcpy_P(wii5BufferString ,(PGM_P) F("BTN2 ")); + break; + #endif + #if BUTTON_3>0 + case BUTTON_3: + strcpy_P(wii5BufferString ,(PGM_P) F("BTN3 ")); + break; + #endif + #if BUTTON_4>0 + case BUTTON_4: + strcpy_P(wii5BufferString ,(PGM_P) F("BTN4 ")); + break; + #endif + #ifdef POWER_MATHS_PIN + case POWER_MATHS_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr Maths ")); + break; + #endif + case POWER_RADIO_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr Radio ")); + break; + case POWER_COMMS_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr Comms ")); + break; + case POWER_GPS_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr GPS ")); + break; + #ifdef POWER_STROBE1_PIN + case POWER_STROBE1_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr Strob1")); + break; + #endif + #ifdef POWER_STROBE1_PIN + case POWER_STROBE2_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr Strob2")); + break; + #endif + #ifdef POWER_IMU_MPU9250_PIN + case POWER_IMU_MPU9250_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr 9250 ")); + break; + #endif + #ifdef POWER_IMU_SERIAL_PIN + case POWER_IMU_SERIAL_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr Spartn")); + break; + #endif + case WEATHER_18B20_DATA: + strcpy_P(wii5BufferString, (PGM_P) F("Data 18B20")); + break; + case POWER_WEATHER_18B20_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr 18B20 ")); + break; + case POWER_WEATHER_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr Weathr")); + break; + case BATTERY_1_VOLTS_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Bat1 Meas ")); + break; + #if POWER_SUBBOARD_1_PIN>0 + case POWER_SUBBOARD_1_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr Sub1 ")); + break; + #endif + #if POWER_SUBBOARD_1_PIN>0 + case POWER_SUBBOARD_2_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr Sub2 ")); + break; + #endif + #if POWER_STORAGE_1_PIN>0 + case POWER_STORAGE_1_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr SD 1 ")); + break; + #endif + #if POWER_STORAGE_2_PIN>0 + case POWER_STORAGE_2_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Pwr SD 2 ")); + break; + #endif + + #ifdef STORAGE_SD1_MATHS_PIN + case STORAGE_SD1_MATHS_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("SEL SD 1 ")); + break; + #endif + #ifdef STORAGE_SD2_MATHS_PIN 57 + case STORAGE_SD2_MATHS_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("SEL SD 2 ")); + break; + #endif + + #ifdef SHARED_5VOLT_PIN + case SHARED_5VOLT_PIN: + strcpy_P(wii5BufferString, (PGM_P) F("Shrd 5Volt")); + break; + #endif + } + return wii5BufferString; +} + +char* WII5Strings::strSelfTestDevice(WII5SELFTEST_DEVICES in) { + switch(in) { + case WII5STD_BOARD: strcpy_P(wii5BufferString, (PGM_P) F("Board")); break; + case WII5STD_GPS: strcpy_P(wii5BufferString, (PGM_P) F("GPS")); break; + case WII5STD_EEPROM: strcpy_P(wii5BufferString, (PGM_P) F("EEPROM")); break; + case WII5STD_SD1: strcpy_P(wii5BufferString, (PGM_P) F("SD1")); break; + case WII5STD_SD2: strcpy_P(wii5BufferString, (PGM_P) F("SD2")); break; + case WII5STD_IRIDIUM: strcpy_P(wii5BufferString, (PGM_P) F("Iridium")); break; + case WII5STD_BATTERY: strcpy_P(wii5BufferString, (PGM_P) F("Battery")); break; + case WII5STD_MATHS: strcpy_P(wii5BufferString, (PGM_P) F("Maths")); break; + case WII5STD_RTC: strcpy_P(wii5BufferString, (PGM_P) F("RTC")); break; + case WII5STD_SPARTON: strcpy_P(wii5BufferString, (PGM_P) F("Sparton")); break; + case WII5STD_18B20: strcpy_P(wii5BufferString, (PGM_P) F("18B20")); break; + case WII5STD_BUTTON: strcpy_P(wii5BufferString, (PGM_P) F("Button")); break; + case WII5STD_LED: strcpy_P(wii5BufferString, (PGM_P) F("LED")); break; + case WII5STD_BUZZER: strcpy_P(wii5BufferString, (PGM_P) F("Buzzer")); break; + case WII5STD_5VOLT: strcpy_P(wii5BufferString, (PGM_P) F("5Volts")); break; + // TODO LED, Button, Buzzer, TEmperature sensor + default: setBufferUnknown(); break; + } + return wii5BufferString; +}; + +char* WII5Strings::strSelfTestStatus(WII5SELFTEST_STATUS in) { + switch(in) { + case WII5STS_NONE: strcpy_P(wii5BufferString, (PGM_P) F("NONE")); break; // , // Never run + case WII5STS_NC: strcpy_P(wii5BufferString, (PGM_P) F("NC")); break; // , // Not connected, not enabled, or not applicable + case WII5STS_SUCCESS: strcpy_P(wii5BufferString, (PGM_P) F("Success")); break; // , // SUCCCESS - literally nothing to report + case WII5STS_HUMAN: strcpy_P(wii5BufferString, (PGM_P) F("Human")); break; // , // HUMAN - we assume success, but need a human to confirm + case WII5STS_SLOW: strcpy_P(wii5BufferString, (PGM_P) F("Slow")); break; // , // SLOW - SUCCESS in all ways, but slower than expected. Good for SD, Maths boot, GPS lock etc + case WII5STS_POWER: strcpy_P(wii5BufferString, (PGM_P) F("Power")); break; // , // Works, but power management may have failed (e.g. serial data still coming in when power off) + case WII5STS_TIMEOUT: strcpy_P(wii5BufferString, (PGM_P) F("Timeout")); break; // , // Literally timed out. No failure we can actually understand. + case WII5STS_OUTOFRANGE: strcpy_P(wii5BufferString, (PGM_P) F("OutOfRange")); break; // , // Out of range - voltage, or GPS or something out of range + case WII5STS_UNKNOWN: strcpy_P(wii5BufferString, (PGM_P) F("Unknown")); break; // , // Some sort of bad, unknown error + default: setBufferUnknown(); break; + } + return wii5BufferString; +}; + +char* WII5Strings::strSelfTestStatusLong(WII5SELFTEST_STATUS in) { + switch(in) { + case WII5STS_NONE: strcpy_P(wii5BufferString, (PGM_P) F("Never been run")); break; // , // Never run + case WII5STS_NC: strcpy_P(wii5BufferString, (PGM_P) F("Not connected or enabled")); break; // , // Not connected, not enabled, or not applicable + case WII5STS_SUCCESS: strcpy_P(wii5BufferString, (PGM_P) F("Nothing wrong. Fantastic!")); break; // , // SUCCCESS - literally nothing to report + case WII5STS_HUMAN: strcpy_P(wii5BufferString, (PGM_P) F("Human needed to see results - e.g. did the LED or buzzer go off")); break; // , // HUMAN - we assume success, but need a human to confirm + case WII5STS_SLOW: strcpy_P(wii5BufferString, (PGM_P) F("Success but slow - e.g. long time to load SD or slow gps lock")); break; // , // SLOW - SUCCESS in all ways, but slower than expected. Good for SD, Maths boot, GPS lock etc + case WII5STS_POWER: strcpy_P(wii5BufferString, (PGM_P) F("Power management issue. E.g. serial data still coming in after power pin off")); break; // , // Works, but power management may have failed (e.g. serial data still coming in when power off) + case WII5STS_TIMEOUT: strcpy_P(wii5BufferString, (PGM_P) F("Timeout no idea why...")); break; // , // Literally timed out. No failure we can actually understand. + case WII5STS_OUTOFRANGE: strcpy_P(wii5BufferString, (PGM_P) F("Out of range, e.g. volts or temperature")); break; // , // Out of range - voltage, or GPS or something out of range + case WII5STS_UNKNOWN: strcpy_P(wii5BufferString, (PGM_P) F("Unknown error")); break; // , // Bad, unknown error + default: setBufferUnknown(); break; + } + return wii5BufferString; +}; + +WII5Strings wii5Strings; diff --git a/WII5Strings.h b/WII5Strings.h new file mode 100644 index 0000000..5d464a5 --- /dev/null +++ b/WII5Strings.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Strings.h + * @brief String-formatting helpers: strDriver, strStatus, strError, etc. + */ + +#ifndef WII5Strings_h +#define WII5Strings_h + +#include +#include + +class WII5Strings { + public: + WII5Strings() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_STRINGS;} + WII5_COMMANDS parseCommand(char *in1, char *in2 = NULL, char *in3 = NULL); + WII5_DEVICES parseDevice(char *in); + WII5_DRIVERS parseDriver(char *in); + WII5_PORTS parsePort(char *in); + WII5GPS_MODES parseGPSMode(char *in); + WII5_SWITCH parseSwitch(char *in); + WII5_MODES parseMode(char *in); + + // char* strDevice(); + char* strCommand(WII5_COMMANDS c); + char* strDevice(WII5_DEVICES d); + char* strDriver(WII5_DRIVERS d); + char* strPort(WII5_PORTS p); + char* strStatus(WII5_STATUS s); + char* strError(WII5_ERRORS e); + char* strSwitch(WII5_SWITCH s); + char* strGPSMode(WII5GPS_MODES m); + char* strPinMode(uint8_t p); + char* strState(uint8_t p); + char* strMode(WII5_MODES p); + char* strPinArduinoName(uint8_t p); + char* strPinWII5Name(uint8_t p); + char* strSelfTestStatus(WII5SELFTEST_STATUS in); + char* strSelfTestStatusLong(WII5SELFTEST_STATUS in); + char* strSelfTestDevice(WII5SELFTEST_DEVICES in); + + protected: + // TODO - remove anything hard coded + // TODO - use protected memory - strncpy et al + void setBufferUnknown(); +}; + +extern WII5Strings wii5Strings; + +#endif diff --git a/WII5Weather_18B20.cpp b/WII5Weather_18B20.cpp new file mode 100644 index 0000000..5ccb845 --- /dev/null +++ b/WII5Weather_18B20.cpp @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Weather_18B20.cpp + * @brief Dallas DS18B20 temperature sensor driver (OneWire). + */ + +/* + +WII5Weather_18B20 + +TODO 2024 - Check when this is running as it blocks all other use + +TODO: + Should be Weather / Temperature then drivers. + + WII5Weather + WII5Weather::Temperature + age() + value(); + value({n}) - which sensor? + WII5Weather::Pressure + WII5Weather::Humidity + ... + +*/ + +#include +#include + +OneWire ds(WEATHER_18B20_DATA); + +void WII5Weather_18B20::begin() { + powerSetPin(POWER_WEATHER_18B20_PIN, POWER_WEATHER_18B20_ON); + powerOff(true); // Force power off at boot + stepWait = 300001; + age = WII5TIME_1WEEK * 1000; +} + +void WII5Weather_18B20::loop() { + // WII5 - removing this loop for now, temperature has to be an active decision. + /* + // If at lest been 5 minutes (maybe make this bigger later) + if (stepWait > 300000) { + if ( (minute() > 0) && ((minute() % 7) == 0) ) { + if (wii5Sparton.captureRunning()) { + console.log(LOG_DEBUG, F("Weather: Skipped - capture running. Min=%d"), minute()); + } + else { + // REMEMBER - This blocks a bit. + if (temperatureRead()) { + console.log(LOG_DEBUG, F("Weather: Temperature Read. Min=%d"), minute()); + } + else { + console.log(LOG_DEBUG, F("Weather: Failed to read temperature (unknown). Min=%d"), minute()); + } + } + stepWait = 0; + } + } + */ +} + +// TODO - This is a blocking libary. Fix it. + +bool WII5Weather_18B20::temperatureRead() { + // (check we have nothing else on) return false; + powerOn(); + + // Give power time + delay(250); + + currentTemperature = 2000; + console.log(LOG_DEBUG, F("currentTemperature=%d"), int(currentTemperature)); + + ds.reset_search(); + + // TODO Rework code - make it so it will seaerch over 10 minutes or so + // But then just queries those, no more searching, or maybe research and + // try and find more devices once every hour. + // What about lost devices? + + /* + + inside OneWire seems to be only small delayMicroseconds. + With the exception of one 400 - (half 1 millisecond) + This means that we could easily write a state machine here with a 1ms loop time + Is that fast enough for other things - then this can be non blocking, even for + large number of temperature sensors. + + */ + for(byte count = 0; count < 3; count++) { + if ( ds.search(addr)) { + ds.reset(); + ds.select(addr); + ds.write(0x44, 1); // start conversion, with parasite power on at the end + // TODO Gah... this should be background ! + + // wii5Controller.safeDelay(1000); // maybe 750ms is enough, maybe not + delay(1000); + + ds.reset(); + ds.select(addr); + ds.write(0xBE); // Read Scratchpad + + for ( byte i = 0; i < 9; i++) // we need 9 bytes + data[i] = ds.read(); + + int16_t raw = (data[1] << 8) | data[0]; + + byte cfg = (data[4] & 0x60); + if (cfg == 0x00) raw = raw & ~7; // 9 bit resolution, 93.75 ms + else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms + else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms + + currentTemperature = (float)raw / 16.0; + + // FIND and STORE them + /* + bool found = false; + for (byte i = 0; i < countTemperature; i++) { + if (ByteArrayCompare(addr, lastTemperature[i].addr, 8)) { + found = true; + lastTemperature[i].age = 0; + // Convert to Decidegrees + lastTemperature[i].celsius = currentTemperature; + } + } + */ + + /* + if (!found) { + if (countTemperature < maxTemperature) { + lastTemperature[countTemperature].age = 0; + // Convert to Decidegrees + lastTemperature[countTemperature].celsius = currentTemperature; + memcpy( lastTemperature[countTemperature].addr, addr, 8 ); // TODO length? + countTemperature++; + } + else { + console.printf(F("ERROR: Maximum temperature probes found\r\n")); + // TODO Too many errors this requires a complete reset + // TODO Make it so if we get this 10 times, we reset the + // whole list and start again. e.g. change countTemperature = 0 + // Reset for next time... + countTemperature = 0; + return true; + } + } + */ + + // Temporary hard coded to one sensor + if (count == 0) { + console.log(LOG_DEBUG, F("Temperature: %02x%02x%02x%02x%02x%02x%02x%02x count=%d currentTemperature=%d.%02d*"), + addr[0], + addr[1], + addr[2], + addr[3], + addr[4], + addr[5], + addr[6], + addr[7], + count, + int(currentTemperature), + abs(int(currentTemperature * 100) % 100) + ); + } + + } + } + // copy to rest of places + powerOff(); + console.log(LOG_DEBUG, F("currentTemperature=%d"), int(currentTemperature)); + // TODO value is int32_t, but int16_t should be heaps? + value = int(currentTemperature * 100); + age = 0; + wii5Display.atCommandSend(F("status"), F("temperature=%d"), + int(value) + ); + wii5Display.atCommandSend(F("status"), F("update")); + + + // TODO ?Move + return true; +} + +WII5Weather_18B20 wii5Weather_18B20; diff --git a/WII5Weather_18B20.h b/WII5Weather_18B20.h new file mode 100644 index 0000000..a4a68ac --- /dev/null +++ b/WII5Weather_18B20.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5Weather_18B20.h + * @brief Dallas DS18B20 temperature sensor driver (OneWire). + */ + +#ifndef WII5Weather_18B20_h +#define WII5Weather_18B20_h + +#include +#include +#include + +class WII5Weather_18B20 : public WII5Power { + public: + WII5Weather_18B20() {} + virtual WII5_CONTROLLERS controllerId() {return WII5CONTROLLER_DRIVER;} + virtual WII5_DRIVERS driverId() {return WII5DRIVER_WEATHER_18B20;} + + void begin(); + void loop(); + bool temperatureRead(); + + elapsedMillis age; + int32_t value; + protected: + byte data[12]; + byte addr[8]; + float currentTemperature; + elapsedMillis stepWait; + +}; + +extern OneWire ds; +extern WII5Weather_18B20 wii5Weather_18B20; + +#endif diff --git a/WII5_board.h b/WII5_board.h new file mode 100644 index 0000000..2351496 --- /dev/null +++ b/WII5_board.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5_board.h + * @brief Board-selection shim: forwards to the active board's pin/peripheral header. + */ + +/* + +WII5 Board File ! + +This is included in all other files. In here we can change which board +file we load. As much as possible this should be automatic, to allow switching +between boards easily. + +*/ + +// LOCAL - What is this? +#define WII5_DEVICE_TYPE "WII5WaveBuoy" +#define WII5_DEVICE_SUBTYPE "PiperC" +#define WII5_SOFTWARE_NAME "WII5_Buoy" + +// Defaults - change these at build +#ifndef WII5_SOFTWARE_VERSION +#define WII5_SOFTWARE_VERSION "WII5Buoy_5.5.12" +#endif +#ifndef WII5_SOFTWARE_COMMIT +#define WII5_SOFTWARE_COMMIT "Unknown" +#endif +#ifndef WII5_SOFTWARE_INTVER +// a * 65536 + b * 256 + c +// 5 * 65536 + 5 * 256 + 4 = 327680 + 1280 + 4 = 328964 +#define WII5_SOFTWARE_INTVER 328972 +#endif + +// 2019-05 - First board build for WII5 +// Currently only the WII5 v2 board is supported. Earlier prototypes (v1, +// Arduino Mega devboard, Moteino Mega, M5Stick C) lived in this tree but +// have been retired; see git history for those headers. + +#if defined (__WII5_V02__) +#include +#else +#error "WII5 BOARD SELECTION = No board definition (expected __WII5_V02__)" +#endif diff --git a/WII5_board_v2.h b/WII5_board_v2.h new file mode 100644 index 0000000..3c1304f --- /dev/null +++ b/WII5_board_v2.h @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file WII5_board_v2.h + * @brief Pin and peripheral configuration for the WII5 v2 hardware revision. + */ + +/* + +WII5 Board = WII5v2.0..v5.0 - This is current configuration used in V5.5 board in 2024 + + * TODO: + * Update all PINS + * Remove unused PINS + * Test + * Test V1 board is still working + +Define PINS and pin Modes, Serial ports etc + +Board Versions + + v1 - Hardware / Layout, just increment each time + v1.1 - Second number is changes locall - e.g. v1.1 might use a Software Serial for Matrix input + mega_dev - Development board using a Sparkfun 3.3V 8Mhz Arduino Mega + +File Structure + +* Names for all ports etc used on this board. + +NOTE: Do we need WII5 prefix?, e.g. WII5_PIN_D0_NAME + + +Initial Build test using 595eb5a + Sketch uses 115740 bytes (44%) of program storage space. Maximum is 258048 bytes. + Global variables use 6667 bytes (81%) of dynamic memory, leaving 1525 bytes for local variables. Maximum is 8192 bytes. + + Sketch uses 115740 bytes (44%) of program storage space. Maximum is 258048 bytes. + Global variables use 6667 bytes (81%) of dynamic memory, leaving 1525 bytes for local variables. Maximum is 8192 bytes. + +Improved strings + Sketch uses 115738 bytes (44%) of program storage space. Maximum is 258048 bytes. + Global variables use 6661 bytes (81%) of dynamic memory, leaving 1531 bytes for local variables. Maximum is 8192 bytes. + + Sketch uses 115914 bytes (44%) of program storage space. Maximum is 258048 bytes. + Global variables use 6647 bytes (81%) of dynamic memory, leaving 1545 bytes for local variables. Maximum is 8192 bytes. + +Free Memory + 213/8192 really of 213 of 1545 - so we still have 1200 bytes dynamic created ! Find and destory + + +SUB 1 + left + right + +SUB 3 + left + 2 - D3 + 5 - D27 + right + 3 - D9 + 4 - D12 + 5 - D62 + +Shared 5 Volts: + * Maths - now has its own supply, not shared + * Needs 5 Volts: + * Sparton + * Iridium + * Beeper + +*/ + +// Enable extended EEPROM code +#define WII5_EEPROMex + +#define WII5_BOARD_ID 102 +#define WII5_BOARD_NAME "WII5_V5.0" +#define WII5_BOARD_CPU "AVR25560" +#define WII5_BOARD_MEMORY 8192 +#define WII5_BOARD_EEPROM 4096 +#ifdef F_CPU +#define WII5_BOARD_CLOCK F_CPU +#else +#define WII5_BOARD_CLOCK 8000000L +#endif + +// ---------------------------------------------------------------------- +// Sh3d stuff - how to pass in. +// auto_node_id = 'manual' + +// ---------------------------------------------------------------------- +// Primary Hardware Components +// ---------------------------------------------------------------------- +// Small packet radio +// #define WII5_RADIO +// #define WII5_RADIO_LORA +// #define WII5_RADIO_69 + +// RTC Configuration +// #define WII5_RTC + +// TODO 2024 review +// (Removed 2024, board 5.5.5) #define WDT_RESET 59 +// #define WII5_WDT_INTERNAL +#define WII5_WDT_EXTERNAL +// WDT External +#define WDT_RESET 59 +#define WII5_LOWPOWERSLEEP + +// Test and Self Test etc (TODO 2024 - consider to save memory etc) +#define WII5_ENABLE_MANUALTEST +#define WII5_ENABLE_SELFTEST + +// GPS Configuration +#define WII5_GPS +// #define WII5_GPS_COMPASS // Compass specific version - some differences + +// COMMS - aka GSM Modem or Iridium Satellite +#define WII5_COMMS +#define WII5_COMMS_IRIDIUM // Iridium version of COMMS Driver +// #define WII5_COMMS_GSMSMS // GSM SMS version + +// IMU +#define WII5_IMU +#define WII5_IMU_SPARTON // SPARTON aka Serial IMU +// #define WII5_IMU_MPU9250 // 9250 aka I2C 9250 + +// Weather - Temperature, pressure, humidity and much more +#define WII5_WEATHER_18B20 // Multiple Weather, incluing standard 18B20 +// #define WII5_WEATHER_ANEMOMETER + +#define WII5_STORAGE_SDBLOCK + +#define WII5_BUTTON_HOLD_TIME 10000 // milliseconds + +// ---------------------------------------------------------------------- +// BUFFER SIZES +// (see Iridium instead) #define WII5_BUFFER_CONSOLE_BINARY 20 +#define WII5_BUFFER_CONSOLE_PRINT 220 +#define WII5_BUFFER_CONSOLE_CMD 50 +#define WII5_BUFFER_RADIO 150 +#define WII5_BUFFER_STRING 50 +#ifdef WII5_COMMS_IRIDIUM +// NOTE: Max receive = 270, Max send = 340 +#define WII5_BUFFER_IRIDIUM 80 +#define WII5_IRIDIUM_BIN_MAX 340 +#endif +#ifdef WII5_GPS +#define WII5_BUFFER_GPS 80 +#endif +#define WII5_FIELD_MAX 15 +#define WII5_TIMERLAPS 5 + +// ---------------------------------------------------------------------- +// Convert Float to Integers etc +#define GPS_POS_MULT 100000 +#define GPS_ALT_MULT 100 +// See BATTERY + +// ---------------------------------------------------------------------- +// SERIAL +#define SerialConsole Serial +#ifdef F_CPU_11 +// #define SerialConsole_Baud 115200 +#define SerialConsole_Baud 230400 +#else +#define SerialConsole_Baud 57600 +#endif +#define SerialConsole_RX 0 +#define SerialConsole_TX 1 + +#define SerialGPS Serial2 +#define SerialGPS_Baud 9600 + +// Comms = Iridium here +#define SerialComms Serial3 +#define SerialComms_Baud 19200 + +// IMU = Sparton +#define SerialIMU Serial1 +#ifdef F_CPU_11 +#define SerialIMU_Baud 115200 +#else +#define SerialIMU_Baud 57600 +#endif + +// NOTE - Test these and all pins +// Software Serial Devices +// Not all pins on the Mega and Mega 2560 support change interrupts, +// so only the following can be used for RX: +// 10, 11, 12, 13, 14, 15, 50, 51, 52, 53, +// A8 (62), A9 (63), A10 (64), A11 (65), A12 (66), A13 (67), A14 (68), A15 (69). + +// STATUS SERIAL - a low level interface +// #define WII5_STATUS_SERIAL +// #define WII5_STATUS_SERIAL_RX 11 +// #define WII5_STATUS_SERIAL_TX 8 +// #define WII5_STATUS_SERIAL_BAUD 9600 + +// DEBUG SERIAL Port - to SUB BOARD 1 +// #define WII5_DEBUG_SERIAL +// #define WII5_DEBUG_SERIAL_CONSOLE // Add to Console, and mark main as OUTPUT +// #define WII5_DEBUG_SERIAL_RX SoftwareSerial5_RX +// #define WII5_DEBUG_SERIAL_TX SoftwareSerial5_TX +// #define WII5_DEBUG_SERIAL_RX 37 +// #define WII5_DEBUG_SERIAL_TX 36 +// #define WII5_DEBUG_SERIAL_BAUD 9600 + +// MOVE Maths to SoftwareSerial 6 +// #define WII5_MATHS_SERIAL +/* +Testing + 35, 34 - no + 34, 35 - Yes (I could have it backwards on the begin?) + D64, D65 +*/ +// #define WII5_MATHS_SERIAL_RX 64 +// #define WII5_MATHS_SERIAL_TX 65 +// #define WII5_MATHS_SERIAL_BAUD 57600 + +// ---------------------------------------------------------------------- +// Buttons and LEDjson2csv'); +#define LED_1 13 +#define LED_1_ON HIGH +#define LED_2 41 +#define LED_2_ON HIGH +#define LED_3 0 +#define LED_3_ON LOW +#define LED_4 0 +#define LED_4_ON LOW +#define BUTTON_1 67 +#define BUTTON_1_ON LOW +#define BUTTON_2 45 +#define BUTTON_2_ON LOW +#define BUTTON_3 0 // Connects to GPIO 18 on the Pi +#define BUTTON_3_ON LOW +#define BUTTON_4 0 +#define BUTTON_4_ON LOW + +// ---------------------------------------------------------------------- +// Display / Debug - these pins can be used with LED & Buttons +// - OR as a buffer / output for single wire LED grids +#define LCD_1 69 +#define LCD_2 68 +#define LCD_3 67 +#define LCD_4 66 +#define LCD_5 65 +#define LCD_6 64 + +// ---------------------------------------------------------------------- +// Power Management +#define POWER_MATHS_PIN 29 +#define POWER_MATHS_ON HIGH +#define POWER_RADIO_PIN 42 +#define POWER_RADIO_ON HIGH +#define POWER_COMMS_PIN 30 +#define POWER_COMMS_ON HIGH +#define POWER_GPS_PIN 56 +#define POWER_GPS_ON HIGH +#define POWER_STROBE1_PIN 33 +#define POWER_STROBE1_ON HIGH +#define POWER_STROBE2_PIN 32 +#define POWER_STROBE2_ON HIGH +#define POWER_IMU_SPARTON_PIN 25 +#define POWER_IMU_SPARTON_ON HIGH +#define POWER_WEATHER_18B20_PIN 6 +#define POWER_WEATHER_18B20_ON HIGH +#define POWER_WEATHER_PIN 39 // NC +#define POWER_WEATHER_ON HIGH +#define POWER_STORAGE_1_PIN 54 +#define POWER_STORAGE_1_ON HIGH +#define POWER_STORAGE_2_PIN 0 +#define POWER_STORAGE_2_ON HIGH +#define POWER_BUZZER_PIN 41 +#define POWER_BUZZER_ON HIGH + +// Shared 5 volt piY_ +#define SHARED_5VOLT_PIN 28 +#define SHARED_5VOLT_ON HIGH + +// ---------------------------------------------------------------------- +// Battery Management +#define BATTERY_1_VOLTS_PIN 35 +#define BATTERY_1_VOLTS_ON HIGH +#define BATTERY_1_VOLTS_ANALOG A14 +//#define BATTERY_2_VOLTS_PIN 36 +//#define BATTERY_2_VOLTS_ON HIGH +//#define BATTERY_2_VOLTS_ANALOG A15 +// 3.027 / 3.3 +// #define BATTERY_1_VOLTS_MULT 1320 +#define BATTERY_1_VOLTS_MULT 1300 +//#define BATTERY_2_VOLTS_MULT 1300 +/* + 1024 * 3027 / 3300 + + 980 +*/ +// What do we need to do to turn it into DeciVolts (100th of a volt) as integer +// TODO Hard coded vs Config calibration of Voltage +/* +#define BATTERY_2_PIN 58 +#define BATTERY_2_ON HIGH +#define BATTERY_2_VOLTS_PIN 56 +#define BATTERY_2_VOLTS_ON HIGH +#define BATTERY_2_VOLTS_ANALOG A0 +*/ +// TODO Hard coded vs Config calibration of Voltage + +// ---------------------------------------------------------------------- +// Other analog sensors (e.g. Water) +// ---------------------------------------------------------------------- +// SPI Storage +// - SPIMEMORY +// - SD - Single SD Card +// - SD2 - Two SD Cards - switched between boards +// - FRAM +#define STORAGE_PATH_TOP "data" +#define STORAGE_PATH_EXT ".txt" +#define STORAGE_PATH_META "meta" +#define STORAGE_PATH_CONSOLE "console" +#define STORAGE_PATH_DATA "data" +#define STORAGE_TYPE_SD2 +#define STORAGE_SPI SPI +#define STORAGE_CS 53 +#define STORAGE_SCK 52 +#define STORAGE_MOSI 51 +#define STORAGE_MISO 50 +#define STORAGE_SD1_MATHS_PIN 58 +#define STORAGE_SD1_MATHS_ON LOW +#define STORAGE_SD2_MATHS_PIN 57 +#define STORAGE_SD2_MATHS_ON LOW + +// ---------------------------------------------------------------------- +// IMU - Serial (Sparton) +// See also POWER_IMU_SERIAL* +#define SPARTON_RESET 26 +// #define SPARTON_INT 30 +#define SPARTON_HZ 8 // 8 or 64 only ! +#define SPARTON_MS (1000 / SPARTON_HZ) // Milliseconds, approx + +// ---------------------------------------------------------------------- +// IMU - 9250 +// See also POWER_IMU_9250* +// + I2C + +// ---------------------------------------------------------------------- +// Weather and Other Special Hardware +#define WEATHER_18B20_DATA 5 + +// ---------------------------------------------------------------------- +// Radio (aka LoRa - not used in WII5 2024) +// See also POWER_RADIO_* +#define RADIO_TYPE_RF95 +#define RADIO_INT 2 +#define RADIO_SPI SPI +#define RADIO_CS 43 +#define RADIO_SCK 52 +#define RADIO_MOSI 51 +#define RADIO_MISO 50 + +// ---------------------------------------------------------------------- +// Communications / Iridium et al +// See also POWER_COMMS* + Serial +#define COMMS_NETWORK 31 + +// ---------------------------------------------------------------------- +// Maths +#define MATHS_RETURNLINE 4 // Connects to GPIO 18 on the Pi, PIN 12 +#define MATHS_RETURNHOLD 3600 // Return HOLD for max 1 hour +#define MATHS_MAXHOLD 43200 // aka 12 hours + +// ---------------------------------------------------------------------- +// RTC +// See also POWER_RTC +#define RTC_ALARM 24 + +#define META_NC 20000 +#define META_VOLTS_12 10012 +#define META_VOLTS_5 10005 +#define META_VOLTS_3 10003 +#define META_VOLTS_SWITCHED 10001 +#define META_GROUND 10000 +#define META_I2C_SDA 20001 +#define META_I2C_SCL 20002 +#define META_SPI_MOSI 20003 +#define META_SPI_MISO 20004 +#define META_SPI_SCK 20005 + +// SubBoard 1 +#define SUBBOARD_1_LEFT_1 META_VOLTS_12 +#define SUBBOARD_1_LEFT_2 META_SPI_SCK +#define SUBBOARD_1_LEFT_3 META_SPI_MOSI +#define SUBBOARD_1_LEFT_4 META_SPI_MISO +#define SUBBOARD_1_LEFT_5 33 +#define SUBBOARD_1_LEFT_6 META_GROUND +#define SUBBOARD_1_RIGHT_1 META_VOLTS_SWITCHED // controlled by 32 +#define SUBBOARD_1_RIGHT_2 META_VOLTS_3 // Always on +#define SUBBOARD_1_RIGHT_3 7 // TX4 +#define SUBBOARD_1_RIGHT_4 10 // RX4 +#define SUBBOARD_1_RIGHT_5 66 +#define SUBBOARD_1_RIGHT_6 META_GROUND + +// SubBoard 2 +#define SUBBOARD_2_LEFT_1 META_VOLTS_12 +#define SUBBOARD_2_LEFT_2 META_NC +#define SUBBOARD_2_LEFT_3 META_NC +#define SUBBOARD_2_LEFT_4 META_NC +#define SUBBOARD_2_LEFT_5 23 +#define SUBBOARD_2_LEFT_6 META_GROUND +#define SUBBOARD_2_RIGHT_1 META_VOLTS_SWITCHED // controlled by 32 +#define SUBBOARD_2_RIGHT_2 META_VOLTS_3 // Always on +#define SUBBOARD_2_RIGHT_3 8 // TX5 +#define SUBBOARD_2_RIGHT_4 11 // RX5 +#define SUBBOARD_2_RIGHT_5 65 +#define SUBBOARD_2_RIGHT_6 META_GROUND + +// SubBoard 3 +#define SUBBOARD_3_LEFT_1 META_VOLTS_12 +#define SUBBOARD_3_LEFT_2 3 +#define SUBBOARD_3_LEFT_3 META_I2C_SDA +#define SUBBOARD_3_LEFT_4 META_I2C_SCL +#define SUBBOARD_3_LEFT_5 27 +#define SUBBOARD_3_LEFT_6 META_GROUND +#define SUBBOARD_3_RIGHT_1 META_VOLTS_3 // Controlled by D61 (POWER_SUBBOARD_3_PIN) +#define SUBBOARD_3_RIGHT_2 META_VOLTS_3 // Always on +#define SUBBOARD_3_RIGHT_3 9 +#define SUBBOARD_3_RIGHT_4 12 +#define SUBBOARD_3_RIGHT_5 62 +#define SUBBOARD_3_RIGHT_6 META_GROUND + +// Timing testing +// #define TIMING_SPARTON_RUN SUBBOARD_3_LEFT_2 +// #define TIMING_SPARTON_CHAR SUBBOARD_3_RIGHT_3 +// #define TIMING_SPARTON_RECORD SUBBOARD_3_RIGHT_4 +// #define TIMING_SD_WRITE SUBBOARD_3_LEFT_5 diff --git a/app/wii5_buoy/wii5_buoy.ino b/app/wii5_buoy/wii5_buoy.ino new file mode 100644 index 0000000..9969f95 --- /dev/null +++ b/app/wii5_buoy/wii5_buoy.ino @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file wii5_buoy.ino + * @brief Arduino sketch entry point: setup() and loop() for the WII5 buoy firmware. + */ + +#include "WII5.h" +#include "WII5Controller.h" + +void setup () { + // Put everthing we can into safe PIN mode (including driving high/low) + wii5Setup.beginSafe(); + + // Move to Setup - e.g. beginSafe(); (including safe pins below with proper names and #ifdef) + SerialConsole.begin(SerialConsole_Baud); + + #ifdef WAITBOOT_HALFSECOND + SerialConsole.println(F("# WII5: Waiting for 1/2 seconds in boot mode, to allow ICSP")); + SerialConsole.flush(); + delay(500); + #endif + + // Blocking call - 3 seconds + wii5Setup.bootbeep(); + + // Do first always... + wii5Setup.begin(); + + // Normal WII startup + wii5Controller.begin(); +} + +void loop() { + wii5Controller.loop(); +} diff --git a/doc/EXTERNAL_REFERENCES.md b/doc/EXTERNAL_REFERENCES.md new file mode 100644 index 0000000..12f68df --- /dev/null +++ b/doc/EXTERNAL_REFERENCES.md @@ -0,0 +1,45 @@ +# External References + +Vendor documentation that the WII5 buoy firmware was developed against. +These documents are not redistributed in this repository (their licensing is +unclear); refer to the vendor sites for current copies. + +## Iridium 9602 / 9603 SBD modems + +Used for satellite telemetry. Key reference is the **Iridium 9602 / 9603 +SBD Transceiver Developer's Guide** (AT-command set, SBD message lifecycle, +binary message framing for `+SBDWB` / `+SBDI`). + +- Iridium developer portal: https://www.iridium.com/services/iridium-sbd/ +- Search the Iridium partner network for the current "9602/9603 Developer + Guide" PDF. + +Relevant code: `WII5Iridium.cpp`, `WII5SerialManager.cpp` (`+SBDWB`, +`+SBDI`, `+CSQ` parsing), `WII5BinData.*` (340-byte message format). + +## Sparton AHRS-M1 / AHRS-M2 + +Used as the primary IMU / wave-motion sensor. Key references: + +- AHRS-M1 / AHRS-M2 Software Interface Control Document +- AHRS-M1 / AHRS-M2 Hardware ICD +- NorthTek Programming Manual (Sparton's Forth-like configuration language; + the `programLine()` helpers in `WII5SerialManager.cpp` emit NorthTek + commands). + +- Sparton (now Bel Power Solutions / part of Bel Fuse) documentation: + https://www.belfuse.com/ + +Relevant code: `WII5Sparton.cpp`, `WII5Setup.cpp` (compass/AHRS init). + +## Other components + +- **Dallas DS18B20** — temperature sensor: + https://www.analog.com/en/products/ds18b20.html + (Used in `WII5Weather_18B20.cpp` via the OneWire / DallasTemperature + Arduino libraries.) +- **TinyGPS++** — NMEA parser library: + https://github.com/mikalhart/TinyGPSPlus +- **u-blox NEO-6M / NEO-7M** — GPS module datasheets (NMEA reference): + https://www.u-blox.com/ + diff --git a/doc/MODES.txt b/doc/MODES.txt new file mode 100644 index 0000000..27534a7 --- /dev/null +++ b/doc/MODES.txt @@ -0,0 +1,218 @@ +Final Modes for WII5 Tasmania Release + + * Incidental ones - used to transition the modes in the state machine + * BOOT + * START + + * Demo / Testing modes - these must not be used in prod, but will have to have just timeouts + (these modes do not have Iridium and are unreliable, they must exit) + * DEMO + * MANUALTEST + * SELFTEST + + * Fully functional modes - each of these has full control, metadata, and Iridium + * SLEEP - as deep as possible, basically off. But will come on every 12 hours + * LOWBATTERY - Automatically selected, no matter what the mode + * POSITION - Send the position over and over + * CAPTURE - Full controlled and capture mode + + + + +Original Notes: + +Top Level Modes + + Most of the buoy uses a lot of memory. But can be rebooted to other states. + And most of the memory is using MALLOC + + + +Boot up stages + + WII5MODE_BOOT - Boot up only once + . Loading configuration from EEPROM etc only needs to be done once + . Long processes, like reading the blocks from the SD Cards + SET - NA + AUTO - Boot up + WII5MODE_START - Probably jump here each time waking from sleep etc + . GPS, RTC, and other updates that need to be regularly checked can be done here + SET - NA + AUTO - Start ? + WII5MODE_OFF, - OFF - Disable everything and go into deep sleep, don't wake, back to sleep + . Really just a mode that allows us to stay here - (e.g. switch is off) + SET - NA + AUTO - Switch position + WII5MODE_SLEEP, - Same as OFF, but will wake with button, alarm, and every 12 hours + . Sleep - wake can be from button, network, time is up, alarm hit (RTC), every 12 hours and more + . Allowing more complicated decisions, e.g. long press to move to MANUAL TEST mode or Force Capture test + -> REPORT - If 12 hoursly updates + -> TemporaryMode - If still not expired + -> mode would be - POSITION or CAPTURE or SLEEP + -> DefaultMode - Otherwise + -> mode would be - POSITION or CAPTURE or SLEEP + SET - expires + val1 = ReportPeriod - How many minutes between reports. Default is 720 (12 hours) + NOTE: No way to go back to temporary mode right now. Consider that? + AUTO - NA + WII5MODE_LOWBATTERY, - Automatically set mode based on Battery Management + . A command mode change can force to MANUALTEST, or others but only for a maximum time period + SET - NA + AUTO - From Battery Module - not saved, recalculated all the time + WII5MODE_POSITION, - Position mode - light weight, no Maths, regular updates + SET - expires + val1 = ReportPeriod - How many minutes between sending position (default 15 minutes) + val2 = StrobeModes - e.g. Turn on strobe never, 5 minutes per hour, 1 minute at every send, etc + NOTE: No way to go back to temporary mode right now. Consider that? + AUTO - NA + WII5MODE_REPORT, - Report - this is like position, but a single report, and back to sleep + MINI Self Test or Requested Self Test + SET - NA + AUTO - Done from sleep, returns back to SLEEP - which will test etc + WII5MODE_CAPTURE, - Normal full capture mode. + SET - expires + val1 - RunPeriod - Minutes between - 15 (every 15 mins), 60 (once an hour), 120 (every 2 hours) + val2 - Records - How many records do we want (needs to be what is needed by maths) + val3 - Predefined sets of data to send back for Iridium - this is going to be a + bit mask. So we can have 32 sets of data we can add. Some might be contray to + each other. e.g. 1 might be low resolution temperature, but 2 high resolution, + so no point adding both. + New simpler Binary module - since now we only need to know this 32 bit number, + to know what is in the rest of the packet - and new types of data get added to the + end..... maybe. + AUTO - NA + WII5MODE_MANUALTEST - Manual test - each power button, module etc, can be toggled. + . Leaving this mode - we should consider a software reboot as the modes and hardware will be in an unknown state + SET - (restrict to set from Serial and not Iridium) + AUTO - NA + WII5MODE_SELFTEST - Self Test mode allows running through all the self tests automatically and rebooting + SET - (restrict to set from Serial and not Iridium) + val1 - ? + AUTO - NA + +Changing Modes: + + * Code: + - wii5Controler.setMode(WII5MODE_MANUALTEST); // Forever - write to config + - wii5Controler.setTemporaryMode(WII5MODE_MANUALTEST); // Until reboot (ie, not to config) + - wii5Controler.setTemporaryMode(WII5MODE_MANUALTEST, 300); // 5 minutes + * Iridium + - mode:ModeName:Seconds(0 forever):option1,option2,option3 + (NOTE: Could we support all serial commands here?) + (NOTE: Could we make it a specific one line reply is always made) + @WII5!,mode,... e.g. it is WII5! for ACK and then the first string, e.g. mode, rest is optional + @WII5,hello,From,Here would be therefore @WII5!,hello,My,Reply + * Serial + - @WII5,mode,ModeName,Seconds,option1,option2,option3 + * Modules Logic + - WII5Battery - force to LOWBATTERY mode (never saved), switch back to Capture when it can (or what ever default) + +Boot Mode: (how do we know where to start) + + (first one to match) + * Check the OFF Switch - if it is active, WII5MODE_OFF + . We don't actually need to do this one, if OFF switch active in loop, it will set it no matter what the mode. + * Check Battery + . Special option in config to ignore the battery for a period of time. + . If battery too low, switch to low power mode (and not ignored) + * Sleeping + . Read the sleeping configuration from memory or EEPROM + . Check the date / time for it has not yet expired (check time is synced) + . Back to sleep + * TemporaryMode + . Read Temporary mode - from memory or EEPROM + . Check the date / time for it has not yet expired (check time is synced) + . Use this mode + * DefaultMode + . Back to default mode + +Configuration Options Per mode: + + * WII5MODE_BOOT + . NA + * WII5MODE_START + . NA + * WII5MODE_OFF + . NA + * WII5MODE_SLEEP + . When to wake up (time in future, or number of seconds from now) + * WII5MODE_WAKE + . NA + * WII5MODE_REPORT + . NA + * WII5MODE_LOWBATTERY + . NA + * WII5MODE_POSITION + . Frequency (how many minutes apart) + . Strobe Rules - instead of just on/off - strobe can have rules in here + * WII5MODE_CAPTURE + . Number of seconds/minutes/records to caputre + . Process flags - Pass in some optional extra flags on processing and what you want returned via Iridium + . When to start (minute in hour or some way of multiple per hour, half hour, quarter hour) + . Hours between too + . Examples: + . Start 5 minutes past the hour, ever 3 hours (ie. 00, 03, 06, 09 ...) + . Run every 15 minutes for one hour, then sleep for an hour (00:00, 00:15, 00:30, 00:45, sleep... 02:00, 02:15, ...) + . etc. + * WII5MODE_MANUALTEST + . NA + * WII5MODE_SELFTEST + . NA - might have some subsets in the future + +Main Modes: + + * OFF + * A rare mode where the device is completely off. + * Exit = ? Physical butto? + * Time? + + * Sleep + * Deep down sleep, don't take up much power + + * WakeUp + * Check why we got woken up? + * User input button + * Back to old mode after sleep (period hit) + * Iridium input + * Period wake up - send status, che + * Exit - always does end of this, back to sleep + + * LowPower + * Special mode, keep everything really short + * Send Data + * Sleep + + * Position + + * Capture + + * Capture Start will define what is checked, e.g. It might not allow iridium + * In that case, Iridium NMEA commands should return correct error to wait + * NOTE: This is not capture itself + + * ManualTest + * Stay in this mode (max tme, maybe 4 hours) to test + * Allow manual start/stop of processes + + +Exclusive vs Together: + + * Classic example... + * Start capture + * In the background the Maths processor is still dealing with the old data. + * And it sends Iridium commands + * And capture needs metadata such as GPS. + * So Sparton serial in, SD writes, Iridium sending data, GPS on. All not kind of related. + + +Sequence: + + Boot + Load up configuration from EEPROM, start devices etc + SelfTest + A built in self test that takes under 1 minutes. It will just set a Simple + error we can use to update the LED + Run + Sleep + +Run Modes diff --git a/doc/PROCESSED.txt b/doc/PROCESSED.txt new file mode 100644 index 0000000..cdb452c --- /dev/null +++ b/doc/PROCESSED.txt @@ -0,0 +1,448 @@ +New reordered array + +process1.part1float + 0..3 hz_max + 4..7 hcm_max + 8..11 htm_max + 12..15 tz_mean + 16 tp + 17 hmod + 18..72 psd + +process2.parse2float + 0..7 moment + 8..16 ... + +// 4 +for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hz_max[i]); +// 4 +for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hcm_max[i]); +// 4 +for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->htm_max[i]); +// 4 16 +for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->tz_mean[i]); +// 1 +fprintf(ido_o,"%LF\n",return_data->tp); +// 1 18 +fprintf(ido_o,"%LF\n",return_data->hmod); +// n_psd = 55 73 +for (i=0;ipsd[i]); + + +// n_mom = 7 +for (i=0;imoment[i]); + +// 8 + fprintf(ido_o,"%d\n",return_data->qflg_open_water); + fprintf(ido_o,"%d\n",return_data->qflg_kist); + fprintf(ido_o,"%d\n",return_data->qflg_imu); + fprintf(ido_o,"%d\n",return_data->qflg_pkist); + fprintf(ido_o,"%d\n",return_data->qflg_accel); + fprintf(ido_o,"%d\n",return_data->qflg_gyro); + fprintf(ido_o,"%d\n",return_data->qflg_magno); + fprintf(ido_o,"%d\n",return_data->qflg_head); + + + // 54 62 + + +155 lines + On average these are all floats - 4 Bytes + 620 bytes - which does not fit into one block + +OR: + 101 * 4 + 54 * 2 + = 512 - exactly the block size! + + psd 55 = 220 + +Normal output from WII3/4 + + * All 4 byte float unless specified here + * direction - 54 * Int (2 byte) - total 108 + * psd - 55 * float (4 byte) - total 220 + + +hz_max: [1, 4], // Maximum wave height (trough - crest) in a 10 min segment + Double + 4 * 4 + = 16 +hcm_max: [5, 8], // Maximum height of a crest above mean water level in a 10 min segment + Double + 4 * 4 + = 16 +htm_max: [9, 12], // Maximum depth of a trough below mean water level in a 10 min segment + Double + 4 * 4 + = 16 +tz_max: [13, 16], // Mean wave period in a 10 min segment + Double + 4 * 4 + = 16 +tp: 17, // Peak period from the full (non averaged) spectra + Double + 4 + = 4 +hmo: 18, // 4*standard deviation of elevation + Double + 4 + = 4 +psd: [19, 73], // Using 1/f_avg = frequency + Double + 55 * 4 + = 220 +moments: [74, 80], // Spectral moments + Double + 7 * 4 + = 28 +theta: 81, // Mean wave direction + Double + 4 + = 4 +dp: 82, // Peak wave direction + Double + 4 + = 4 +s: 83, // Wave spread + Double + 4 + = 4 +r: 84, // Ratio term to evaluate quality of wave direction approximation + Double + 4 + = 4 +hs_dir:85, // Hs calculated using direction analysis + Double + 4 + = 4 +a: 86, // Noise slope + Double + 4 + = 4 +b: 87, // Noise intercept + Double + 4 + = 4 +nstd: 88, // Noise standard deviation + Double + 4 + = 4 +f2: 89, // Frequency cut-off + Double + 4 + = 4 +qf_mvar: 90, // Quality flag: Mean vertical acceleration removed + Double + 4 + = 4 +qf_kist: 91, // Quality flat: Kistler passed (0), Kistler failed (1) + Int + 2 + = 2 +qf_imu: 92, // Quality flat: IMU passed (0), IMU failed (1) + Int + 2 + = 2 +qf_p_kist: 93, // Quality flag: Percentage of flagged Kistler samples + Int + 2 + = 2 +qf_p_accel: 94, // Quality flag: Percentage of flagged acceleration samples (averaged across 3 axis) + Int + 2 + = 2 +qf_p_gyro: 95, // Quality flag: Percentage of flagged gyro samples (averaged across 3 axis) + Int + 2 + = 2 +qf_p_mag: 96, // Quality flag: Percentage of flagged magno samples (averaged across 3 axis) + Int + 2 + = 2 +qf_head: 97, // Quality flag: GPS heading passed (0), GPS heading failed (1) + Int + 2 + = 2 +power_diff: 98, // Difference between power in the time domain and frequency domain + Double + 4 + = 4 +yaw_std: 99, // Yaw standard deviation + Double + 4 + = 4 +open_water: 100, // Open Water flag + Int + 2 + = 2 +direction: [101, 154], // direction data + Int + 54 * 2 + = 108 + +492 bytes + + + +11.787015 +9.582090 +12.528253 +14.153369 +7.735189 +5.657926 +4.293090 +5.485378 +-7.106659 +-5.864575 +-8.348605 +-8.667991 +29.714286 +28.750000 +28.055556 +30.400000 +32.000000 +10.224887 +11.823206 +46.393283 +50.172329 +45.776413 +96.298192 +69.859577 +37.341004 +29.351810 +17.013856 +24.406505 +10.531730 +6.074609 +4.671587 +3.752939 +6.758200 +5.808995 +5.183176 +4.280694 +1.921353 +2.080787 +1.444816 +1.249582 +1.329185 +0.709623 +1.501197 +1.624132 +0.683771 +1.638818 +0.252953 +0.567005 +0.321616 +0.659857 +0.243726 +0.682648 +0.268455 +0.297780 +0.281510 +0.218103 +0.302124 +0.219463 +0.273396 +0.091317 +0.122532 +0.145382 +0.038717 +0.057687 +0.044266 +0.035344 +0.020552 +0.013517 +0.007770 +0.006380 +0.004311 +0.002205 +0.001864 +5740.408809 +186.498311 +6.548624 +0.253406 +0.011854 +0.000831 +0.000111 +55.514691 +14.862172 +75.469394 +32.156745 +3.584171 +-4.368659 +-10.127936 +410.341584 +0.031250 +10.716096 +1 +0 +99 +1 +99 +0 +2 +4.583661 +11165.160686 +1 +5.154564 +214 +149 +245 +815 +687 +659 +204 +455 +253 +786 +371 +692 +550 +430 +490 +358 +201 +440 +431 +388 +644 +676 +389 +521 +855 +550 +728 +583 +863 +635 +818 +863 +795 +567 +674 +482 +884 +429 +787 +299 +619 +787 +328 +742 +572 +712 +798 +130 +650 +780 +336 +680 +547 +434 + + +for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hz_max[i]); + 4 Float +for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->hcm_max[i]); + 4 Float +for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->htm_max[i]); + 4 Float +for (i=0;i<2*k;i++) fprintf(ido_o,"%LF\n",return_data->tz_mean[i]); + 4 Float +fprintf(ido_o,"%LF\n",return_data->tp); + 1 Float +fprintf(ido_o,"%LF\n",return_data->hmod); + 1 Float +for (i=0;ipsd[i]); + 55 Float +for (i=0;imoment[i]); + 7 Float +fprintf(ido_o,"%LF\n",return_data->direction*180.0/pi); + 1 Float +fprintf(ido_o,"%LF\n",return_data->peak_direction*180.0/pi); + 1 Float +fprintf(ido_o,"%LF\n",return_data->spread*180.0/pi); + 1 Float +fprintf(ido_o,"%LF\n",return_data->ratio); + 1 Float +fprintf(ido_o,"%LF\n",return_data->hs_dir); + 1 Float +fprintf(ido_o,"%LF\n",return_data->a); + 1 Float +fprintf(ido_o,"%LF\n",return_data->b); + 1 Float +fprintf(ido_o,"%LF\n",return_data->nstd); + 1 Float +fprintf(ido_o,"%LF\n",return_data->f2); + 1 Float +fprintf(ido_o,"%LF\n",return_data->qflg_mean_removed_imu); + 1 Float +fprintf(ido_o,"%d\n",return_data->qflg_kist); + 1 Int +fprintf(ido_o,"%d\n",return_data->qflg_imu); + 1 Int +fprintf(ido_o,"%d\n",return_data->qflg_pkist); + 1 Int +fprintf(ido_o,"%d\n",return_data->qflg_accel); + 1 Int +fprintf(ido_o,"%d\n",return_data->qflg_gyro); + 1 Int +fprintf(ido_o,"%d\n",return_data->qflg_magno); + 1 Int +fprintf(ido_o,"%d\n",return_data->qflg_head); + 1 Int +fprintf(ido_o,"%LF\n",return_data->qflg_tot_pow_imu); + 1 Float +fprintf(ido_o,"%LF\n",return_data->std_yaw*180.0/pi); + 1 Float +fprintf(ido_o,"%d\n",return_data->qflg_open_water); + 1 Int +fprintf(ido_o,"%LF\n",return_data->hz_mean[0]); + 1 Float +direction + 54 Int + + 4 Float + 4 Float + 4 Float + 4 Float + 1 Float + 1 Float + 55 Float + + 73 + = 292 Bytes + + 7 Float + 1 Float + 1 Float + 1 Float + 1 Float + 1 Float + 1 Float + 1 Float + 1 Float + 1 Float + 1 Float + 17 + = 68 bytes + 1 Int + 1 Int + 1 Int + 1 Int + 1 Int + 1 Int + 1 Int + 7 + = 14 bytes + 1 Float + 1 Float + = 8 bytes + 1 Int + = 2 bytes + 1 Float + = 4 bytres + 54 Int + = 108 + = 204 total diff --git a/doc/SPARTON.txt b/doc/SPARTON.txt new file mode 100644 index 0000000..7ca3d52 --- /dev/null +++ b/doc/SPARTON.txt @@ -0,0 +1,39 @@ +Sparton Programming + +Standard: + + 'chan0TriggerDivisor 0 set drop', + 'accelrange 2 set drop', + 'chan0Enables array[ 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]array set drop', + 'chan0Format 3 set drop', + 'chan0EnableBit cputime dvid@ set drop', + 'chan0EnableBit magp dvid@ set drop', + 'chan0EnableBit accelp dvid@ set drop', + 'chan0EnableBit roll dvid@ set drop', + 'chan0EnableBit pitch dvid@ set drop', + 'chan0EnableBit yaw dvid@ set drop', + 'chan0EnableBit gyrop dvid@ set drop', + 'chan0Trigger 6 set drop', + 'chanTimerX100us 156 set drop', + // Trigger on delay? + 'chan0TriggerDivisor 1 set drop', + +Change from 64Hz to 8Hz + + + + +Problem with X and Y Data + + ‘centripetalCompensation 0 set drop’ + ‘magAiding 1 set drop’ + +Log some sample Data + +name di. +serialnumber di. +VERSION di. +VERSION_M4 di. +gyroSampleRate di. +save_mask d.on +db.print diff --git a/test/wii5_bindata/wii5_bindata.ino b/test/wii5_bindata/wii5_bindata.ino new file mode 100644 index 0000000..3a34565 --- /dev/null +++ b/test/wii5_bindata/wii5_bindata.ino @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file wii5_bindata.ino + * @brief Test sketch: BinData layout/split exercise. + */ + +/* + * BinData Test + */ + +#include +#include +#include +#include +#include + +void setup() { + wii5Setup.setupConsole(); + wii5Setup.setupIO(); + console.printf(F("waiting 1 seconds\r\n")); + delay(1000); + console.printf(F("WII5: Test BinData\r\n")); +} + +elapsedMillis wdtWait = 0; +void loop() { + if (wdtWait > 5100) { + digitalWrite(WDT_RESET, LOW); + wdtWait = 0; + } + else if (wdtWait > 5000) { + digitalWrite(WDT_RESET, HIGH); + } + + sh3dNodeIO.loop(); + console.loop(); + if (console.available()) { + switch(console.getCommand()) { + case '0': + break; + } + } + + console.printf(F("BinData: showSizes\n")); + wii5BinData.showSizes(); + + console.printf(F("BinData: Map Bits test\n")); + for (uint8_t x = 0; x < 16; x++) { + uint32_t t = 0; + SetBit(t, x); + + console.printf(F("X = %d, T = %lu\r\n"), x, t); + + for (uint8_t y = 0; y < 16; y++) { + if (BitVal(t, y)) { + console.printf(F("BitVal matched on %d\r\n"), y); + } + } + } + + console.printf(F("BinData: Trying first 32 types\r\n")); + uint32_t binDataType = 0; + while(1) { + binDataType = (binDataType << 1) + 1; + console.printf(F("BinData: type=%lu, size=%d\r\n"), binDataType, wii5BinData.getSize(binDataType)); + console.flush(); + delay(500); + console.printf(F("BinData: Creating\r\n")); + wii5BinData.setData(binDataType, wii5BinaryIridium, 340); // TODO Hard coded + console.flush(); + delay(500); + console.printf(F("BinData: type=%lu, size=%d\r\n"), binDataType, wii5BinData.getSize(binDataType)); + wii5BinData.dumpData(binDataType, wii5BinaryIridium, 340); // TODO Hard coded + console.flush(); + delay(500); + } + + delay(5000); +} diff --git a/test/wii5_commands/wii5_commands.ino b/test/wii5_commands/wii5_commands.ino new file mode 100644 index 0000000..17db15e --- /dev/null +++ b/test/wii5_commands/wii5_commands.ino @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file wii5_commands.ino + * @brief Test sketch: command-protocol exercise. + */ + +#include "WII5.h" +#include "WII5Commands.h" + +void setup () { + Serial.begin(115200); + wii5Setup.begin(); +} + +void loop() { + wii5Commands.loop(); +} diff --git a/test/wii5_gps/wii5_gps.ino b/test/wii5_gps/wii5_gps.ino new file mode 100644 index 0000000..728a52e --- /dev/null +++ b/test/wii5_gps/wii5_gps.ino @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file wii5_gps.ino + * @brief Test sketch: GPS NMEA passthrough. + */ + +#include +#include +#include + +#define SerialGPS Serial1 + +void setup() { + // Serial + Serial.begin(57600); + console.begin(); + console.add(&Serial); + console.printf(F("WII5: Test GPS")); + + // GPS + wii5Gps.begin(); + // wii5Gps.begin(&SerialGPS); + // wii5Gps.setMode(WII5GPS_PASSTHROUGH); + + wii5Gps.start(); + // wii5Gps.setMode(WII5GPS_PASSTHROUGH); +} + +void loop() { + if (SerialGPS.available()) { + char c = SerialGPS.read(); + Serial.write(c); + } + return; + + console.loop(); + wii5Gps.loop(); + if (console.available()) { + console.printf(F("CONSOLE: Got command %c=%d \r\n"), + console.getCommand(), + console.getVal() + ); + switch(console.getCommand()) { + case '0': + console.printf(F("setMode WII5GPS_OFF\n")); + wii5Gps.setMode(WII5GPS_OFF); + break; + case '1': + console.printf(F("setMode WII5GPS_QUIET\n")); + wii5Gps.setMode(WII5GPS_QUIET); + break; + case '2': + console.printf(F("setMode WII5GPS_PASSTHROUGH\n")); + wii5Gps.setMode(WII5GPS_PASSTHROUGH); + break; + case '3': + console.printf(F("setMode WII5GPS_TIMEONCE\n")); + wii5Gps.setMode(WII5GPS_TIMEONCE); + break; + case '4': + console.printf(F("setMode WII5GPS_POSONCE\n")); + wii5Gps.setMode(WII5GPS_POSONCE); + break; + case '5': + console.printf(F("setMode WII5GPS_POSACCURATE\n")); + wii5Gps.setMode(WII5GPS_POSACCURATE); + break; + case '6': + console.printf(F("setMode WII5GPS_POSREPEAT\n")); + wii5Gps.setMode(WII5GPS_POSREPEAT); + break; + + case 'D': + // TODO dump; + wii5Gps.dump(); + break; + + case 'S': + // Status + // - Number of bytes received + // - Lock status etc + break; + + default: + console.printf(F("GPS Help:\r\n")); + console.printf(F(" 0 - mode off\r\n")); + console.printf(F(" 1 - mode Quiet\r\n")); + console.printf(F(" 2 - mode Passthrough\r\n")); + console.printf(F(" 3 - mode Time Once\r\n")); + console.printf(F(" 4 - mode Position Once\r\n")); + console.printf(F(" 5 - mode Position Accurate\r\n")); + console.printf(F(" 6 - mode Position Repeat\r\n")); + console.printf(F(" D - Dump all data\r\n")); + console.printf(F(" S - Show status\r\n")); + }; + } +} diff --git a/test/wii5_iridium/wii5_iridium.ino b/test/wii5_iridium/wii5_iridium.ino new file mode 100644 index 0000000..a0ab634 --- /dev/null +++ b/test/wii5_iridium/wii5_iridium.ino @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file wii5_iridium.ino + * @brief Test sketch: Iridium AT-command exercise. + */ + +#include +#include +#include + +void setup() { + wii5Setup.begin(); +} + +void loop() { + wii5Iridium.loop(); + + console.loop(); + if (console.available()) { + switch(console.getCommand()) { + case 'p': + console.log(LOG_DEBUG, F("IRIDIUM: 'p' passthrough toggle")); + wii5Iridium.setPassthrough(!wii5Iridium.getPassthrough()); + console.log(LOG_DEBUG, F("IRIDIUM: passthrough=%d"), int(wii5Iridium.getPassthrough())); + break; + + case 'd': + console.log(LOG_DEBUG, F("IRIDIUM: 'd' debug toggle")); + wii5Iridium.setDebug(!wii5Iridium.getDebug()); + console.log(LOG_DEBUG, F("IRIDIUM: debug=%d"), int(wii5Iridium.getDebug())); + break; + + case 's': + console.log(LOG_DEBUG, F("IRIDIUM: 's' start")); + wii5Iridium.start(); + break; + + case 'S': + console.log(LOG_DEBUG, F("IRIDIUM: 'S' stop")); + wii5Iridium.stop(); + break; + + default: + break; + }; + console.log(LOG_DEBUG, F("IRIDIUM: Status = %d"), wii5Iridium.getPassthrough()); + wii5Iridium.displayStatus(); + wii5Iridium.displayError(); + } +} diff --git a/test/wii5_pins/wii5_pins.ino b/test/wii5_pins/wii5_pins.ino new file mode 100644 index 0000000..5550fbf --- /dev/null +++ b/test/wii5_pins/wii5_pins.ino @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file wii5_pins.ino + * @brief Test sketch: multi-port serial passthrough for pin diagnostics. + */ + +/* + +WII5 Pins + +*/ +#include +#include +#include + +// TODO This needs serious normalisation for each serial port ! (A class perhaps) +// Large Line buffers for passthrough +#define BUFFER_SIZE 250 +#define DEVICE_MAX 6 +Stream *streams[DEVICE_MAX]; +char bufferSerial[DEVICE_MAX][BUFFER_SIZE + 1]; +uint8_t bufferPos[DEVICE_MAX]; +uint32_t count = 0; +elapsedMillis passthroughWait = 0; +bool passthroughSerial[DEVICE_MAX]; + +void setup() { + // Short delay for debug / errors + + Serial.begin(57600); + console.begin(); + console.add(&Serial); + + sh3dNodeUtil.begin(); + + // Start each port at the right speed + Serial1.begin(9600); + streams[1] = &Serial1; + Serial2.begin(9600); + streams[2] = &Serial2; + Serial3.begin(9600); + streams[3] = &Serial3; + // Serial4.begin(9600); + // streams[4] = &Serial4; + // Serial5.begin(9600); + // streams[5] = &Serial5; +} + +void processSerial(uint8_t id) { + if (!streams[id]) { + return; + } + while (streams[id]->available()) { + char c = streams[id]->read(); + + // Ignore start/stop character of Wind Sonic + if (c == 2) + c = 'W'; + if (c == 3) + c = '*'; + + if ( (c != '\n') && (c != '\r') ) { + if (bufferPos[id] < BUFFER_SIZE) { + bufferSerial[id][bufferPos[id]] = c; + bufferPos[id]++; + bufferSerial[id][bufferPos[id]] = '\0'; + } + } + else { + if (passthroughSerial[id]) { + console.printf(F("S%d: (%lu) %s\r\n"), id, millis(), bufferSerial[id]); + } + } + } +} + +elapsedMillis wait = 0; +void loop() { + // TODO Should not be hard coded 60 seconds + /* + if (passthroughWait > 60000) { + // Skips 0 + for (uint8_t id = 1; id < DEVICE_MAX; id++) { + passthroughSerial[id] = false; + } + console.log(LOG_INFO, F("Passthrough time expired")); + } + */ + + console.loop(); + if (console.available()) { + console.log(LOG_INFO, F("WII5 Console command received - %c=%d"), + console.getCommand(), + console.getVal() + ); + + switch(console.getCommand()) { + case '1': + case '2': + case '3': + case '4': + case '5': + // Toggle + passthroughSerial[(uint8_t)console.getCommand() - '0'] + = !passthroughSerial[(uint8_t)console.getCommand() - '0']; + // Reset timer - all off when hits time + passthroughWait = 0; + console.log(LOG_INFO, + F("Passthrough enabled for $d for %d seconds"), + (uint16_t)console.getCommand() - '0', + 60 + ); + break; + + case 'A': + for (uint8_t id = 1; id < DEVICE_MAX; id++) { + passthroughSerial[id] = true; + } + passthroughWait = 0; + break; + + // Output + case 'o': + console.printf(F("OUTPU pin=%d\r\n"), int(console.getVal())); + pinMode(int(console.getVal()), OUTPUT); + break; + + // Output + case 'i': + console.printf(F("INPUT pin=%d\r\n"), int(console.getVal())); + pinMode(int(console.getVal()), INPUT); + break; + + // Low + case 'x': + console.printf(F("LOW pin=%d\r\n"), int(console.getVal())); + digitalWrite(int(console.getVal()), LOW); + console.printf(F("pin=%d\r\n"), int(digitalRead(console.getVal()))); + break; + + // HIGH + case 'X': + console.printf(F("HIGH pin=%d\r\n"), int(console.getVal())); + digitalWrite(int(console.getVal()), HIGH); + console.printf(F("pin=%d\r\n"), int(digitalRead(console.getVal()))); + break; + + // Low + case 's': + console.printf(F("pin=%d, state=%d\r\n"), int(console.getVal()), int(digitalRead(console.getVal()))); + break; + + case 'd': + // dumpPins(); + break; + + case 'h': + case 'H': + console.log(LOG_INFO, F(" WII5: Pins")); + console.log(LOG_INFO, F(" 'L' - Stop logging")); + break; + } + } + + if (wait > 500) { + console.printf(F("pin=%d, state=%d\r\n"), int(console.getVal()), int(digitalRead(console.getVal()))); + wait = 0; + } + + // Process all incoming Serial + for (uint8_t id = 1; id < DEVICE_MAX; id++) { + processSerial(id); + } +} diff --git a/test/wii5_sparton/wii5_sparton.ino b/test/wii5_sparton/wii5_sparton.ino new file mode 100644 index 0000000..aa602ca --- /dev/null +++ b/test/wii5_sparton/wii5_sparton.ino @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file wii5_sparton.ino + * @brief Test sketch: Sparton AHRS exercise. + */ + +#include +#include +#include + +void setup() { + Serial.begin(115200); + console.begin(WII5_BUFFER_CONSOLE_PRINT, WII5_BUFFER_CONSOLE_CMD); + console.add(&Serial); + console.printf(F("WII5: Test Sparton")); + + wii5Sparton.begin(); + wii5Sparton.setDebug(true); + + wii5Sparton.setRecords(5000); + wii5Sparton.start(); +} + +void loop() { + wii5Sparton.loop(); + console.loop(); + if (console.available()) { + uint32_t val = console.getVal(); + console.printf(F("CONSOLE: Got command %c=%d \r\n"), + console.getCommand(), + val + ); + switch(console.getCommand()) { + // Toggle Sparton passthrough + case 'p': + console.log(LOG_DEBUG, F("SPARTON: 'p' passthrough toggle")); + wii5Sparton.setPassthrough(!wii5Sparton.getPassthrough()); + break; + + case 's': + if (val < 100) val = 100; + console.log(LOG_DEBUG, F("SPARTON: 's' start with %lu records"), val); + wii5Sparton.setRecords(val); + wii5Sparton.start(); + break; + + case 'S': + console.log(LOG_DEBUG, F("SPARTON: 'S' stop")); + wii5Sparton.stop(); + break; + + default: + break; + }; + console.log(LOG_DEBUG, F("SPARTON: Status = %d"), wii5Sparton.getPassthrough()); + wii5Sparton.displayStatus(); + wii5Sparton.displayError(); + } +} diff --git a/test/wii5_weather_18b20/wii5_weather_18b20.ino b/test/wii5_weather_18b20/wii5_weather_18b20.ino new file mode 100644 index 0000000..a6ca015 --- /dev/null +++ b/test/wii5_weather_18b20/wii5_weather_18b20.ino @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +/** + * @file wii5_weather_18b20.ino + * @brief Test sketch: DS18B20 temperature exercise. + */ + +#include "WII5.h" +#include "WII5Weather_18B20.h" + +void setup () { + Serial.begin(115200); + console.begin(); + console.add(&Serial); + wii5Weather_18B20.begin(); +} + +void loop() { + wii5Weather_18B20.temperatureRead(); + delay(5000); +} diff --git a/tools/2022 b/tools/2022 new file mode 100644 index 0000000..f5cf665 --- /dev/null +++ b/tools/2022 @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Reference snippet (not directly executable): how the 2022 build burned the +# optiboot bootloader on an ATmega2560 via ICSP. Use tools/icsp.sh for the +# parameterized version. +# +# avrdude -C -v -patmega2560 -cstk500v2 -Pusb \ +# -Uflash:w::i \ +# -Ulock:w:0x0F:m diff --git a/tools/2024/11mhz.sh b/tools/2024/11mhz.sh new file mode 100755 index 0000000..8063379 --- /dev/null +++ b/tools/2024/11mhz.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Burn LED-flash test sketch + bootloader for the 11.0592 MHz board variant. +# See http://eleccelerator.com/fusecalc/fusecalc.php?chip=atmega2560 +# lfuse 0xff - external oscillator +# hfuse 0xd8 +# efuse 0xfd - BOD Level 1 = 2.7V +: "${WII5_AVRDUDE:=avrdude}" + +CONFIGFILE=./avrdude.conf +BOOT_HEX=./optiboot_flash_atmega2560_UART0_230400_11059200L_BIGBOOT.hex + +"$WII5_AVRDUDE" \ + -C"$CONFIGFILE" -v \ + -v \ + -patmega2560 \ + -cstk500v2 \ + -Pusb \ + -Uflash:w:ledflash_11mhz.ino.hex:i \ + -Uboot:w:"$BOOT_HEX":i \ + -Ulock:w:0 diff --git a/tools/2024/16mhz.sh b/tools/2024/16mhz.sh new file mode 100755 index 0000000..0e42a05 --- /dev/null +++ b/tools/2024/16mhz.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Burn LED-flash test sketch + bootloader for the 16 MHz board variant. +# See http://eleccelerator.com/fusecalc/fusecalc.php?chip=atmega2560 +# lfuse 0xff - external oscillator +# hfuse 0xd8 +# efuse 0xfd - BOD Level 1 = 2.7V +: "${WII5_AVRDUDE:=avrdude}" + +CONFIGFILE=./avrdude.conf +BOOT_HEX=./optiboot_flash_atmega2560_UART0_230400_11059200L_BIGBOOT.hex + +"$WII5_AVRDUDE" \ + -C"$CONFIGFILE" -v \ + -v \ + -patmega2560 \ + -cstk500v2 \ + -Pusb \ + -Uflash:w:ledflash_16mhz.ino.hex:i \ + -Uboot:w:"$BOOT_HEX":i \ + -Ulock:w:0 diff --git a/tools/2024/Arduino.tar.gz b/tools/2024/Arduino.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..e095fe98e88fe388338978c7945a53f76737dfda GIT binary patch literal 647741 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 Dh=v!sD}=+Z&e1A2v_Zo=ozG!D50Q7CRDy41 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 z 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 XU##pt{-I`l91GPa_r#g4Uu*R8H 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 z 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 # optional parent +# id = [, [, ] ...] ; # are quoted strings +# desc = ; # quoted string +# type = ; # programmer type, quoted string +# # supported programmer types can be listed by "-c ?type" +# connection_type = parallel | serial | usb +# baudrate = ; # baudrate for avr910-programmer +# vcc = [, ... ] ; # pin number(s) +# buff = [, ... ] ; # pin number(s) +# reset = ; # pin number +# sck = ; # pin number +# mosi = ; # pin number +# miso = ; # pin number +# errled = ; # pin number +# rdyled = ; # pin number +# pgmled = ; # pin number +# vfyled = ; # pin number +# usbvid = ; # USB VID (Vendor ID) +# usbpid = [, ...] # USB PID (Product ID) (1) +# usbdev = ; # USB interface or other device info +# usbvendor = ; # USB Vendor Name +# usbproduct = ; # USB Product Name +# usbsn = ; # USB Serial Number +# +# To invert a bit, use = ~ , the spaces are important. +# For a pin list all pins must be inverted. +# A single pin can be specified as usual = ~ , for lists +# specify it as follows = ~ ( [, ... ] ) . +# +# (1) Not all programmer types can process a list of PIDs. +# ; +# +# part +# id = ; # quoted string +# desc = ; # quoted string +# has_jtag = ; # part has JTAG i/f +# has_debugwire = ; # part has debugWire i/f +# has_pdi = ; # part has PDI i/f +# has_updi = ; # part has UPDI i/f +# has_tpi = ; # part has TPI i/f +# devicecode = ; # deprecated, use stk500_devcode +# stk500_devcode = ; # numeric +# avr910_devcode = ; # numeric +# signature = ; # signature bytes +# usbpid = ; # DFU USB PID +# chip_erase_delay = ; # micro-seconds +# reset = dedicated | io; +# retry_pulse = reset | sck; +# pgm_enable = ; +# chip_erase = ; +# chip_erase_delay = ; # chip erase delay (us) +# # STK500 parameters (parallel programming IO lines) +# pagel = ; # pin name in hex, i.e., 0xD7 +# bs2 = ; # pin name in hex, i.e., 0xA0 +# serial = ; # can use serial downloading +# parallel = ; # can use par. programming +# # STK500v2 parameters, to be taken from Atmel's XML files +# timeout = ; +# stabdelay = ; +# cmdexedelay = ; +# synchloops = ; +# bytedelay = ; +# pollvalue = ; +# pollindex = ; +# predelay = ; +# postdelay = ; +# pollmethod = ; +# mode = ; +# delay = ; +# blocksize = ; +# readsize = ; +# hvspcmdexedelay = ; +# # STK500v2 HV programming parameters, from XML +# pp_controlstack = , , ...; # PP only +# hvsp_controlstack = , , ...; # HVSP only +# hventerstabdelay = ; +# progmodedelay = ; # PP only +# latchcycles = ; +# togglevtg = ; +# poweroffdelay = ; +# resetdelayms = ; +# resetdelayus = ; +# hvleavestabdelay = ; +# resetdelay = ; +# synchcycles = ; # HVSP only +# chiperasepulsewidth = ; # PP only +# chiperasepolltimeout = ; +# chiperasetime = ; # HVSP only +# programfusepulsewidth = ; # PP only +# programfusepolltimeout = ; +# programlockpulsewidth = ; # PP only +# programlockpolltimeout = ; +# # JTAG ICE mkII parameters, also from XML files +# allowfullpagebitstream = ; +# enablepageprogramming = ; +# idr = ; # IO addr of IDR (OCD) reg. +# rampz = ; # IO addr of RAMPZ reg. +# spmcr = ; # mem addr of SPMC[S]R reg. +# eecr = ; # mem addr of EECR reg. +# # (only when != 0x3c) +# is_at90s1200 = ; # AT90S1200 part +# is_avr32 = ; # AVR32 part +# +# memory +# paged = ; # yes / no +# size = ; # bytes +# page_size = ; # bytes +# num_pages = ; # numeric +# min_write_delay = ; # micro-seconds +# max_write_delay = ; # micro-seconds +# readback_p1 = ; # byte value +# readback_p2 = ; # byte value +# pwroff_after_write = ; # yes / no +# read = ; +# write = ; +# read_lo = ; +# read_hi = ; +# write_lo = ; +# write_hi = ; +# loadpage_lo = ; +# loadpage_hi = ; +# writepage = ; +# ; +# ; +# +# If any of the above parameters are not specified, the default value +# of 0 is used for numerics or the empty string ("") for string +# values. If a required parameter is left empty, AVRDUDE will +# complain. +# +# Parts can also inherit parameters from previously defined parts +# using the following syntax. In this case specified integer and +# string values override parameter values from the parent part. New +# memory definitions are added to the definitions inherited from the +# parent. +# +# part parent # quoted string +# id = ; # quoted string +# +# ; +# +# NOTES: +# * 'devicecode' is the device code used by the STK500 (see codes +# listed below) +# * Not all memory types will implement all instructions. +# * AVR Fuse bits and Lock bits are implemented as a type of memory. +# * Example memory types are: +# "flash", "eeprom", "fuse", "lfuse" (low fuse), "hfuse" (high +# fuse), "signature", "calibration", "lock" +# * The memory type specified on the avrdude command line must match +# one of the memory types defined for the specified chip. +# * The pwroff_after_write flag causes avrdude to attempt to +# power the device off and back on after an unsuccessful write to +# the affected memory area if VCC programmer pins are defined. If +# VCC pins are not defined for the programmer, a message +# indicating that the device needs a power-cycle is printed out. +# This flag was added to work around a problem with the +# at90s4433/2333's; see the at90s4433 errata at: +# +# http://www.atmel.com/dyn/resources/prod_documents/doc1280.pdf +# +# INSTRUCTION FORMATS +# +# Instruction formats are specified as a comma seperated list of +# string values containing information (bit specifiers) about each +# of the 32 bits of the instruction. Bit specifiers may be one of +# the following formats: +# +# '1' = the bit is always set on input as well as output +# +# '0' = the bit is always clear on input as well as output +# +# 'x' = the bit is ignored on input and output +# +# 'a' = the bit is an address bit, the bit-number matches this bit +# specifier's position within the current instruction byte +# +# 'aN' = the bit is the Nth address bit, bit-number = N, i.e., a12 +# is address bit 12 on input, a0 is address bit 0. +# +# 'i' = the bit is an input data bit +# +# 'o' = the bit is an output data bit +# +# Each instruction must be composed of 32 bit specifiers. The +# instruction specification closely follows the instruction data +# provided in Atmel's data sheets for their parts. +# +# See below for some examples. +# +# +# The following are STK500 part device codes to use for the +# "devicecode" field of the part. These came from Atmel's software +# section avr061.zip which accompanies the application note +# AVR061 available from: +# +# http://www.atmel.com/dyn/resources/prod_documents/doc2525.pdf +# + +#define ATTINY10 0x10 /* the _old_ one that never existed! */ +#define ATTINY11 0x11 +#define ATTINY12 0x12 +#define ATTINY15 0x13 +#define ATTINY13 0x14 + +#define ATTINY22 0x20 +#define ATTINY26 0x21 +#define ATTINY28 0x22 +#define ATTINY2313 0x23 + +#define AT90S1200 0x33 + +#define AT90S2313 0x40 +#define AT90S2323 0x41 +#define AT90S2333 0x42 +#define AT90S2343 0x43 + +#define AT90S4414 0x50 +#define AT90S4433 0x51 +#define AT90S4434 0x52 +#define ATMEGA48 0x59 + +#define AT90S8515 0x60 +#define AT90S8535 0x61 +#define AT90C8534 0x62 +#define ATMEGA8515 0x63 +#define ATMEGA8535 0x64 + +#define ATMEGA8 0x70 +#define ATMEGA88 0x73 +#define ATMEGA168 0x86 + +#define ATMEGA161 0x80 +#define ATMEGA163 0x81 +#define ATMEGA16 0x82 +#define ATMEGA162 0x83 +#define ATMEGA169 0x84 + +#define ATMEGA323 0x90 +#define ATMEGA32 0x91 + +#define ATMEGA64 0xA0 + +#define ATMEGA103 0xB1 +#define ATMEGA128 0xB2 +#define AT90CAN128 0xB3 +#define AT90CAN64 0xB3 +#define AT90CAN32 0xB3 + +#define AT86RF401 0xD0 + +#define AT89START 0xE0 +#define AT89S51 0xE0 +#define AT89S52 0xE1 + +# The following table lists the devices in the original AVR910 +# appnote: +# |Device |Signature | Code | +# +-------+----------+------+ +# |tiny12 | 1E 90 05 | 0x55 | +# |tiny15 | 1E 90 06 | 0x56 | +# | | | | +# | S1200 | 1E 90 01 | 0x13 | +# | | | | +# | S2313 | 1E 91 01 | 0x20 | +# | S2323 | 1E 91 02 | 0x48 | +# | S2333 | 1E 91 05 | 0x34 | +# | S2343 | 1E 91 03 | 0x4C | +# | | | | +# | S4414 | 1E 92 01 | 0x28 | +# | S4433 | 1E 92 03 | 0x30 | +# | S4434 | 1E 92 02 | 0x6C | +# | | | | +# | S8515 | 1E 93 01 | 0x38 | +# | S8535 | 1E 93 03 | 0x68 | +# | | | | +# |mega32 | 1E 95 01 | 0x72 | +# |mega83 | 1E 93 05 | 0x65 | +# |mega103| 1E 97 01 | 0x41 | +# |mega161| 1E 94 01 | 0x60 | +# |mega163| 1E 94 02 | 0x64 | + +# Appnote AVR109 also has a table of AVR910 device codes, which +# lists: +# dev avr910 signature +# ATmega8 0x77 0x1E 0x93 0x07 +# ATmega8515 0x3B 0x1E 0x93 0x06 +# ATmega8535 0x6A 0x1E 0x93 0x08 +# ATmega16 0x75 0x1E 0x94 0x03 +# ATmega162 0x63 0x1E 0x94 0x04 +# ATmega163 0x66 0x1E 0x94 0x02 +# ATmega169 0x79 0x1E 0x94 0x05 +# ATmega32 0x7F 0x1E 0x95 0x02 +# ATmega323 0x73 0x1E 0x95 0x01 +# ATmega64 0x46 0x1E 0x96 0x02 +# ATmega128 0x44 0x1E 0x97 0x02 +# +# These codes refer to "BOOT" device codes which are apparently +# different than standard device codes, for whatever reasons +# (often one above the standard code). + +# There are several extended versions of AVR910 implementations around +# in the Internet. These add the following codes (only devices that +# actually exist are listed): + +# ATmega8515 0x3A +# ATmega128 0x43 +# ATmega64 0x45 +# ATtiny26 0x5E +# ATmega8535 0x69 +# ATmega32 0x72 +# ATmega16 0x74 +# ATmega8 0x76 +# ATmega169 0x78 + +# +# Overall avrdude defaults; suitable for ~/.avrduderc +# +default_parallel = "unknown"; +default_serial = "unknown"; +# default_bitclock = 2.5; + +# Turn off safemode by default +#default_safemode = no; + + +# +# PROGRAMMER DEFINITIONS +# + +# http://wiring.org.co/ +# Basically STK500v2 protocol, with some glue to trigger the +# bootloader. +programmer + id = "wiring"; + desc = "Wiring"; + type = "wiring"; + connection_type = serial; +; + +programmer + id = "arduino"; + desc = "Arduino"; + type = "arduino"; + connection_type = serial; +; +# this will interface with the chips on these programmers: +# +# http://real.kiev.ua/old/avreal/en/adapters +# http://www.amontec.com/jtagkey.shtml, jtagkey-tiny.shtml +# http://www.olimex.com/dev/arm-usb-ocd.html, arm-usb-tiny.html +# http://www.ethernut.de/en/hardware/turtelizer/index.html +# http://elk.informatik.fh-augsburg.de/hhweb/doc/openocd/usbjtag/usbjtag.html +# http://dangerousprototypes.com/docs/FT2232_breakout_board +# http://www.ftdichip.com/Products/Modules/DLPModules.htm,DLP-2232*,DLP-USB1232H +# http://flashrom.org/FT2232SPI_Programmer +# +# The drivers will look for a specific device and use the first one found. +# If you have mulitple devices, then look for unique information (like SN) +# And fill that in here. +# +# Note that the pin numbers for the main ISP signals (reset, sck, +# mosi, miso) are fixed and cannot be changed, since they must match +# the way the Multi-Protocol Synchronous Serial Engine (MPSSE) of +# these FTDI ICs has been designed. + +programmer + id = "avrftdi"; + desc = "FT2232D based generic programmer"; + type = "avrftdi"; + connection_type = usb; + usbvid = 0x0403; + usbpid = 0x6010; + usbvendor = ""; + usbproduct = ""; + usbdev = "A"; + usbsn = ""; +#ISP-signals - lower ADBUS-Nibble (default) + reset = 3; + sck = 0; + mosi = 1; + miso = 2; +#LED SIGNALs - higher ADBUS-Nibble +# errled = 4; +# rdyled = 5; +# pgmled = 6; +# vfyled = 7; +#Buffer Signal - ACBUS - Nibble +# buff = 8; +; +# This is an implementation of the above with a buffer IC (74AC244) and +# 4 LEDs directly attached, all active low. +programmer + id = "2232HIO"; + desc = "FT2232H based generic programmer"; + type = "avrftdi"; + connection_type = usb; + usbvid = 0x0403; +# Note: This PID is reserved for generic H devices and +# should be programmed into the EEPROM +# usbpid = 0x8A48; + usbpid = 0x6010; + usbdev = "A"; + usbvendor = ""; + usbproduct = ""; + usbsn = ""; +#ISP-signals + reset = 3; + sck = 0; + mosi = 1; + miso = 2; + buff = ~4; +#LED SIGNALs + errled = ~ 11; + rdyled = ~ 14; + pgmled = ~ 13; + vfyled = ~ 12; +; + +#The FT4232H can be treated as FT2232H, but it has a different USB +#device ID of 0x6011. +programmer parent "avrftdi" + id = "4232h"; + desc = "FT4232H based generic programmer"; + usbpid = 0x6011; +; + +programmer + id = "jtagkey"; + desc = "Amontec JTAGKey, JTAGKey-Tiny and JTAGKey2"; + type = "avrftdi"; + connection_type = usb; + usbvid = 0x0403; +# Note: This PID is used in all JTAGKey variants + usbpid = 0xCFF8; + usbdev = "A"; + usbvendor = ""; + usbproduct = ""; + usbsn = ""; +#ISP-signals => 20 - Pin connector on JTAGKey + reset = 3; # TMS 7 violet + sck = 0; # TCK 9 white + mosi = 1; # TDI 5 green + miso = 2; # TDO 13 orange + buff = ~4; +# VTG VREF 1 brown with red tip +# GND GND 20 black +# The colors are on the 20 pin breakout cable +# from Amontec +; + +# UM232H module from FTDI and Glyn.com.au. +# See helix.air.net.au for detailed usage information. +# J1: Connect pin 2 and 3 for USB power. +# J2: Connect pin 2 and 3 for USB power. +# J2: Pin 7 is SCK +# : Pin 8 is MOSI +# : Pin 9 is MISO +# : Pin 11 is RST +# : Pin 6 is ground +# Use the -b flag to set the SPI clock rate eg -b 3750000 is the fastest I could get +# a 16MHz Atmega1280 to program reliably. The 232H is conveniently 5V tolerant. +programmer + id = "UM232H"; + desc = "FT232H based module from FTDI and Glyn.com.au"; + type = "avrftdi"; + usbvid = 0x0403; +# Note: This PID is reserved for generic 232H devices and +# should be programmed into the EEPROM + usbpid = 0x6014; + usbdev = "A"; + usbvendor = ""; + usbproduct = ""; + usbsn = ""; +#ISP-signals + sck = 0; + mosi = 1; + miso = 2; + reset = 3; +; + +# C232HM module from FTDI and Glyn.com.au. +# : Orange is SCK +# : Yellow is MOSI +# : Green is MISO +# : Brown is RST +# : Black is ground +# Use the -b flag to set the SPI clock rate eg -b 3750000 is the fastest I could get +# a 16MHz Atmega1280 to program reliably. The 232H is conveniently 5V tolerant. +programmer + id = "C232HM"; + desc = "FT232H based module from FTDI and Glyn.com.au"; + type = "avrftdi"; + usbvid = 0x0403; +# Note: This PID is reserved for generic 232H devices and +# should be programmed into the EEPROM + usbpid = 0x6014; + usbdev = "A"; + usbvendor = ""; + usbproduct = ""; + usbsn = ""; +#ISP-signals + sck = 0; + mosi = 1; + miso = 2; + reset = 3; +; + + +# On the adapter you can read "O-Link". On the PCB is printed "OpenJTAG v3.1" +# You can find it as "OpenJTAG ARM JTAG USB" in the internet. +# (But there are also several projects called Open JTAG, eg. +# http://www.openjtag.org, which are completely different.) +# http://www.100ask.net/shop/english.html (website seems to be outdated) +# http://item.taobao.com/item.htm?id=1559277013 +# http://www.micro4you.com/store/openjtag-arm-jtag-usb.html (schematics!) +# some other sources which call it O-Link +# http://www.andahammer.com/olink/ +# http://www.developmentboard.net/31-o-link-debugger.html +# http://armwerks.com/catalog/o-link-debugger-copy/ +# or just have a look at ebay ... +# It is basically the same entry as jtagkey with different usb ids. +programmer parent "jtagkey" + id = "o-link"; + desc = "O-Link, OpenJTAG from www.100ask.net"; + usbvid = 0x1457; + usbpid = 0x5118; + usbvendor = "www.100ask.net"; + usbproduct = "USB<=>JTAG&RS232"; +; + +# http://wiki.openmoko.org/wiki/Debug_Board_v3 +programmer + id = "openmoko"; + desc = "Openmoko debug board (v3)"; + type = "avrftdi"; + usbvid = 0x1457; + usbpid = 0x5118; + usbdev = "A"; + usbvendor = ""; + usbproduct = ""; + usbsn = ""; + reset = 3; # TMS 7 + sck = 0; # TCK 9 + mosi = 1; # TDI 5 + miso = 2; # TDO 13 +; + +# Only Rev. A boards. +# Schematic and user manual: http://www.cs.put.poznan.pl/wswitala/download/pdf/811EVBK.pdf +programmer + id = "lm3s811"; + desc = "Luminary Micro LM3S811 Eval Board (Rev. A)"; + type = "avrftdi"; + connection_type = usb; + usbvid = 0x0403; + usbpid = 0xbcd9; + usbvendor = "LMI"; + usbproduct = "LM3S811 Evaluation Board"; + usbdev = "A"; + usbsn = ""; +#ISP-signals - lower ACBUS-Nibble (default) + reset = 3; + sck = 0; + mosi = 1; + miso = 2; +# Enable correct buffers + buff = 7; +; + +# submitted as bug #46020 +programmer + id = "tumpa"; + desc = "TIAO USB Multi-Protocol Adapter"; + type = "avrftdi"; + connection_type = usb; + usbvid = 0x0403; + usbpid = 0x8A98; + usbdev = "A"; + usbvendor = "TIAO"; + usbproduct = ""; + usbsn = ""; + sck = 0; # TCK 9 + mosi = 1; # TDI 5 + miso = 2; # TDO 13 + reset = 3; # TMS 7 +; + +programmer + id = "avrisp"; + desc = "Atmel AVR ISP"; + type = "stk500"; + connection_type = serial; +; + +programmer + id = "avrispv2"; + desc = "Atmel AVR ISP V2"; + type = "stk500v2"; + connection_type = serial; +; + +programmer + id = "avrispmkII"; + desc = "Atmel AVR ISP mkII"; + type = "stk500v2"; + connection_type = usb; +; + +programmer parent "avrispmkII" + id = "avrisp2"; +; + +programmer + id = "buspirate"; + desc = "The Bus Pirate"; + type = "buspirate"; + connection_type = serial; +; + +programmer + id = "buspirate_bb"; + desc = "The Bus Pirate (bitbang interface, supports TPI)"; + type = "buspirate_bb"; + connection_type = serial; + # pins are bits in bitbang byte (numbers are 87654321) + # 1|POWER|PULLUP|AUX|MOSI|CLK|MISO|CS + reset = 1; + sck = 3; + mosi = 4; + miso = 2; + #vcc = 7; This is internally set independent of this setting. +; + +# This is supposed to be the "default" STK500 entry. +# Attempts to select the correct firmware version +# by probing for it. Better use one of the entries +# below instead. +programmer + id = "stk500"; + desc = "Atmel STK500"; + type = "stk500generic"; + connection_type = serial; +; + +programmer + id = "stk500v1"; + desc = "Atmel STK500 Version 1.x firmware"; + type = "stk500"; + connection_type = serial; +; + +programmer + id = "mib510"; + desc = "Crossbow MIB510 programming board"; + type = "stk500"; + connection_type = serial; +; + +programmer + id = "stk500v2"; + desc = "Atmel STK500 Version 2.x firmware"; + type = "stk500v2"; + connection_type = serial; +; + +programmer + id = "stk500pp"; + desc = "Atmel STK500 V2 in parallel programming mode"; + type = "stk500pp"; + connection_type = serial; +; + +programmer + id = "stk500hvsp"; + desc = "Atmel STK500 V2 in high-voltage serial programming mode"; + type = "stk500hvsp"; + connection_type = serial; +; + +programmer + id = "stk600"; + desc = "Atmel STK600"; + type = "stk600"; + connection_type = usb; +; + +programmer + id = "stk600pp"; + desc = "Atmel STK600 in parallel programming mode"; + type = "stk600pp"; + connection_type = usb; +; + +programmer + id = "stk600hvsp"; + desc = "Atmel STK600 in high-voltage serial programming mode"; + type = "stk600hvsp"; + connection_type = usb; +; + +programmer + id = "avr910"; + desc = "Atmel Low Cost Serial Programmer"; + type = "avr910"; + connection_type = serial; +; + +programmer + id = "ft245r"; + desc = "FT245R Synchronous BitBang"; + type = "ftdi_syncbb"; + connection_type = usb; + miso = 1; # D1 + sck = 0; # D0 + mosi = 2; # D2 + reset = 4; # D4 +; + +programmer + id = "ft232r"; + desc = "FT232R Synchronous BitBang"; + type = "ftdi_syncbb"; + connection_type = usb; + miso = 1; # RxD + sck = 0; # TxD + mosi = 2; # RTS + reset = 4; # DTR +; + +# see http://www.bitwizard.nl/wiki/index.php/FTDI_ATmega +programmer + id = "bwmega"; + desc = "BitWizard ftdi_atmega builtin programmer"; + type = "ftdi_syncbb"; + connection_type = usb; + miso = 5; # DSR + sck = 6; # DCD + mosi = 3; # CTS + reset = 7; # RI +; + +# see http://www.geocities.jp/arduino_diecimila/bootloader/index_en.html +# Note: pins are numbered from 1! +programmer + id = "arduino-ft232r"; + desc = "Arduino: FT232R connected to ISP"; + type = "ftdi_syncbb"; + connection_type = usb; + miso = 3; # CTS X3(1) + sck = 5; # DSR X3(2) + mosi = 6; # DCD X3(3) + reset = 7; # RI X3(4) +; + +# website mentioned above uses this id +programmer parent "arduino-ft232r" + id = "diecimila"; + desc = "alias for arduino-ft232r"; +; + +# There is a ATmega328P kit PCB called "uncompatino". +# This board allows ISP via its on-board FT232R. +# This is designed like Arduino Duemilanove but has no standard ICPS header. +# Its 4 pairs of pins are shorted to enable ftdi_syncbb. +# http://akizukidenshi.com/catalog/g/gP-07487/ +# http://akizukidenshi.com/download/ds/akizuki/k6096_manual_20130816.pdf +programmer + id = "uncompatino"; + desc = "uncompatino with all pairs of pins shorted"; + type = "ftdi_syncbb"; + connection_type = usb; + miso = 3; # cts + sck = 5; # dsr + mosi = 6; # dcd + reset = 7; # ri +; + +# FTDI USB to serial cable TTL-232R-5V with a custom adapter for ICSP +# http://www.ftdichip.com/Products/Cables/USBTTLSerial.htm +# http://www.ftdichip.com/Support/Documents/DataSheets/Cables/DS_TTL-232R_CABLES.pdf +# For ICSP pinout see for example http://www.atmel.com/images/doc2562.pdf +# (Figure 1. ISP6PIN header pinout and Table 1. Connections required for ISP ...) +# TTL-232R GND 1 Black -> ICPS GND (pin 6) +# TTL-232R CTS 2 Brown -> ICPS MOSI (pin 4) +# TTL-232R VCC 3 Red -> ICPS VCC (pin 2) +# TTL-232R TXD 4 Orange -> ICPS RESET (pin 5) +# TTL-232R RXD 5 Yellow -> ICPS SCK (pin 3) +# TTL-232R RTS 6 Green -> ICPS MISO (pin 1) +# Except for VCC and GND, you can connect arbitual pairs as long as +# the following table is adjusted. +programmer + id = "ttl232r"; + desc = "FTDI TTL232R-5V with ICSP adapter"; + type = "ftdi_syncbb"; + connection_type = usb; + miso = 2; # rts + sck = 1; # rxd + mosi = 3; # cts + reset = 0; # txd +; + +programmer + id = "usbasp"; + desc = "USBasp, http://www.fischl.de/usbasp/"; + type = "usbasp"; + connection_type = usb; + usbvid = 0x16C0; # VOTI + usbpid = 0x05DC; # Obdev's free shared PID + usbvendor = "www.fischl.de"; + usbproduct = "USBasp"; + + # following variants are autodetected for id "usbasp" + + # original usbasp from fischl.de + # see above "usbasp" + + # old usbasp from fischl.de + #usbvid = 0x03EB; # ATMEL + #usbpid = 0xC7B4; # (unoffical) USBasp + #usbvendor = "www.fischl.de"; + #usbproduct = "USBasp"; + + # NIBObee (only if -P nibobee is given on command line) + # see below "nibobee" +; + +programmer + id = "nibobee"; + desc = "NIBObee"; + type = "usbasp"; + connection_type = usb; + usbvid = 0x16C0; # VOTI + usbpid = 0x092F; # NIBObee PID + usbvendor = "www.nicai-systems.com"; + usbproduct = "NIBObee"; +; + +programmer + id = "usbasp-clone"; + desc = "Any usbasp clone with correct VID/PID"; + type = "usbasp"; + connection_type = usb; + usbvid = 0x16C0; # VOTI + usbpid = 0x05DC; # Obdev's free shared PID + #usbvendor = ""; + #usbproduct = ""; +; + +# USBtiny can also be used for TPI programming. +# In that case, a resistor of 1 kOhm is needed between MISO and MOSI +# pins of the connector, and MISO (pin 1 of the 6-pin connector) +# connects to TPIDATA. +programmer + id = "usbtiny"; + desc = "USBtiny simple USB programmer, https://learn.adafruit.com/usbtinyisp"; + type = "usbtiny"; + connection_type = usb; + usbvid = 0x1781; + usbpid = 0x0c9f; +; + +# commercial version of USBtiny, using a separate VID/PID +programmer + id = "ehajo-isp"; + desc = "avr-isp-programmer from eHaJo, http://www.eHaJo.de"; + type = "usbtiny"; + connection_type = usb; + usbvid = 0x16D0; + usbpid = 0x0BA5; +; + +programmer + id = "arduinoisp"; + desc = "Arduino ISP Programmer"; + type = "usbtiny"; + connection_type = usb; + usbvid = 0x2341; + usbpid = 0x0049; +; + +programmer + id = "arduinoisporg"; + desc = "Arduino ISP Programmer"; + type = "usbtiny"; + connection_type = usb; + usbvid = 0x2A03; + usbpid = 0x0049; +; + +programmer + id = "butterfly"; + desc = "Atmel Butterfly Development Board"; + type = "butterfly"; + connection_type = serial; +; + +programmer + id = "avr109"; + desc = "Atmel AppNote AVR109 Boot Loader"; + type = "butterfly"; + connection_type = serial; +; + +programmer + id = "avr911"; + desc = "Atmel AppNote AVR911 AVROSP"; + type = "butterfly"; + connection_type = serial; +; + +# suggested in http://forum.mikrokopter.de/topic-post48317.html +programmer + id = "mkbutterfly"; + desc = "Mikrokopter.de Butterfly"; + type = "butterfly_mk"; + connection_type = serial; +; + +programmer parent "mkbutterfly" + id = "butterfly_mk"; +; + +programmer + id = "jtagmkI"; + desc = "Atmel JTAG ICE (mkI)"; + baudrate = 115200; # default is 115200 + type = "jtagmki"; + connection_type = serial; +; + +# easier to type +programmer parent "jtagmkI" + id = "jtag1"; +; + +# easier to type +programmer parent "jtag1" + id = "jtag1slow"; + baudrate = 19200; +; + +# The JTAG ICE mkII has both, serial and USB connectivity. As it is +# mostly used through USB these days (AVR Studio 5 only supporting it +# that way), we make connection_type = usb the default. Users are +# still free to use a serial port with the -P option. + +programmer + id = "jtagmkII"; + desc = "Atmel JTAG ICE mkII"; + baudrate = 19200; # default is 19200 + type = "jtagmkii"; + connection_type = usb; +; + +# easier to type +programmer parent "jtagmkII" + id = "jtag2slow"; +; + +# JTAG ICE mkII @ 115200 Bd +programmer parent "jtag2slow" + id = "jtag2fast"; + baudrate = 115200; +; + +# make the fast one the default, people will love that +programmer parent "jtag2fast" + id = "jtag2"; +; + +# JTAG ICE mkII in ISP mode +programmer + id = "jtag2isp"; + desc = "Atmel JTAG ICE mkII in ISP mode"; + baudrate = 115200; + type = "jtagmkii_isp"; + connection_type = usb; +; + +# JTAG ICE mkII in debugWire mode +programmer + id = "jtag2dw"; + desc = "Atmel JTAG ICE mkII in debugWire mode"; + baudrate = 115200; + type = "jtagmkii_dw"; + connection_type = usb; +; + +# JTAG ICE mkII in AVR32 mode +programmer + id = "jtagmkII_avr32"; + desc = "Atmel JTAG ICE mkII im AVR32 mode"; + baudrate = 115200; + type = "jtagmkii_avr32"; + connection_type = usb; +; + +# JTAG ICE mkII in AVR32 mode +programmer + id = "jtag2avr32"; + desc = "Atmel JTAG ICE mkII im AVR32 mode"; + baudrate = 115200; + type = "jtagmkii_avr32"; + connection_type = usb; +; + +# JTAG ICE mkII in PDI mode +programmer + id = "jtag2pdi"; + desc = "Atmel JTAG ICE mkII PDI mode"; + baudrate = 115200; + type = "jtagmkii_pdi"; + connection_type = usb; +; + +# AVR Dragon in JTAG mode +programmer + id = "dragon_jtag"; + desc = "Atmel AVR Dragon in JTAG mode"; + baudrate = 115200; + type = "dragon_jtag"; + connection_type = usb; +; + +# AVR Dragon in ISP mode +programmer + id = "dragon_isp"; + desc = "Atmel AVR Dragon in ISP mode"; + baudrate = 115200; + type = "dragon_isp"; + connection_type = usb; +; + +# AVR Dragon in PP mode +programmer + id = "dragon_pp"; + desc = "Atmel AVR Dragon in PP mode"; + baudrate = 115200; + type = "dragon_pp"; + connection_type = usb; +; + +# AVR Dragon in HVSP mode +programmer + id = "dragon_hvsp"; + desc = "Atmel AVR Dragon in HVSP mode"; + baudrate = 115200; + type = "dragon_hvsp"; + connection_type = usb; +; + +# AVR Dragon in debugWire mode +programmer + id = "dragon_dw"; + desc = "Atmel AVR Dragon in debugWire mode"; + baudrate = 115200; + type = "dragon_dw"; + connection_type = usb; +; + +# AVR Dragon in PDI mode +programmer + id = "dragon_pdi"; + desc = "Atmel AVR Dragon in PDI mode"; + baudrate = 115200; + type = "dragon_pdi"; + connection_type = usb; +; + +programmer + id = "jtag3"; + desc = "Atmel AVR JTAGICE3 in JTAG mode"; + type = "jtagice3"; + connection_type = usb; + usbpid = 0x2110, 0x2140; +; + +programmer + id = "jtag3pdi"; + desc = "Atmel AVR JTAGICE3 in PDI mode"; + type = "jtagice3_pdi"; + connection_type = usb; + usbpid = 0x2110, 0x2140; +; + +programmer + id = "jtag3dw"; + desc = "Atmel AVR JTAGICE3 in debugWIRE mode"; + type = "jtagice3_dw"; + connection_type = usb; + usbpid = 0x2110, 0x2140; +; + +programmer + id = "jtag3isp"; + desc = "Atmel AVR JTAGICE3 in ISP mode"; + type = "jtagice3_isp"; + connection_type = usb; + usbpid = 0x2110, 0x2140; +; + +programmer + id = "xplainedpro"; + desc = "Atmel AVR XplainedPro in JTAG mode"; + type = "jtagice3"; + connection_type = usb; + usbpid = 0x2111; +; + +programmer + id = "xplainedpro_updi"; + desc = "Atmel AVR XplainedPro in UPDI mode"; + type = "jtagice3_updi"; + connection_type = usb; + usbpid = 0x2111; +; + +programmer + id = "xplainedmini"; + desc = "Atmel AVR XplainedMini in ISP mode"; + type = "jtagice3_isp"; + connection_type = usb; + usbpid = 0x2145; +; + +programmer + id = "xplainedmini_dw"; + desc = "Atmel AVR XplainedMini in debugWIRE mode"; + type = "jtagice3_dw"; + connection_type = usb; + usbpid = 0x2145; +; + +programmer + id = "xplainedmini_updi"; + desc = "Atmel AVR XplainedMini in UPDI mode"; + type = "jtagice3_updi"; + connection_type = usb; + usbpid = 0x2145; +; + +programmer + id = "atmelice"; + desc = "Atmel-ICE (ARM/AVR) in JTAG mode"; + type = "jtagice3"; + connection_type = usb; + usbpid = 0x2141; +; + +programmer + id = "atmelice_pdi"; + desc = "Atmel-ICE (ARM/AVR) in PDI mode"; + type = "jtagice3_pdi"; + connection_type = usb; + usbpid = 0x2141; +; + +programmer + id = "atmelice_updi"; + desc = "Atmel-ICE (ARM/AVR) in UPDI mode"; + type = "jtagice3_updi"; + connection_type = usb; + usbpid = 0x2141; +; + +programmer + id = "atmelice_dw"; + desc = "Atmel-ICE (ARM/AVR) in debugWIRE mode"; + type = "jtagice3_dw"; + connection_type = usb; + usbpid = 0x2141; +; + +programmer + id = "atmelice_isp"; + desc = "Atmel-ICE (ARM/AVR) in ISP mode"; + type = "jtagice3_isp"; + connection_type = usb; + usbpid = 0x2141; +; + +programmer + id = "powerdebugger"; + desc = "Atmel PowerDebugger (ARM/AVR) in JTAG mode"; + type = "jtagice3"; + connection_type = usb; + usbpid = 0x2144; +; + +programmer + id = "powerdebugger_pdi"; + desc = "Atmel PowerDebugger (ARM/AVR) in PDI mode"; + type = "jtagice3_pdi"; + connection_type = usb; + usbpid = 0x2144; +; + +programmer + id = "powerdebugger_updi"; + desc = "Atmel PowerDebugger (ARM/AVR) in UPDI mode"; + type = "jtagice3_updi"; + connection_type = usb; + usbpid = 0x2144; +; + +programmer + id = "powerdebugger_dw"; + desc = "Atmel PowerDebugger (ARM/AVR) in debugWire mode"; + type = "jtagice3_dw"; + connection_type = usb; + usbpid = 0x2144; +; + +programmer + id = "powerdebugger_isp"; + desc = "Atmel PowerDebugger (ARM/AVR) in ISP mode"; + type = "jtagice3_isp"; + connection_type = usb; + usbpid = 0x2144; +; + +programmer + id = "pavr"; + desc = "Jason Kyle's pAVR Serial Programmer"; + type = "avr910"; + connection_type = serial; +; + +programmer + id = "pickit2"; + desc = "MicroChip's PICkit2 Programmer"; + type = "pickit2"; + connection_type = usb; +; + +programmer + id = "flip1"; + desc = "FLIP USB DFU protocol version 1 (doc7618)"; + type = "flip1"; + connection_type = usb; +; + +programmer + id = "flip2"; + desc = "FLIP USB DFU protocol version 2 (AVR4023)"; + type = "flip2"; + connection_type = usb; +; + + +#This programmer bitbangs GPIO lines using the Linux sysfs GPIO interface +# +#To enable it set the configuration below to match the GPIO lines connected to the +#relevant ISP header pins and uncomment the entry definition. In case you don't +#have the required permissions to edit this system wide config file put the +#entry in a separate .conf file and use it with -C+.conf +#on the command line. +# +#To check if your avrdude build has support for the linuxgpio programmer compiled in, +#use -c?type on the command line and look for linuxgpio in the list. If it's not available +#you need pass the --enable-linuxgpio=yes option to configure and recompile avrdude. +# +#programmer +# id = "linuxgpio"; +# desc = "Use the Linux sysfs interface to bitbang GPIO lines"; +# type = "linuxgpio"; +# reset = 4; +# NOTE: Serial does not appear compatible +# connection_type = serial; +# -# sck = ?; +# -# mosi = ?; +# -# miso = ?; +#; + + +#This programmer uses the built in linux SPI bus devices to program an +#attached AVR. A GPIO accessed through the sysfs GPIO interface needs to +#be specified for a reset pin since the linux SPI userspace functions do +#not allow for control over the slave select/chip select signal. +# +programmer + id = "linuxspi"; + desc = "Use Linux SPI device in /dev/spidev*"; + type = "linuxspi"; + reset = 25; +; + +# some ultra cheap programmers use bitbanging on the +# serialport. +# +# PC - DB9 - Pins for RS232: +# +# GND 5 -- |O +# | O| <- 9 RI +# DTR 4 <- |O | +# | O| <- 8 CTS +# TXD 3 <- |O | +# | O| -> 7 RTS +# RXD 2 -> |O | +# | O| <- 6 DSR +# DCD 1 -> |O +# +# Using RXD is currently not supported. +# Using RI is not supported under Win32 but is supported under Posix. + +# serial ponyprog design (dasa2 in uisp) +# reset=!txd sck=rts mosi=dtr miso=cts + +programmer + id = "ponyser"; + desc = "design ponyprog serial, reset=!txd sck=rts mosi=dtr miso=cts"; + type = "serbb"; + connection_type = serial; + reset = ~3; + sck = 7; + mosi = 4; + miso = 8; +; + +# Same as above, different name +# reset=!txd sck=rts mosi=dtr miso=cts + +programmer parent "ponyser" + id = "siprog"; + desc = "Lancos SI-Prog "; +; + +# unknown (dasa in uisp) +# reset=rts sck=dtr mosi=txd miso=cts + +programmer + id = "dasa"; + desc = "serial port banging, reset=rts sck=dtr mosi=txd miso=cts"; + type = "serbb"; + connection_type = serial; + reset = 7; + sck = 4; + mosi = 3; + miso = 8; +; + +# unknown (dasa3 in uisp) +# reset=!dtr sck=rts mosi=txd miso=cts + +programmer + id = "dasa3"; + desc = "serial port banging, reset=!dtr sck=rts mosi=txd miso=cts"; + type = "serbb"; + connection_type = serial; + reset = ~4; + sck = 7; + mosi = 3; + miso = 8; +; + +# C2N232i (jumper configuration "auto") +# reset=dtr sck=!rts mosi=!txd miso=!cts + +programmer + id = "c2n232i"; + desc = "serial port banging, reset=dtr sck=!rts mosi=!txd miso=!cts"; + type = "serbb"; + connection_type = serial; + reset = 4; + sck = ~7; + mosi = ~3; + miso = ~8; +; + +# +# PART DEFINITIONS +# + +#------------------------------------------------------------ +# ATtiny11 +#------------------------------------------------------------ + +# This is an HVSP-only device. + +part + id = "t11"; + desc = "ATtiny11"; + stk500_devcode = 0x11; + signature = 0x1e 0x90 0x04; + chip_erase_delay = 20000; + + timeout = 200; + hvsp_controlstack = + 0x4C, 0x0C, 0x1C, 0x2C, 0x3C, 0x64, 0x74, 0x00, + 0x68, 0x78, 0x68, 0x68, 0x00, 0x00, 0x68, 0x78, + 0x78, 0x00, 0x6D, 0x0C, 0x80, 0x40, 0x20, 0x10, + 0x11, 0x08, 0x04, 0x02, 0x03, 0x08, 0x04, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + hvspcmdexedelay = 0; + synchcycles = 6; + latchcycles = 1; + togglevtg = 1; + poweroffdelay = 25; + resetdelayms = 0; + resetdelayus = 50; + hvleavestabdelay = 100; + resetdelay = 25; + chiperasepolltimeout = 40; + chiperasetime = 0; + programfusepolltimeout = 25; + programlockpolltimeout = 25; + + memory "eeprom" + size = 64; + blocksize = 64; + readsize = 256; + delay = 5; + ; + + memory "flash" + size = 1024; + blocksize = 128; + readsize = 256; + delay = 3; + ; + + memory "signature" + size = 3; + ; + + memory "lock" + size = 1; + ; + + memory "calibration" + size = 1; + ; + + memory "fuse" + size = 1; + ; +; + +#------------------------------------------------------------ +# ATtiny12 +#------------------------------------------------------------ + +part + id = "t12"; + desc = "ATtiny12"; + stk500_devcode = 0x12; + avr910_devcode = 0x55; + signature = 0x1e 0x90 0x05; + chip_erase_delay = 20000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + hvsp_controlstack = + 0x4C, 0x0C, 0x1C, 0x2C, 0x3C, 0x64, 0x74, 0x00, + 0x68, 0x78, 0x68, 0x68, 0x00, 0x00, 0x68, 0x78, + 0x78, 0x00, 0x6D, 0x0C, 0x80, 0x40, 0x20, 0x10, + 0x11, 0x08, 0x04, 0x02, 0x03, 0x08, 0x04, 0x00; + hventerstabdelay = 100; + hvspcmdexedelay = 0; + synchcycles = 6; + latchcycles = 1; + togglevtg = 1; + poweroffdelay = 25; + resetdelayms = 0; + resetdelayus = 50; + hvleavestabdelay = 100; + resetdelay = 25; + chiperasepolltimeout = 40; + chiperasetime = 0; + programfusepolltimeout = 25; + programlockpolltimeout = 25; + + memory "eeprom" + size = 64; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 x x x x x x x x", + "x x a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 x x x x x x x x", + "x x a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + mode = 0x04; + delay = 8; + blocksize = 64; + readsize = 256; + ; + + memory "flash" + size = 1024; + min_write_delay = 4500; + max_write_delay = 20000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + write_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 5; + blocksize = 128; + readsize = 256; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x x x x x x o o x"; + + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 i i 1", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "fuse" + size = 1; + read = "0 1 0 1 0 0 0 0 x x x x x x x x", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 x x x x x", + "x x x x x x x x i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; +; + +#------------------------------------------------------------ +# ATtiny13 +#------------------------------------------------------------ + +part + id = "t13"; + desc = "ATtiny13"; + has_debugwire = yes; + flash_instr = 0xB4, 0x0E, 0x1E; + eeprom_instr = 0xBB, 0xFE, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBC, 0x0E, 0xB4, 0x0E, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; + stk500_devcode = 0x14; + signature = 0x1e 0x90 0x07; + chip_erase_delay = 4000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + hvsp_controlstack = + 0x4C, 0x0C, 0x1C, 0x2C, 0x3C, 0x64, 0x74, 0x66, + 0x68, 0x78, 0x68, 0x68, 0x7A, 0x6A, 0x68, 0x78, + 0x78, 0x7D, 0x6D, 0x0C, 0x80, 0x40, 0x20, 0x10, + 0x11, 0x08, 0x04, 0x02, 0x03, 0x08, 0x04, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + hvspcmdexedelay = 0; + synchcycles = 6; + latchcycles = 1; + togglevtg = 1; + poweroffdelay = 25; + resetdelayms = 0; + resetdelayus = 90; + hvleavestabdelay = 100; + resetdelay = 25; + chiperasepolltimeout = 40; + chiperasetime = 0; + programfusepolltimeout = 25; + programlockpolltimeout = 25; + + ocdrev = 0; + + memory "eeprom" + size = 64; + page_size = 4; + min_write_delay = 4000; + max_write_delay = 4000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 x x x x x", + "x x a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 x x x x x", + "x x a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " x x a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 5; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 1024; + page_size = 32; + num_pages = 32; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 0 0 0 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 0 0 0 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 0 0 0 a8", + " a7 a6 a5 a4 x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 32; + readsize = 256; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "lock" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "calibration" + size = 2; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 a0 o o o o o o o o"; + ; + + memory "lfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + ; + +; + + +#------------------------------------------------------------ +# ATtiny15 +#------------------------------------------------------------ + +part + id = "t15"; + desc = "ATtiny15"; + stk500_devcode = 0x13; + avr910_devcode = 0x56; + signature = 0x1e 0x90 0x06; + chip_erase_delay = 8200; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + hvsp_controlstack = + 0x4C, 0x0C, 0x1C, 0x2C, 0x3C, 0x64, 0x74, 0x00, + 0x68, 0x78, 0x68, 0x68, 0x00, 0x00, 0x68, 0x78, + 0x78, 0x00, 0x6D, 0x0C, 0x80, 0x40, 0x20, 0x10, + 0x11, 0x08, 0x04, 0x02, 0x03, 0x08, 0x04, 0x00; + hventerstabdelay = 100; + hvspcmdexedelay = 5; + synchcycles = 6; + latchcycles = 16; + togglevtg = 1; + poweroffdelay = 25; + resetdelayms = 0; + resetdelayus = 50; + hvleavestabdelay = 100; + resetdelay = 25; + chiperasepolltimeout = 40; + chiperasetime = 0; + programfusepolltimeout = 25; + programlockpolltimeout = 25; + + memory "eeprom" + size = 64; + min_write_delay = 8200; + max_write_delay = 8200; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 x x x x x x x x", + "x x a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 x x x x x x x x", + "x x a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + mode = 0x04; + delay = 10; + blocksize = 64; + readsize = 256; + ; + + memory "flash" + size = 1024; + min_write_delay = 4100; + max_write_delay = 4100; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + write_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 5; + blocksize = 128; + readsize = 256; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x x x x x x o o x"; + + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 i i 1", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "fuse" + size = 1; + read = "0 1 0 1 0 0 0 0 x x x x x x x x", + "x x x x x x x x o o o o x x o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 x x x x x", + "x x x x x x x x i i i i 1 1 i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; +; + +#------------------------------------------------------------ +# AT90s1200 +#------------------------------------------------------------ + +part + id = "1200"; + desc = "AT90S1200"; + is_at90s1200 = yes; + stk500_devcode = 0x33; + avr910_devcode = 0x13; + signature = 0x1e 0x90 0x01; + pagel = 0xd7; + bs2 = 0xa0; + chip_erase_delay = 20000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 1; + bytedelay = 0; + pollindex = 0; + pollvalue = 0xFF; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 15; + chiperasepolltimeout = 0; + programfusepulsewidth = 2; + programfusepolltimeout = 0; + programlockpulsewidth = 0; + programlockpolltimeout = 1; + + memory "eeprom" + size = 64; + min_write_delay = 4000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 x x x x x x x x", + "x x a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 x x x x x x x x", + "x x a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + mode = 0x04; + delay = 20; + blocksize = 32; + readsize = 256; + ; + memory "flash" + size = 1024; + min_write_delay = 4000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + write_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x02; + delay = 15; + blocksize = 128; + readsize = 256; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "fuse" + size = 1; + ; + memory "lock" + size = 1; + min_write_delay = 9000; + max_write_delay = 20000; + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 i i 1", + "x x x x x x x x x x x x x x x x"; + ; + ; + +#------------------------------------------------------------ +# AT90s4414 +#------------------------------------------------------------ + +part + id = "4414"; + desc = "AT90S4414"; + stk500_devcode = 0x50; + avr910_devcode = 0x28; + signature = 0x1e 0x92 0x01; + chip_erase_delay = 20000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 15; + chiperasepolltimeout = 0; + programfusepulsewidth = 2; + programfusepolltimeout = 0; + programlockpulsewidth = 0; + programlockpolltimeout = 1; + + memory "eeprom" + size = 256; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0x80; + readback_p2 = 0x7f; + read = " 1 0 1 0 0 0 0 0 x x x x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0 x x x x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 64; + readsize = 256; + ; + memory "flash" + size = 4096; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0x7f; + readback_p2 = 0x7f; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write_lo = " 0 1 0 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + write_hi = " 0 1 0 0 1 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 64; + readsize = 256; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "fuse" + size = 1; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 i i 1", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + ; + +#------------------------------------------------------------ +# AT90s2313 +#------------------------------------------------------------ + +part + id = "2313"; + desc = "AT90S2313"; + stk500_devcode = 0x40; + avr910_devcode = 0x20; + signature = 0x1e 0x91 0x01; + chip_erase_delay = 20000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 15; + chiperasepolltimeout = 0; + programfusepulsewidth = 2; + programfusepolltimeout = 0; + programlockpulsewidth = 0; + programlockpolltimeout = 1; + + memory "eeprom" + size = 128; + min_write_delay = 4000; + max_write_delay = 9000; + readback_p1 = 0x80; + readback_p2 = 0x7f; + read = "1 0 1 0 0 0 0 0 x x x x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 x x x x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 64; + readsize = 256; + ; + memory "flash" + size = 2048; + min_write_delay = 4000; + max_write_delay = 9000; + readback_p1 = 0x7f; + readback_p2 = 0x7f; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + write_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 128; + readsize = 256; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "fuse" + size = 1; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 x x i i x", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + ; + +#------------------------------------------------------------ +# AT90s2333 +#------------------------------------------------------------ + +part + id = "2333"; +##### WARNING: No XML file for device 'AT90S2333'! ##### + desc = "AT90S2333"; + stk500_devcode = 0x42; + avr910_devcode = 0x34; + signature = 0x1e 0x91 0x05; + chip_erase_delay = 20000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 15; + chiperasepolltimeout = 0; + programfusepulsewidth = 2; + programfusepolltimeout = 0; + programlockpulsewidth = 0; + programlockpolltimeout = 1; + + memory "eeprom" + size = 128; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0x00; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 x x x x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 x x x x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 128; + readsize = 256; + ; + + memory "flash" + size = 2048; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + write_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 128; + readsize = 256; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "fuse" + size = 1; + min_write_delay = 9000; + max_write_delay = 20000; + pwroff_after_write = yes; + read = "0 1 0 1 0 0 0 0 x x x x x x x x", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 i i i i i", + "x x x x x x x x x x x x x x x x"; + ; + memory "lock" + size = 1; + min_write_delay = 9000; + max_write_delay = 20000; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x x x x x x o o x"; + + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 i i 1", + "x x x x x x x x x x x x x x x x"; + ; + ; + + +#------------------------------------------------------------ +# AT90s2343 (also AT90s2323 and ATtiny22) +#------------------------------------------------------------ + +part + id = "2343"; + desc = "AT90S2343"; + stk500_devcode = 0x43; + avr910_devcode = 0x4c; + signature = 0x1e 0x91 0x03; + chip_erase_delay = 18000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + hvsp_controlstack = + 0x4C, 0x0C, 0x1C, 0x2C, 0x3C, 0x64, 0x74, 0x00, + 0x68, 0x78, 0x68, 0x68, 0x00, 0x00, 0x68, 0x78, + 0x78, 0x00, 0x6D, 0x0C, 0x80, 0x40, 0x20, 0x10, + 0x11, 0x08, 0x04, 0x02, 0x03, 0x08, 0x04, 0x00; + hventerstabdelay = 100; + hvspcmdexedelay = 0; + synchcycles = 6; + latchcycles = 1; + togglevtg = 0; + poweroffdelay = 25; + resetdelayms = 0; + resetdelayus = 50; + hvleavestabdelay = 100; + resetdelay = 25; + chiperasepolltimeout = 40; + chiperasetime = 0; + programfusepolltimeout = 25; + programlockpolltimeout = 25; + + memory "eeprom" + size = 128; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0x00; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0", + "x a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0", + "x a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 64; + readsize = 256; + ; + memory "flash" + size = 2048; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + write_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 128; + readsize = 128; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "fuse" + size = 1; + min_write_delay = 9000; + max_write_delay = 20000; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x o o o x x x x o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 1 1 1 1 i", + "x x x x x x x x x x x x x x x x"; + ; + memory "lock" + size = 1; + min_write_delay = 9000; + max_write_delay = 20000; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x o o o x x x x o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 i i 1", + "x x x x x x x x x x x x x x x x"; + ; + ; + + +#------------------------------------------------------------ +# AT90s4433 +#------------------------------------------------------------ + +part + id = "4433"; + desc = "AT90S4433"; + stk500_devcode = 0x51; + avr910_devcode = 0x30; + signature = 0x1e 0x92 0x03; + chip_erase_delay = 20000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 15; + chiperasepolltimeout = 0; + programfusepulsewidth = 2; + programfusepolltimeout = 0; + programlockpulsewidth = 0; + programlockpolltimeout = 1; + + memory "eeprom" + size = 256; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0x00; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0 x x x x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0 x x x x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 128; + readsize = 256; + ; + memory "flash" + size = 4096; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write_lo = " 0 1 0 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + write_hi = " 0 1 0 0 1 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 128; + readsize = 256; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "fuse" + size = 1; + min_write_delay = 9000; + max_write_delay = 20000; + pwroff_after_write = yes; + read = "0 1 0 1 0 0 0 0 x x x x x x x x", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 i i i i i", + "x x x x x x x x x x x x x x x x"; + ; + memory "lock" + size = 1; + min_write_delay = 9000; + max_write_delay = 20000; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x x x x x x o o x"; + + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 i i 1", + "x x x x x x x x x x x x x x x x"; + ; + ; + +#------------------------------------------------------------ +# AT90s4434 +#------------------------------------------------------------ + +part + id = "4434"; +##### WARNING: No XML file for device 'AT90S4434'! ##### + desc = "AT90S4434"; + stk500_devcode = 0x52; + avr910_devcode = 0x6c; + signature = 0x1e 0x92 0x02; + chip_erase_delay = 20000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + memory "eeprom" + size = 256; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0x00; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0 x x x x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0 x x x x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + ; + memory "flash" + size = 4096; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write_lo = " 0 1 0 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + write_hi = " 0 1 0 0 1 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "fuse" + size = 1; + min_write_delay = 9000; + max_write_delay = 20000; + read = "0 1 0 1 0 0 0 0 x x x x x x x x", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 i i i i i", + "x x x x x x x x x x x x x x x x"; + ; + memory "lock" + size = 1; + min_write_delay = 9000; + max_write_delay = 20000; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x x x x x x o o x"; + + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 i i 1", + "x x x x x x x x x x x x x x x x"; + ; + ; + +#------------------------------------------------------------ +# AT90s8515 +#------------------------------------------------------------ + +part + id = "8515"; + desc = "AT90S8515"; + stk500_devcode = 0x60; + avr910_devcode = 0x38; + signature = 0x1e 0x93 0x01; + chip_erase_delay = 20000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + resetdelay = 15; + chiperasepulsewidth = 15; + chiperasepolltimeout = 0; + programfusepulsewidth = 2; + programfusepolltimeout = 0; + programlockpulsewidth = 0; + programlockpolltimeout = 1; + + memory "eeprom" + size = 512; + min_write_delay = 4000; + max_write_delay = 9000; + readback_p1 = 0x80; + readback_p2 = 0x7f; + read = " 1 0 1 0 0 0 0 0 x x x x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0 x x x x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 128; + readsize = 256; + ; + memory "flash" + size = 8192; + min_write_delay = 4000; + max_write_delay = 9000; + readback_p1 = 0x7f; + readback_p2 = 0x7f; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write_lo = " 0 1 0 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + write_hi = " 0 1 0 0 1 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 128; + readsize = 256; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "fuse" + size = 1; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 i i 1", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + ; + +#------------------------------------------------------------ +# AT90s8535 +#------------------------------------------------------------ + +part + id = "8535"; + desc = "AT90S8535"; + stk500_devcode = 0x61; + avr910_devcode = 0x68; + signature = 0x1e 0x93 0x03; + chip_erase_delay = 20000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 15; + chiperasepolltimeout = 0; + programfusepulsewidth = 2; + programfusepolltimeout = 0; + programlockpulsewidth = 0; + programlockpolltimeout = 1; + + memory "eeprom" + size = 512; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0x00; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0 x x x x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0 x x x x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 128; + readsize = 256; + ; + memory "flash" + size = 8192; + min_write_delay = 9000; + max_write_delay = 20000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write_lo = " 0 1 0 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + write_hi = " 0 1 0 0 1 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 128; + readsize = 256; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "fuse" + size = 1; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x x x x x x x x o"; + write = "1 0 1 0 1 1 0 0 1 0 1 1 1 1 1 i", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x o o x x x x x x"; + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 i i 1", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + ; + +#------------------------------------------------------------ +# ATmega103 +#------------------------------------------------------------ + +part + id = "m103"; + desc = "ATmega103"; + stk500_devcode = 0xB1; + avr910_devcode = 0x41; + signature = 0x1e 0x97 0x01; + chip_erase_delay = 112000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x8E, 0x9E, 0x2E, 0x3E, 0xAE, 0xBE, + 0x4E, 0x5E, 0xCE, 0xDE, 0x6E, 0x7E, 0xEE, 0xDE, + 0x66, 0x76, 0xE6, 0xF6, 0x6A, 0x7A, 0xEA, 0x7A, + 0x7F, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 15; + chiperasepolltimeout = 0; + programfusepulsewidth = 2; + programfusepolltimeout = 0; + programlockpulsewidth = 0; + programlockpolltimeout = 10; + + memory "eeprom" + size = 4096; + min_write_delay = 4000; + max_write_delay = 9000; + readback_p1 = 0x80; + readback_p2 = 0x7f; + read = " 1 0 1 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 64; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 131072; + page_size = 256; + num_pages = 512; + min_write_delay = 22000; + max_write_delay = 56000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x11; + delay = 70; + blocksize = 256; + readsize = 256; + ; + + memory "fuse" + size = 1; + read = "0 1 0 1 0 0 0 0 x x x x x x x x", + "x x x x x x x x x x o x o 1 o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 1 i 1 i i", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x x x x x x o o x"; + + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 i i 1", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + + +#------------------------------------------------------------ +# ATmega64 +#------------------------------------------------------------ + +part + id = "m64"; + desc = "ATmega64"; + has_jtag = yes; + stk500_devcode = 0xA0; + avr910_devcode = 0x45; + signature = 0x1e 0x96 0x02; + chip_erase_delay = 9000; + pagel = 0xD7; + bs2 = 0xA0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x22; + spmcr = 0x68; + allowfullpagebitstream = yes; + + ocdrev = 2; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 2048; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 20; + blocksize = 64; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 65536; + page_size = 256; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " x a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 6; + blocksize = 128; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 4; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + + + + +#------------------------------------------------------------ +# ATmega128 +#------------------------------------------------------------ + +part + id = "m128"; + desc = "ATmega128"; + has_jtag = yes; + stk500_devcode = 0xB2; + avr910_devcode = 0x43; + signature = 0x1e 0x97 0x02; + chip_erase_delay = 9000; + pagel = 0xD7; + bs2 = 0xA0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x22; + spmcr = 0x68; + rampz = 0x3b; + allowfullpagebitstream = yes; + + ocdrev = 1; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 4096; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 12; + blocksize = 64; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 131072; + page_size = 256; + num_pages = 512; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 6; + blocksize = 128; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 4; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# AT90CAN128 +#------------------------------------------------------------ + +part + id = "c128"; + desc = "AT90CAN128"; + has_jtag = yes; + stk500_devcode = 0xB3; +# avr910_devcode = 0x43; + signature = 0x1e 0x97 0x81; + chip_erase_delay = 9000; + pagel = 0xD7; + bs2 = 0xA0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + rampz = 0x3b; + eecr = 0x3f; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 4096; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + + mode = 0x41; + delay = 20; + blocksize = 8; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 131072; + page_size = 256; + num_pages = 512; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 256; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# AT90CAN64 +#------------------------------------------------------------ + +part + id = "c64"; + desc = "AT90CAN64"; + has_jtag = yes; + stk500_devcode = 0xB3; +# avr910_devcode = 0x43; + signature = 0x1e 0x96 0x81; + chip_erase_delay = 9000; + pagel = 0xD7; + bs2 = 0xA0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + rampz = 0x3b; + eecr = 0x3f; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 2048; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + + mode = 0x41; + delay = 20; + blocksize = 8; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 65536; + page_size = 256; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 256; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# AT90CAN32 +#------------------------------------------------------------ + +part + id = "c32"; + desc = "AT90CAN32"; + has_jtag = yes; + stk500_devcode = 0xB3; +# avr910_devcode = 0x43; + signature = 0x1e 0x95 0x81; + chip_erase_delay = 9000; + pagel = 0xD7; + bs2 = 0xA0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + rampz = 0x3b; + eecr = 0x3f; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 1024; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + + mode = 0x41; + delay = 20; + blocksize = 8; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 32768; + page_size = 256; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 256; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + + +#------------------------------------------------------------ +# ATmega16 +#------------------------------------------------------------ + +part + id = "m16"; + desc = "ATmega16"; + has_jtag = yes; + stk500_devcode = 0x82; + avr910_devcode = 0x74; + signature = 0x1e 0x94 0x03; + pagel = 0xd7; + bs2 = 0xa0; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 100; + latchcycles = 6; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + resetdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + allowfullpagebitstream = yes; + + ocdrev = 2; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 512; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x04; + delay = 10; + blocksize = 128; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 16384; + page_size = 128; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 6; + blocksize = 128; + readsize = 256; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "calibration" + size = 4; + + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + ; + + +#------------------------------------------------------------ +# ATmega164P +#------------------------------------------------------------ + +# close to ATmega16 + +part parent "m16" + id = "m164p"; + desc = "ATmega164P"; + signature = 0x1e 0x94 0x0a; + + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + allowfullpagebitstream = no; + chip_erase_delay = 55000; + + ocdrev = 3; + ; + + +#------------------------------------------------------------ +# ATmega324P +#------------------------------------------------------------ + +# similar to ATmega164P + +part + id = "m324p"; + desc = "ATmega324P"; + has_jtag = yes; + stk500_devcode = 0x82; # no STK500v1 support, use the ATmega16 one + avr910_devcode = 0x74; + signature = 0x1e 0x95 0x08; + pagel = 0xd7; + bs2 = 0xa0; + chip_erase_delay = 55000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 1024; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 128; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 32768; + page_size = 128; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 6; + blocksize = 256; + readsize = 256; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x 1 1 1 1 1 i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 1; + + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + ; + + +#------------------------------------------------------------ +# ATmega324PA +#------------------------------------------------------------ + +# similar to ATmega324P + +part parent "m324p" + id = "m324pa"; + desc = "ATmega324PA"; + signature = 0x1e 0x95 0x11; + + ocdrev = 3; + ; + + +#------------------------------------------------------------ +# ATmega644 +#------------------------------------------------------------ + +# similar to ATmega164 + +part + id = "m644"; + desc = "ATmega644"; + has_jtag = yes; + stk500_devcode = 0x82; # no STK500v1 support, use the ATmega16 one + avr910_devcode = 0x74; + signature = 0x1e 0x96 0x09; + pagel = 0xd7; + bs2 = 0xa0; + chip_erase_delay = 55000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 2048; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 128; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 65536; + page_size = 256; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 6; + blocksize = 256; + readsize = 256; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x 1 1 1 1 1 i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 1; + + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega644P +#------------------------------------------------------------ + +# similar to ATmega164p + +part parent "m644" + id = "m644p"; + desc = "ATmega644P"; + signature = 0x1e 0x96 0x0a; + + ocdrev = 3; + ; + + + +#------------------------------------------------------------ +# ATmega1284 +#------------------------------------------------------------ + +# similar to ATmega164 + +part + id = "m1284"; + desc = "ATmega1284"; + has_jtag = yes; + stk500_devcode = 0x82; # no STK500v1 support, use the ATmega16 one + avr910_devcode = 0x74; + signature = 0x1e 0x97 0x06; + pagel = 0xd7; + bs2 = 0xa0; + chip_erase_delay = 55000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 4096; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 128; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 131072; + page_size = 256; + num_pages = 512; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 256; + readsize = 256; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x 1 1 1 1 1 i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 1; + + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + ; + + + +#------------------------------------------------------------ +# ATmega1284P +#------------------------------------------------------------ + +# similar to ATmega164p + +part + id = "m1284p"; + desc = "ATmega1284P"; + has_jtag = yes; + stk500_devcode = 0x82; # no STK500v1 support, use the ATmega16 one + avr910_devcode = 0x74; + signature = 0x1e 0x97 0x05; + pagel = 0xd7; + bs2 = 0xa0; + chip_erase_delay = 55000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 4096; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 128; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 131072; + page_size = 256; + num_pages = 512; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 256; + readsize = 256; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x 1 1 1 1 1 i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 1; + + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + ; + + + +#------------------------------------------------------------ +# ATmega162 +#------------------------------------------------------------ + +part + id = "m162"; + desc = "ATmega162"; + has_jtag = yes; + stk500_devcode = 0x83; + avr910_devcode = 0x63; + signature = 0x1e 0x94 0x04; + chip_erase_delay = 9000; + pagel = 0xd7; + bs2 = 0xa0; + + idr = 0x04; + spmcr = 0x57; + allowfullpagebitstream = yes; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + ocdrev = 2; + + memory "flash" + paged = yes; + size = 16384; + page_size = 128; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + mode = 0x41; + delay = 10; + blocksize = 128; + readsize = 256; + + ; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 512; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + + read = " 1 0 1 0 0 0 0 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 16000; + max_write_delay = 16000; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 16000; + max_write_delay = 16000; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "efuse" + size = 1; + min_write_delay = 16000; + max_write_delay = 16000; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x 1 1 1 1 1 i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 16000; + max_write_delay = 16000; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "signature" + size = 3; + + read = "0 0 1 1 0 0 0 0 0 0 x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 1; + + read = "0 0 1 1 1 0 0 0 0 0 x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; +; + + + +#------------------------------------------------------------ +# ATmega163 +#------------------------------------------------------------ + +part + id = "m163"; + desc = "ATmega163"; + stk500_devcode = 0x81; + avr910_devcode = 0x64; + signature = 0x1e 0x94 0x02; + chip_erase_delay = 32000; + pagel = 0xd7; + bs2 = 0xa0; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 30; + programfusepulsewidth = 0; + programfusepolltimeout = 2; + programlockpulsewidth = 0; + programlockpolltimeout = 2; + + + memory "eeprom" + size = 512; + min_write_delay = 4000; + max_write_delay = 4000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 16384; + page_size = 128; + num_pages = 128; + min_write_delay = 16000; + max_write_delay = 16000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x11; + delay = 20; + blocksize = 128; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o x x o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i 1 1 i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x x x x x 1 o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x 1 1 1 1 1 i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x 0 x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega169 +#------------------------------------------------------------ + +part + id = "m169"; + desc = "ATmega169"; + has_jtag = yes; + stk500_devcode = 0x85; + avr910_devcode = 0x78; + signature = 0x1e 0x94 0x05; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + + ocdrev = 2; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 512; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 16384; + page_size = 128; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 128; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + ; + + memory "lock" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega329 +#------------------------------------------------------------ + +part + id = "m329"; + desc = "ATmega329"; + has_jtag = yes; +# stk500_devcode = 0x85; # no STK500 support, only STK500v2 +# avr910_devcode = 0x?; # try the ATmega169 one: + avr910_devcode = 0x75; + signature = 0x1e 0x95 0x03; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 1024; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 8; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 32768; + page_size = 128; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 256; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "efuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega329P +#------------------------------------------------------------ +# Identical to ATmega329 except of the signature + +part parent "m329" + id = "m329p"; + desc = "ATmega329P"; + signature = 0x1e 0x95 0x0b; + + ocdrev = 3; + ; + +#------------------------------------------------------------ +# ATmega3290 +#------------------------------------------------------------ + +# identical to ATmega329 + +part parent "m329" + id = "m3290"; + desc = "ATmega3290"; + signature = 0x1e 0x95 0x04; + + ocdrev = 3; + ; + +#------------------------------------------------------------ +# ATmega3290P +#------------------------------------------------------------ + +# identical to ATmega3290 except of the signature + +part parent "m3290" + id = "m3290p"; + desc = "ATmega3290P"; + signature = 0x1e 0x95 0x0c; + + ocdrev = 3; + ; + +#------------------------------------------------------------ +# ATmega649 +#------------------------------------------------------------ + +part + id = "m649"; + desc = "ATmega649"; + has_jtag = yes; +# stk500_devcode = 0x85; # no STK500 support, only STK500v2 +# avr910_devcode = 0x?; # try the ATmega169 one: + avr910_devcode = 0x75; + signature = 0x1e 0x96 0x03; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 2048; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 8; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 65536; + page_size = 256; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 256; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "efuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega6490 +#------------------------------------------------------------ + +# identical to ATmega649 + +part parent "m649" + id = "m6490"; + desc = "ATmega6490"; + signature = 0x1e 0x96 0x04; + + ocdrev = 3; + ; + +#------------------------------------------------------------ +# ATmega32 +#------------------------------------------------------------ + +part + id = "m32"; + desc = "ATmega32"; + has_jtag = yes; + stk500_devcode = 0x91; + avr910_devcode = 0x72; + signature = 0x1e 0x95 0x02; + chip_erase_delay = 9000; + pagel = 0xd7; + bs2 = 0xa0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + allowfullpagebitstream = yes; + + ocdrev = 2; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 1024; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x04; + delay = 10; + blocksize = 64; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 32768; + page_size = 128; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 6; + blocksize = 64; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 4; + read = "0 0 1 1 1 0 0 0 0 0 x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega161 +#------------------------------------------------------------ + +part + id = "m161"; + desc = "ATmega161"; + stk500_devcode = 0x80; + avr910_devcode = 0x60; + signature = 0x1e 0x94 0x01; + chip_erase_delay = 28000; + pagel = 0xd7; + bs2 = 0xa0; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 30; + programfusepulsewidth = 0; + programfusepolltimeout = 2; + programlockpulsewidth = 0; + programlockpolltimeout = 2; + + memory "eeprom" + size = 512; + min_write_delay = 3400; + max_write_delay = 3400; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 5; + blocksize = 128; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 16384; + page_size = 128; + num_pages = 128; + min_write_delay = 14000; + max_write_delay = 14000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 16; + blocksize = 128; + readsize = 256; + ; + + memory "fuse" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 0 0 0 0 x x x x x x x x", + "x x x x x x x x x o x o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 x x x x x", + "x x x x x x x x 1 i 1 i i i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + + +#------------------------------------------------------------ +# ATmega8 +#------------------------------------------------------------ + +part + id = "m8"; + desc = "ATmega8"; + stk500_devcode = 0x70; + avr910_devcode = 0x76; + signature = 0x1e 0x93 0x07; + pagel = 0xd7; + bs2 = 0xc2; + chip_erase_delay = 10000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 2; + resetdelayus = 0; + hvleavestabdelay = 15; + resetdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + memory "eeprom" + size = 512; + page_size = 4; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 20; + blocksize = 128; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 8192; + page_size = 64; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 0 x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 0 x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 10; + blocksize = 64; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + # Required for Arduino IDE + # see: https://github.com/arduino/Arduino/issues/2075 + # https://github.com/arduino/Arduino/issues/2075#issuecomment-238031689 + memory "efuse" + size = 0; + ; + + memory "lock" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "calibration" + size = 4; + read = "0 0 1 1 1 0 0 0 0 0 x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + + + +#------------------------------------------------------------ +# ATmega8515 +#------------------------------------------------------------ + +part + id = "m8515"; + desc = "ATmega8515"; + stk500_devcode = 0x63; + avr910_devcode = 0x3A; + signature = 0x1e 0x93 0x06; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + memory "eeprom" + size = 512; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 20; + blocksize = 128; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 8192; + page_size = 64; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 0 x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 0 x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 6; + blocksize = 64; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "calibration" + size = 4; + read = "0 0 1 1 1 0 0 0 0 0 x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + + + + +#------------------------------------------------------------ +# ATmega8535 +#------------------------------------------------------------ + +part + id = "m8535"; + desc = "ATmega8535"; + stk500_devcode = 0x64; + avr910_devcode = 0x69; + signature = 0x1e 0x93 0x08; + pagel = 0xd7; + bs2 = 0xa0; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 6; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + memory "eeprom" + size = 512; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + mode = 0x04; + delay = 20; + blocksize = 128; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 8192; + page_size = 64; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 0 x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 0 x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 6; + blocksize = 64; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 2000; + max_write_delay = 2000; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "calibration" + size = 4; + read = "0 0 1 1 1 0 0 0 0 0 x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + + +#------------------------------------------------------------ +# ATtiny26 +#------------------------------------------------------------ + +part + id = "t26"; + desc = "ATtiny26"; + stk500_devcode = 0x21; + avr910_devcode = 0x5e; + signature = 0x1e 0x91 0x09; + pagel = 0xb3; + bs2 = 0xb2; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0xC4, 0xE4, 0xC4, 0xE4, 0xCC, 0xEC, 0xCC, 0xEC, + 0xD4, 0xF4, 0xD4, 0xF4, 0xDC, 0xFC, 0xDC, 0xFC, + 0xC8, 0xE8, 0xD8, 0xF8, 0x4C, 0x6C, 0x5C, 0x7C, + 0xEC, 0xBC, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 2; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + memory "eeprom" + size = 128; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 x x x x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 x x x x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + mode = 0x04; + delay = 10; + blocksize = 64; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 2048; + page_size = 32; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 6; + blocksize = 16; + readsize = 256; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x x x x x x x o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 1 i i", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x x x x i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 4; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + +; + + +#------------------------------------------------------------ +# ATtiny261 +#------------------------------------------------------------ +# Close to ATtiny26 + +part + id = "t261"; + desc = "ATtiny261"; + has_debugwire = yes; + flash_instr = 0xB4, 0x00, 0x10; + eeprom_instr = 0xBB, 0xFF, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBC, 0x00, 0xB4, 0x00, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; +# stk500_devcode = 0x21; +# avr910_devcode = 0x5e; + signature = 0x1e 0x91 0x0c; + pagel = 0xb3; + bs2 = 0xb2; + chip_erase_delay = 4000; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0xC4, 0xE4, 0xC4, 0xE4, 0xCC, 0xEC, 0xCC, 0xEC, + 0xD4, 0xF4, 0xD4, 0xF4, 0xDC, 0xFC, 0xDC, 0xFC, + 0xC8, 0xE8, 0xD8, 0xF8, 0x4C, 0x6C, 0x5C, 0x7C, + 0xEC, 0xBC, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 2; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; + size = 128; + page_size = 4; + num_pages = 32; + min_write_delay = 4000; + max_write_delay = 4000; + readback_p1 = 0xff; + readback_p2 = 0xff; + + read = "1 0 1 0 0 0 0 0 x x x x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 x x x x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 2048; + page_size = 32; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " x x x x x x a9 a8", + " a7 a6 a5 a4 x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 32; + readsize = 256; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x x x x x x x o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 1 i i", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + +; + + +#------------------------------------------------------------ +# ATtiny461 +#------------------------------------------------------------ +# Close to ATtiny261 + +part + id = "t461"; + desc = "ATtiny461"; + has_debugwire = yes; + flash_instr = 0xB4, 0x00, 0x10; + eeprom_instr = 0xBB, 0xFF, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBC, 0x00, 0xB4, 0x00, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; +# stk500_devcode = 0x21; +# avr910_devcode = 0x5e; + signature = 0x1e 0x92 0x08; + pagel = 0xb3; + bs2 = 0xb2; + chip_erase_delay = 4000; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0xC4, 0xE4, 0xC4, 0xE4, 0xCC, 0xEC, 0xCC, 0xEC, + 0xD4, 0xF4, 0xD4, 0xF4, 0xDC, 0xFC, 0xDC, 0xFC, + 0xC8, 0xE8, 0xD8, 0xF8, 0x4C, 0x6C, 0x5C, 0x7C, + 0xEC, 0xBC, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 2; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; + size = 256; + page_size = 4; + num_pages = 64; + min_write_delay = 4000; + max_write_delay = 4000; + readback_p1 = 0xff; + readback_p2 = 0xff; + + read = " 1 0 1 0 0 0 0 0 x x x x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0 x x x x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 4096; + page_size = 64; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 64; + readsize = 256; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x x x x x x x o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 1 i i", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + +; + + +#------------------------------------------------------------ +# ATtiny861 +#------------------------------------------------------------ +# Close to ATtiny461 + +part + id = "t861"; + desc = "ATtiny861"; + has_debugwire = yes; + flash_instr = 0xB4, 0x00, 0x10; + eeprom_instr = 0xBB, 0xFF, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBC, 0x00, 0xB4, 0x00, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; +# stk500_devcode = 0x21; +# avr910_devcode = 0x5e; + signature = 0x1e 0x93 0x0d; + pagel = 0xb3; + bs2 = 0xb2; + chip_erase_delay = 4000; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 0; + + pp_controlstack = + 0xC4, 0xE4, 0xC4, 0xE4, 0xCC, 0xEC, 0xCC, 0xEC, + 0xD4, 0xF4, 0xD4, 0xF4, 0xDC, 0xFC, 0xDC, 0xFC, + 0xC8, 0xE8, 0xD8, 0xF8, 0x4C, 0x6C, 0x5C, 0x7C, + 0xEC, 0xBC, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 2; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; + size = 512; + num_pages = 128; + page_size = 4; + min_write_delay = 4000; + max_write_delay = 4000; + readback_p1 = 0xff; + readback_p2 = 0xff; + + read = " 1 0 1 0 0 0 0 0 x x x x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0 x x x x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 8192; + page_size = 64; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + + read_lo = " 0 0 1 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 64; + readsize = 256; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 x x x x x x x x", + "x x x x x x x x x x x x x x o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 1 1 1 i i", + "x x x x x x x x x x x x x x x x"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + +; + + +#------------------------------------------------------------ +# ATtiny28 +#------------------------------------------------------------ + +# This is an HVPP-only device. + +part + id = "t28"; + desc = "ATtiny28"; + stk500_devcode = 0x22; + avr910_devcode = 0x5c; + signature = 0x1e 0x91 0x07; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 0; + poweroffdelay = 0; + resetdelayms = 0; + resetdelayus = 0; + hvleavestabdelay = 15; + resetdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + memory "flash" + size = 2048; + page_size = 2; + readsize = 256; + delay = 5; + ; + + memory "signature" + size = 3; + ; + + memory "lock" + size = 1; + ; + + memory "calibration" + size = 1; + ; + + memory "fuse" + size = 1; + ; +; + + + +#------------------------------------------------------------ +# ATmega48 +#------------------------------------------------------------ + +part + id = "m48"; + desc = "ATmega48"; + has_debugwire = yes; + flash_instr = 0xB6, 0x01, 0x11; + eeprom_instr = 0xBD, 0xF2, 0xBD, 0xE1, 0xBB, 0xCF, 0xB4, 0x00, + 0xBE, 0x01, 0xB6, 0x01, 0xBC, 0x00, 0xBB, 0xBF, + 0x99, 0xF9, 0xBB, 0xAF; + stk500_devcode = 0x59; +# avr910_devcode = 0x; + signature = 0x1e 0x92 0x05; + pagel = 0xd7; + bs2 = 0xc2; + chip_erase_delay = 45000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + resetdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; + page_size = 4; + size = 256; + min_write_delay = 3600; + max_write_delay = 3600; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 x x x x x", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 4096; + page_size = 64; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 64; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "efuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + ; + + memory "lock" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega48P +#------------------------------------------------------------ + +part parent "m48" + id = "m48p"; + desc = "ATmega48P"; + signature = 0x1e 0x92 0x0a; + + ocdrev = 1; + ; + +#------------------------------------------------------------ +# ATmega48PB +#------------------------------------------------------------ + +part parent "m48" + id = "m48pb"; + desc = "ATmega48PB"; + signature = 0x1e 0x92 0x10; + + ocdrev = 1; + ; + +#------------------------------------------------------------ +# ATmega88 +#------------------------------------------------------------ + +part + id = "m88"; + desc = "ATmega88"; + has_debugwire = yes; + flash_instr = 0xB6, 0x01, 0x11; + eeprom_instr = 0xBD, 0xF2, 0xBD, 0xE1, 0xBB, 0xCF, 0xB4, 0x00, + 0xBE, 0x01, 0xB6, 0x01, 0xBC, 0x00, 0xBB, 0xBF, + 0x99, 0xF9, 0xBB, 0xAF; + stk500_devcode = 0x73; +# avr910_devcode = 0x; + signature = 0x1e 0x93 0x0a; + pagel = 0xd7; + bs2 = 0xc2; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + resetdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; + page_size = 4; + size = 512; + min_write_delay = 3600; + max_write_delay = 3600; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 8192; + page_size = 64; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 64; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "efuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega88P +#------------------------------------------------------------ + +part parent "m88" + id = "m88p"; + desc = "ATmega88P"; + signature = 0x1e 0x93 0x0f; + + ocdrev = 1; + ; + +#------------------------------------------------------------ +# ATmega88PB +#------------------------------------------------------------ + +part parent "m88" + id = "m88pb"; + desc = "ATmega88PB"; + signature = 0x1e 0x93 0x16; + + ocdrev = 1; + ; + +#------------------------------------------------------------ +# ATmega168 +#------------------------------------------------------------ + +part + id = "m168"; + desc = "ATmega168"; + has_debugwire = yes; + flash_instr = 0xB6, 0x01, 0x11; + eeprom_instr = 0xBD, 0xF2, 0xBD, 0xE1, 0xBB, 0xCF, 0xB4, 0x00, + 0xBE, 0x01, 0xB6, 0x01, 0xBC, 0x00, 0xBB, 0xBF, + 0x99, 0xF9, 0xBB, 0xAF; + stk500_devcode = 0x86; + # avr910_devcode = 0x; + signature = 0x1e 0x94 0x06; + pagel = 0xd7; + bs2 = 0xc2; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + resetdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; + page_size = 4; + size = 512; + min_write_delay = 3600; + max_write_delay = 3600; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 16384; + page_size = 128; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 128; + readsize = 256; + + ; + + memory "lfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "efuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; +; + +#------------------------------------------------------------ +# ATmega168P +#------------------------------------------------------------ + +part parent "m168" + id = "m168p"; + desc = "ATmega168P"; + signature = 0x1e 0x94 0x0b; + + ocdrev = 1; +; + +#------------------------------------------------------------ +# ATmega168PB +#------------------------------------------------------------ + +part parent "m168" + id = "m168pb"; + desc = "ATmega168PB"; + signature = 0x1e 0x94 0x15; + + ocdrev = 1; +; + +#------------------------------------------------------------ +# ATtiny88 +#------------------------------------------------------------ + +part + id = "t88"; + desc = "ATtiny88"; + has_debugwire = yes; + flash_instr = 0xB6, 0x01, 0x11; + eeprom_instr = 0xBD, 0xF2, 0xBD, 0xE1, 0xBB, 0xCF, 0xB4, 0x00, + 0xBE, 0x01, 0xB6, 0x01, 0xBC, 0x00, 0xBB, 0xBF, + 0x99, 0xF9, 0xBB, 0xAF; + stk500_devcode = 0x73; +# avr910_devcode = 0x; + signature = 0x1e 0x93 0x11; + pagel = 0xd7; + bs2 = 0xc2; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + resetdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; + page_size = 4; + size = 64; + min_write_delay = 3600; + max_write_delay = 3600; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 64; + ; + memory "flash" + paged = yes; + size = 8192; + page_size = 64; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 64; + readsize = 256; + ; + + memory "lfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "efuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + ; + + memory "lock" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega328 +#------------------------------------------------------------ + +part + id = "m328"; + desc = "ATmega328"; + has_debugwire = yes; + flash_instr = 0xB6, 0x01, 0x11; + eeprom_instr = 0xBD, 0xF2, 0xBD, 0xE1, 0xBB, 0xCF, 0xB4, 0x00, + 0xBE, 0x01, 0xB6, 0x01, 0xBC, 0x00, 0xBB, 0xBF, + 0x99, 0xF9, 0xBB, 0xAF; + stk500_devcode = 0x86; + # avr910_devcode = 0x; + signature = 0x1e 0x95 0x14; + pagel = 0xd7; + bs2 = 0xc2; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + resetdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; + page_size = 4; + size = 1024; + min_write_delay = 3600; + max_write_delay = 3600; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 x x x a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 32768; + page_size = 128; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 128; + readsize = 256; + + ; + + memory "lfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "efuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; +; + +part parent "m328" + id = "m328p"; + desc = "ATmega328P"; + signature = 0x1e 0x95 0x0F; + + ocdrev = 1; +; + +part parent "m328" + id = "m328pb"; + desc = "ATmega328PB"; + signature = 0x1e 0x95 0x16; + + ocdrev = 1; +; + +#------------------------------------------------------------ +# ATmega32m1 +#------------------------------------------------------------ + +part parent "m328" + id = "m32m1"; + desc = "ATmega32M1"; + # stk500_devcode = 0x; + # avr910_devcode = 0x; + signature = 0x1e 0x95 0x84; + bs2 = 0xe2; + + memory "efuse" + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x i i i i i i"; + ; +; + +#------------------------------------------------------------ +# ATmega64m1 +#------------------------------------------------------------ + +part parent "m328" + id = "m64m1"; + desc = "ATmega64M1"; + # stk500_devcode = 0x; + # avr910_devcode = 0x; + signature = 0x1e 0x96 0x84; + bs2 = 0xe2; + + memory "efuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x i i i i i i"; + ; +; + +#------------------------------------------------------------ +# ATtiny2313 +#------------------------------------------------------------ + +part + id = "t2313"; + desc = "ATtiny2313"; + has_debugwire = yes; + flash_instr = 0xB2, 0x0F, 0x1F; + eeprom_instr = 0xBB, 0xFE, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBA, 0x0F, 0xB2, 0x0F, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; + stk500_devcode = 0x23; +## Use the ATtiny26 devcode: + avr910_devcode = 0x5e; + signature = 0x1e 0x91 0x0a; + pagel = 0xD4; + bs2 = 0xD6; + reset = io; + chip_erase_delay = 9000; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0E, 0x1E, 0x2E, 0x3E, 0x2E, 0x3E, + 0x4E, 0x5E, 0x4E, 0x5E, 0x6E, 0x7E, 0x6E, 0x7E, + 0x26, 0x36, 0x66, 0x76, 0x2A, 0x3A, 0x6A, 0x7A, + 0x2E, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 0; + + memory "eeprom" + size = 128; + paged = no; + page_size = 4; + min_write_delay = 4000; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 2048; + page_size = 32; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + +# The information in the data sheet of April/2004 is wrong, this works: + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + +# The information in the data sheet of April/2004 is wrong, this works: + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + +# The information in the data sheet of April/2004 is wrong, this works: + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 32; + readsize = 256; + ; +# ATtiny2313 has Signature Bytes: 0x1E 0x91 0x0A. + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; +# The Tiny2313 has calibration data for both 4 MHz and 8 MHz. +# The information in the data sheet of April/2004 is wrong, this works: + + memory "calibration" + size = 2; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATtiny4313 +#------------------------------------------------------------ + +part + id = "t4313"; + desc = "ATtiny4313"; + has_debugwire = yes; + flash_instr = 0xB2, 0x0F, 0x1F; + eeprom_instr = 0xBB, 0xFE, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBA, 0x0F, 0xB2, 0x0F, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; + stk500_devcode = 0x23; +## Use the ATtiny26 devcode: + avr910_devcode = 0x5e; + signature = 0x1e 0x92 0x0d; + pagel = 0xD4; + bs2 = 0xD6; + reset = io; + chip_erase_delay = 9000; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0E, 0x1E, 0x2E, 0x3E, 0x2E, 0x3E, + 0x4E, 0x5E, 0x4E, 0x5E, 0x6E, 0x7E, 0x6E, 0x7E, + 0x26, 0x36, 0x66, 0x76, 0x2A, 0x3A, 0x6A, 0x7A, + 0x2E, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 0; + + memory "eeprom" + size = 256; + paged = no; + page_size = 4; + min_write_delay = 4000; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 4096; + page_size = 64; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 32; + readsize = 256; + ; +# ATtiny4313 has Signature Bytes: 0x1E 0x92 0x0D. + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 2; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# AT90PWM2 +#------------------------------------------------------------ + +part + id = "pwm2"; + desc = "AT90PWM2"; + has_debugwire = yes; + flash_instr = 0xB6, 0x01, 0x11; + eeprom_instr = 0xBD, 0xF2, 0xBD, 0xE1, 0xBB, 0xCF, 0xB4, 0x00, + 0xBE, 0x01, 0xB6, 0x01, 0xBC, 0x00, 0xBB, 0xBF, + 0x99, 0xF9, 0xBB, 0xAF; + stk500_devcode = 0x65; +## avr910_devcode = ?; + signature = 0x1e 0x93 0x81; + pagel = 0xD8; + bs2 = 0xE2; + reset = io; + chip_erase_delay = 9000; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + memory "eeprom" + size = 512; + paged = no; + page_size = 4; + min_write_delay = 4000; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 8192; + page_size = 64; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 64; + readsize = 256; + ; +# AT90PWM2 has Signature Bytes: 0x1E 0x93 0x81. + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# AT90PWM3 +#------------------------------------------------------------ + +# Completely identical to AT90PWM2 (including the signature!) + +part parent "pwm2" + id = "pwm3"; + desc = "AT90PWM3"; + ; + +#------------------------------------------------------------ +# AT90PWM2B +#------------------------------------------------------------ +# Same as AT90PWM2 but different signature. + +part parent "pwm2" + id = "pwm2b"; + desc = "AT90PWM2B"; + signature = 0x1e 0x93 0x83; + + ocdrev = 1; + ; + +#------------------------------------------------------------ +# AT90PWM3B +#------------------------------------------------------------ + +# Completely identical to AT90PWM2B (including the signature!) + +part parent "pwm2b" + id = "pwm3b"; + desc = "AT90PWM3B"; + + ocdrev = 1; + ; + +#------------------------------------------------------------ +# AT90PWM316 +#------------------------------------------------------------ + +# Similar to AT90PWM3B, but with 16 kiB flash, 512 B EEPROM, and 1024 B SRAM. + +part parent "pwm3b" + id = "pwm316"; + desc = "AT90PWM316"; + signature = 0x1e 0x94 0x83; + + ocdrev = 1; + + memory "flash" + paged = yes; + size = 16384; + page_size = 128; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x21; + delay = 6; + blocksize = 128; + readsize = 256; + ; + ; + +#------------------------------------------------------------ +# AT90PWM216 +#------------------------------------------------------------ +# Completely identical to AT90PWM316 (including the signature!) + +part parent "pwm316" + id = "pwm216"; + desc = "AT90PWM216"; + ; + +#------------------------------------------------------------ +# ATtiny25 +#------------------------------------------------------------ + +part + id = "t25"; + desc = "ATtiny25"; + has_debugwire = yes; + flash_instr = 0xB4, 0x02, 0x12; + eeprom_instr = 0xBB, 0xFF, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBC, 0x02, 0xB4, 0x02, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; +## no STK500 devcode in XML file, use the ATtiny45 one + stk500_devcode = 0x14; +## avr910_devcode = ?; +## Try the AT90S2313 devcode: + avr910_devcode = 0x20; + signature = 0x1e 0x91 0x08; + reset = io; + chip_erase_delay = 4500; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + hvsp_controlstack = + 0x4C, 0x0C, 0x1C, 0x2C, 0x3C, 0x64, 0x74, 0x66, + 0x68, 0x78, 0x68, 0x68, 0x7A, 0x6A, 0x68, 0x78, + 0x78, 0x7D, 0x6D, 0x0C, 0x80, 0x40, 0x20, 0x10, + 0x11, 0x08, 0x04, 0x02, 0x03, 0x08, 0x04, 0x00; + hventerstabdelay = 100; + hvspcmdexedelay = 0; + synchcycles = 6; + latchcycles = 1; + togglevtg = 1; + poweroffdelay = 25; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 100; + resetdelay = 25; + chiperasepolltimeout = 40; + chiperasetime = 0; + programfusepolltimeout = 25; + programlockpolltimeout = 25; + + ocdrev = 1; + + memory "eeprom" + size = 128; + paged = no; + page_size = 4; + min_write_delay = 4000; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 2048; + page_size = 32; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 32; + readsize = 256; + ; +# ATtiny25 has Signature Bytes: 0x1E 0x91 0x08. + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATtiny45 +#------------------------------------------------------------ + +part + id = "t45"; + desc = "ATtiny45"; + has_debugwire = yes; + flash_instr = 0xB4, 0x02, 0x12; + eeprom_instr = 0xBB, 0xFF, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBC, 0x02, 0xB4, 0x02, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; + stk500_devcode = 0x14; +## avr910_devcode = ?; +## Try the AT90S2313 devcode: + avr910_devcode = 0x20; + signature = 0x1e 0x92 0x06; + reset = io; + chip_erase_delay = 4500; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + hvsp_controlstack = + 0x4C, 0x0C, 0x1C, 0x2C, 0x3C, 0x64, 0x74, 0x66, + 0x68, 0x78, 0x68, 0x68, 0x7A, 0x6A, 0x68, 0x78, + 0x78, 0x7D, 0x6D, 0x0C, 0x80, 0x40, 0x20, 0x10, + 0x11, 0x08, 0x04, 0x02, 0x03, 0x08, 0x04, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + hvspcmdexedelay = 0; + synchcycles = 6; + latchcycles = 1; + togglevtg = 1; + poweroffdelay = 25; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 100; + resetdelay = 25; + chiperasepolltimeout = 40; + chiperasetime = 0; + programfusepolltimeout = 25; + programlockpolltimeout = 25; + + ocdrev = 1; + + memory "eeprom" + size = 256; + page_size = 4; + min_write_delay = 4000; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 4096; + page_size = 64; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 32; + readsize = 256; + ; +# ATtiny45 has Signature Bytes: 0x1E 0x92 0x08. (Data sheet 2586C-AVR-06/05 (doc2586.pdf) indicates otherwise!) + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATtiny85 +#------------------------------------------------------------ + +part + id = "t85"; + desc = "ATtiny85"; + has_debugwire = yes; + flash_instr = 0xB4, 0x02, 0x12; + eeprom_instr = 0xBB, 0xFF, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBC, 0x02, 0xB4, 0x02, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; +## no STK500 devcode in XML file, use the ATtiny45 one + stk500_devcode = 0x14; +## avr910_devcode = ?; +## Try the AT90S2313 devcode: + avr910_devcode = 0x20; + signature = 0x1e 0x93 0x0b; + reset = io; + chip_erase_delay = 400000; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + hvsp_controlstack = + 0x4C, 0x0C, 0x1C, 0x2C, 0x3C, 0x64, 0x74, 0x66, + 0x68, 0x78, 0x68, 0x68, 0x7A, 0x6A, 0x68, 0x78, + 0x78, 0x7D, 0x6D, 0x0C, 0x80, 0x40, 0x20, 0x10, + 0x11, 0x08, 0x04, 0x02, 0x03, 0x08, 0x04, 0x00; + hventerstabdelay = 100; + hvspcmdexedelay = 0; + synchcycles = 6; + latchcycles = 1; + togglevtg = 1; + poweroffdelay = 25; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 100; + resetdelay = 25; + chiperasepolltimeout = 40; + chiperasetime = 0; + programfusepolltimeout = 25; + programlockpolltimeout = 25; + + ocdrev = 1; + + memory "eeprom" + size = 512; + paged = no; + page_size = 4; + min_write_delay = 4000; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 12; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 8192; + page_size = 64; + num_pages = 128; + min_write_delay = 30000; + max_write_delay = 30000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 32; + readsize = 256; + ; +# ATtiny85 has Signature Bytes: 0x1E 0x93 0x08. + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega640 +#------------------------------------------------------------ +# Almost same as ATmega1280, except for different memory sizes + +part + id = "m640"; + desc = "ATmega640"; + signature = 0x1e 0x96 0x08; + has_jtag = yes; +# stk500_devcode = 0xB2; +# avr910_devcode = 0x43; + chip_erase_delay = 9000; + pagel = 0xD7; + bs2 = 0xA0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + rampz = 0x3b; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 4096; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 8; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 65536; + page_size = 256; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 256; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega1280 +#------------------------------------------------------------ + +part + id = "m1280"; + desc = "ATmega1280"; + signature = 0x1e 0x97 0x03; + has_jtag = yes; +# stk500_devcode = 0xB2; +# avr910_devcode = 0x43; + chip_erase_delay = 9000; + pagel = 0xD7; + bs2 = 0xA0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + rampz = 0x3b; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 4096; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 8; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 131072; + page_size = 256; + num_pages = 512; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 256; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega1281 +#------------------------------------------------------------ +# Identical to ATmega1280 + +part parent "m1280" + id = "m1281"; + desc = "ATmega1281"; + signature = 0x1e 0x97 0x04; + + ocdrev = 3; + ; + +#------------------------------------------------------------ +# ATmega2560 +#------------------------------------------------------------ + +part + id = "m2560"; + desc = "ATmega2560"; + signature = 0x1e 0x98 0x01; + has_jtag = yes; + stk500_devcode = 0xB2; +# avr910_devcode = 0x43; + chip_erase_delay = 9000; + pagel = 0xD7; + bs2 = 0xA0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + rampz = 0x3b; + allowfullpagebitstream = no; + + ocdrev = 4; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 4096; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 8; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 262144; + page_size = 256; + num_pages = 1024; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + load_ext_addr = " 0 1 0 0 1 1 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 0 a16", + " 0 0 0 0 0 0 0 0"; + + mode = 0x41; + delay = 10; + blocksize = 256; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega2561 +#------------------------------------------------------------ + +part parent "m2560" + id = "m2561"; + desc = "ATmega2561"; + signature = 0x1e 0x98 0x02; + + ocdrev = 4; + ; + +#------------------------------------------------------------ +# ATmega128RFA1 +#------------------------------------------------------------ +# Identical to ATmega2561 but half the ROM + +part parent "m2561" + id = "m128rfa1"; + desc = "ATmega128RFA1"; + signature = 0x1e 0xa7 0x01; + chip_erase_delay = 55000; + bs2 = 0xE2; + + ocdrev = 3; + + memory "flash" + paged = yes; + size = 131072; + page_size = 256; + num_pages = 512; + min_write_delay = 50000; + max_write_delay = 50000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 256; + readsize = 256; + ; + ; + +#------------------------------------------------------------ +# ATmega256RFR2 +#------------------------------------------------------------ + +part parent "m2561" + id = "m256rfr2"; + desc = "ATmega256RFR2"; + signature = 0x1e 0xa8 0x02; + chip_erase_delay = 18500; + bs2 = 0xE2; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 8192; + min_write_delay = 13000; + max_write_delay = 13000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 8; + readsize = 256; + ; + + + ocdrev = 4; + ; + +#------------------------------------------------------------ +# ATmega128RFR2 +#------------------------------------------------------------ + +part parent "m128rfa1" + id = "m128rfr2"; + desc = "ATmega128RFR2"; + signature = 0x1e 0xa7 0x02; + + + ocdrev = 3; + ; + +#------------------------------------------------------------ +# ATmega64RFR2 +#------------------------------------------------------------ + +part parent "m128rfa1" + id = "m64rfr2"; + desc = "ATmega64RFR2"; + signature = 0x1e 0xa6 0x02; + + + ocdrev = 3; + + memory "flash" + paged = yes; + size = 65536; + page_size = 256; + num_pages = 256; + min_write_delay = 50000; + max_write_delay = 50000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 256; + readsize = 256; + ; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 2048; + min_write_delay = 13000; + max_write_delay = 13000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 8; + readsize = 256; + ; + + + ; + +#------------------------------------------------------------ +# ATmega2564RFR2 +#------------------------------------------------------------ + +part parent "m256rfr2" + id = "m2564rfr2"; + desc = "ATmega2564RFR2"; + signature = 0x1e 0xa8 0x03; + ; + +#------------------------------------------------------------ +# ATmega1284RFR2 +#------------------------------------------------------------ + +part parent "m128rfr2" + id = "m1284rfr2"; + desc = "ATmega1284RFR2"; + signature = 0x1e 0xa7 0x03; + ; + +#------------------------------------------------------------ +# ATmega644RFR2 +#------------------------------------------------------------ + +part parent "m64rfr2" + id = "m644rfr2"; + desc = "ATmega644RFR2"; + signature = 0x1e 0xa6 0x03; + ; + +#------------------------------------------------------------ +# ATtiny24 +#------------------------------------------------------------ + +part + id = "t24"; + desc = "ATtiny24"; + has_debugwire = yes; + flash_instr = 0xB4, 0x07, 0x17; + eeprom_instr = 0xBB, 0xFF, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBC, 0x07, 0xB4, 0x07, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; +## no STK500 devcode in XML file, use the ATtiny45 one + stk500_devcode = 0x14; +## avr910_devcode = ?; +## Try the AT90S2313 devcode: + avr910_devcode = 0x20; + signature = 0x1e 0x91 0x0b; + reset = io; + chip_erase_delay = 4500; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + hvsp_controlstack = + 0x4C, 0x0C, 0x1C, 0x2C, 0x3C, 0x64, 0x74, 0x66, + 0x68, 0x78, 0x68, 0x68, 0x7A, 0x6A, 0x68, 0x78, + 0x78, 0x7D, 0x6D, 0x0C, 0x80, 0x40, 0x20, 0x10, + 0x11, 0x08, 0x04, 0x02, 0x03, 0x08, 0x04, 0x0F; + hventerstabdelay = 100; + hvspcmdexedelay = 0; + synchcycles = 6; + latchcycles = 1; + togglevtg = 1; + poweroffdelay = 25; + resetdelayms = 0; + resetdelayus = 70; + hvleavestabdelay = 100; + resetdelay = 25; + chiperasepolltimeout = 40; + chiperasetime = 0; + programfusepolltimeout = 25; + programlockpolltimeout = 25; + + ocdrev = 1; + + memory "eeprom" + size = 128; + paged = no; + page_size = 4; + min_write_delay = 4000; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 x x x x x", + "x a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 2048; + page_size = 32; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x x a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 32; + readsize = 256; + ; +# ATtiny24 has Signature Bytes: 0x1E 0x91 0x0B. + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x x x x x x x i i"; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATtiny44 +#------------------------------------------------------------ + +part + id = "t44"; + desc = "ATtiny44"; + has_debugwire = yes; + flash_instr = 0xB4, 0x07, 0x17; + eeprom_instr = 0xBB, 0xFF, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBC, 0x07, 0xB4, 0x07, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; +## no STK500 devcode in XML file, use the ATtiny45 one + stk500_devcode = 0x14; +## avr910_devcode = ?; +## Try the AT90S2313 devcode: + avr910_devcode = 0x20; + signature = 0x1e 0x92 0x07; + reset = io; + chip_erase_delay = 4500; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + hvsp_controlstack = + 0x4C, 0x0C, 0x1C, 0x2C, 0x3C, 0x64, 0x74, 0x66, + 0x68, 0x78, 0x68, 0x68, 0x7A, 0x6A, 0x68, 0x78, + 0x78, 0x7D, 0x6D, 0x0C, 0x80, 0x40, 0x20, 0x10, + 0x11, 0x08, 0x04, 0x02, 0x03, 0x08, 0x04, 0x0F; + hventerstabdelay = 100; + hvspcmdexedelay = 0; + synchcycles = 6; + latchcycles = 1; + togglevtg = 1; + poweroffdelay = 25; + resetdelayms = 0; + resetdelayus = 70; + hvleavestabdelay = 100; + resetdelay = 25; + chiperasepolltimeout = 40; + chiperasetime = 0; + programfusepolltimeout = 25; + programlockpolltimeout = 25; + + ocdrev = 1; + + memory "eeprom" + size = 256; + paged = no; + page_size = 4; + min_write_delay = 4000; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 x x x x x", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 4096; + page_size = 64; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 32; + readsize = 256; + ; +# ATtiny44 has Signature Bytes: 0x1E 0x92 0x07. + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x x x x x x x i i"; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATtiny84 +#------------------------------------------------------------ + +part + id = "t84"; + desc = "ATtiny84"; + has_debugwire = yes; + flash_instr = 0xB4, 0x07, 0x17; + eeprom_instr = 0xBB, 0xFF, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBC, 0x07, 0xB4, 0x07, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; +## no STK500 devcode in XML file, use the ATtiny45 one + stk500_devcode = 0x14; +## avr910_devcode = ?; +## Try the AT90S2313 devcode: + avr910_devcode = 0x20; + signature = 0x1e 0x93 0x0c; + reset = io; + chip_erase_delay = 4500; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + hvsp_controlstack = + 0x4C, 0x0C, 0x1C, 0x2C, 0x3C, 0x64, 0x74, 0x66, + 0x68, 0x78, 0x68, 0x68, 0x7A, 0x6A, 0x68, 0x78, + 0x78, 0x7D, 0x6D, 0x0C, 0x80, 0x40, 0x20, 0x10, + 0x11, 0x08, 0x04, 0x02, 0x03, 0x08, 0x04, 0x0F; + hventerstabdelay = 100; + hvspcmdexedelay = 0; + synchcycles = 6; + latchcycles = 1; + togglevtg = 1; + poweroffdelay = 25; + resetdelayms = 0; + resetdelayus = 70; + hvleavestabdelay = 100; + resetdelay = 25; + chiperasepolltimeout = 40; + chiperasetime = 0; + programfusepolltimeout = 25; + programlockpolltimeout = 25; + + ocdrev = 1; + + memory "eeprom" + size = 512; + paged = no; + page_size = 4; + min_write_delay = 4000; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 x x x x a8", + "a7 a6 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " x a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 8192; + page_size = 64; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 32; + readsize = 256; + ; +# ATtiny84 has Signature Bytes: 0x1E 0x93 0x0C. + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x x x x x x x i i"; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATtiny441 +#------------------------------------------------------------ + +part parent "t44" + id = "t441"; + desc = "ATtiny441"; + signature = 0x1e 0x92 0x15; + + memory "flash" + paged = yes; + size = 4096; + page_size = 16; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x x x a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x x x a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 16; + readsize = 256; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; +; + +#------------------------------------------------------------ +# ATtiny841 +#------------------------------------------------------------ + +part parent "t84" + id = "t841"; + desc = "ATtiny841"; + signature = 0x1e 0x93 0x15; + + memory "flash" + paged = yes; + size = 8192; + page_size = 16; + num_pages = 512; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x x x a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x x x a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 16; + readsize = 256; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; +; + +#------------------------------------------------------------ +# ATtiny43U +#------------------------------------------------------------ + +part + id = "t43u"; + desc = "ATtiny43u"; + has_debugwire = yes; + flash_instr = 0xB4, 0x07, 0x17; + eeprom_instr = 0xBB, 0xFF, 0xBB, 0xEE, 0xBB, 0xCC, 0xB2, 0x0D, + 0xBC, 0x07, 0xB4, 0x07, 0xBA, 0x0D, 0xBB, 0xBC, + 0x99, 0xE1, 0xBB, 0xAC; + stk500_devcode = 0x14; +## avr910_devcode = ?; +## Try the AT90S2313 devcode: + avr910_devcode = 0x20; + signature = 0x1e 0x92 0x0C; + reset = io; + chip_erase_delay = 1000; + + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + pp_controlstack = 0x0E, 0x1E, 0x0E, 0x1E, 0x2E, 0x3E, 0x2E, 0x3E, 0x4E, 0x5E, + 0x4E, 0x5E, 0x6E, 0x7E, 0x6E, 0x7E, 0x06, 0x16, 0x46, 0x56, + 0x0A, 0x1A, 0x4A, 0x5A, 0x1E, 0x7C, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + hvspcmdexedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 20; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + resetdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + memory "eeprom" + size = 64; + paged = yes; + page_size = 4; + num_pages = 16; + min_write_delay = 4000; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = "1 0 1 0 0 0 0 0 0 0 0 x x x x x", + "0 0 a4 a3 a2 a1 a0 o o o o o o o o"; + + write = "1 1 0 0 0 0 0 0 0 0 0 x x x x x", + "0 0 a5 a4 a3 a2 a1 a0 i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x x", + " 0 0 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 5; + blocksize = 4; + readsize = 256; + ; + memory "flash" + paged = yes; + size = 4096; + page_size = 64; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x x a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 64; + readsize = 256; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + memory "lock" + size = 1; + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x x x x i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 4500; + max_write_delay = 4500; + ; + + memory "calibration" + size = 2; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 a0 o o o o o o o o"; + ; +; + +#------------------------------------------------------------ +# ATmega32u4 +#------------------------------------------------------------ + +part + id = "m32u4"; + desc = "ATmega32U4"; + signature = 0x1e 0x95 0x87; + usbpid = 0x2ff4; + has_jtag = yes; +# stk500_devcode = 0xB2; +# avr910_devcode = 0x43; + chip_erase_delay = 9000; + pagel = 0xD7; + bs2 = 0xA0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + rampz = 0x3b; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 1024; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 32768; + page_size = 128; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 128; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# AT90USB646 +#------------------------------------------------------------ + +part + id = "usb646"; + desc = "AT90USB646"; + signature = 0x1e 0x96 0x82; + usbpid = 0x2ff9; + has_jtag = yes; +# stk500_devcode = 0xB2; +# avr910_devcode = 0x43; + chip_erase_delay = 9000; + pagel = 0xD7; + bs2 = 0xA0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + rampz = 0x3b; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 2048; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x x a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 8; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 65536; + page_size = 256; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 256; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# AT90USB647 +#------------------------------------------------------------ +# identical to AT90USB646 + +part parent "usb646" + id = "usb647"; + desc = "AT90USB647"; + signature = 0x1e 0x96 0x82; + + ocdrev = 3; + ; + +#------------------------------------------------------------ +# AT90USB1286 +#------------------------------------------------------------ + +part + id = "usb1286"; + desc = "AT90USB1286"; + signature = 0x1e 0x97 0x82; + usbpid = 0x2ffb; + has_jtag = yes; +# stk500_devcode = 0xB2; +# avr910_devcode = 0x43; + chip_erase_delay = 9000; + pagel = 0xD7; + bs2 = 0xA0; + reset = dedicated; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + rampz = 0x3b; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 4096; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " x x x x a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 8; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 131072; + page_size = 256; + num_pages = 512; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 x x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 256; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x x i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 x x x x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 x x x x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# AT90USB1287 +#------------------------------------------------------------ +# identical to AT90USB1286 + +part parent "usb1286" + id = "usb1287"; + desc = "AT90USB1287"; + signature = 0x1e 0x97 0x82; + + ocdrev = 3; + ; + +#------------------------------------------------------------ +# AT90USB162 +#------------------------------------------------------------ + +part + id = "usb162"; + desc = "AT90USB162"; + has_jtag = no; + has_debugwire = yes; + signature = 0x1e 0x94 0x82; + usbpid = 0x2ffa; + chip_erase_delay = 9000; + reset = io; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + pagel = 0xD7; + bs2 = 0xC6; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 512; + num_pages = 128; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 16384; + page_size = 128; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 128; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# AT90USB82 +#------------------------------------------------------------ +# Changes against AT90USB162 (beside IDs) +# memory "flash" +# size = 8192; +# num_pages = 64; + +part + id = "usb82"; + desc = "AT90USB82"; + has_jtag = no; + has_debugwire = yes; + signature = 0x1e 0x93 0x82; + usbpid = 0x2ff7; + chip_erase_delay = 9000; + reset = io; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + pagel = 0xD7; + bs2 = 0xC6; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 512; + num_pages = 128; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 8192; + page_size = 128; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 128; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega32U2 +#------------------------------------------------------------ +# Changes against AT90USB162 (beside IDs) +# memory "flash" +# size = 32768; +# num_pages = 256; +# memory "eeprom" +# size = 1024; +# num_pages = 256; +part + id = "m32u2"; + desc = "ATmega32U2"; + has_jtag = no; + has_debugwire = yes; + signature = 0x1e 0x95 0x8a; + usbpid = 0x2ff0; + chip_erase_delay = 9000; + reset = io; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + pagel = 0xD7; + bs2 = 0xC6; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 1024; + num_pages = 256; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 32768; + page_size = 128; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 128; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; +#------------------------------------------------------------ +# ATmega16U2 +#------------------------------------------------------------ +# Changes against ATmega32U2 (beside IDs) +# memory "flash" +# size = 16384; +# num_pages = 128; +# memory "eeprom" +# size = 512; +# num_pages = 128; +part + id = "m16u2"; + desc = "ATmega16U2"; + has_jtag = no; + has_debugwire = yes; + signature = 0x1e 0x94 0x89; + usbpid = 0x2fef; + chip_erase_delay = 9000; + reset = io; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + pagel = 0xD7; + bs2 = 0xC6; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 512; + num_pages = 128; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 16384; + page_size = 128; + num_pages = 128; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 128; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega8U2 +#------------------------------------------------------------ +# Changes against ATmega16U2 (beside IDs) +# memory "flash" +# size = 8192; +# page_size = 64; +# blocksize = 64; + +part + id = "m8u2"; + desc = "ATmega8U2"; + has_jtag = no; + has_debugwire = yes; + signature = 0x1e 0x93 0x89; + usbpid = 0x2fee; + chip_erase_delay = 9000; + reset = io; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + pagel = 0xD7; + bs2 = 0xC6; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + ocdrev = 1; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 512; + num_pages = 128; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0x00; + readback_p2 = 0x00; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 0 0 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 20; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 8192; + page_size = 128; + num_pages = 64; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0x00; + readback_p2 = 0x00; + read_lo = " 0 0 1 0 0 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " x x x x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + "a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 128; + readsize = 256; + ; + + memory "lfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x i i i i i i i i"; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; + ; +#------------------------------------------------------------ +# ATmega325 +#------------------------------------------------------------ + +part + id = "m325"; + desc = "ATmega325"; + signature = 0x1e 0x95 0x05; + has_jtag = yes; +# stk500_devcode = 0x??; # No STK500v1 support? +# avr910_devcode = 0x??; # Try the ATmega16 one + avr910_devcode = 0x74; + pagel = 0xd7; + bs2 = 0xa0; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 4; /* for parallel programming */ + size = 1024; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 0 0 0 0 a9 a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 32768; + page_size = 128; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 0 0 0 0 0", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 0 0 0 0 0", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 128; + readsize = 256; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 0 0 0 0 0", + "0 0 0 0 0 0 0 0 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "0 0 0 0 0 0 0 0 i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "0 0 0 0 0 0 0 0 i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "0 0 0 0 0 0 0 0 1 1 1 1 1 i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 1; + + read = "0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega645 +#------------------------------------------------------------ + +part + id = "m645"; + desc = "ATmega645"; + signature = 0x1E 0x96 0x05; + has_jtag = yes; +# stk500_devcode = 0x??; # No STK500v1 support? +# avr910_devcode = 0x??; # Try the ATmega16 one + avr910_devcode = 0x74; + pagel = 0xd7; + bs2 = 0xa0; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0F, 0x1F, 0x2E, 0x3E, 0x2F, 0x3F, + 0x4E, 0x5E, 0x4F, 0x5F, 0x6E, 0x7E, 0x6F, 0x7F, + 0x66, 0x76, 0x67, 0x77, 0x6A, 0x7A, 0x6B, 0x7B, + 0xBE, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 5; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + idr = 0x31; + spmcr = 0x57; + allowfullpagebitstream = no; + + ocdrev = 3; + + memory "eeprom" + paged = no; /* leave this "no" */ + page_size = 8; /* for parallel programming */ + size = 2048; + min_write_delay = 9000; + max_write_delay = 9000; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 0 0 0 a10 a9 a8", + " a7 a6 a5 a4 a3 0 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 10; + blocksize = 8; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 65536; + page_size = 256; + num_pages = 256; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 0 0 0 0 0", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 0 0 0 0 0", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " a15 a14 a13 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " 0 0 0 0 0 0 0 0"; + + mode = 0x41; + delay = 10; + blocksize = 128; + readsize = 256; + ; + + memory "lock" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 0 0 0 0 0", + "0 0 0 0 0 0 0 0 1 1 i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "lfuse" + size = 1; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "0 0 0 0 0 0 0 0 i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "hfuse" + size = 1; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "0 0 0 0 0 0 0 0 i i i i i i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "efuse" + size = 1; + + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "0 0 0 0 0 0 0 0 1 1 1 1 1 i i i"; + min_write_delay = 9000; + max_write_delay = 9000; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 a1 a0 o o o o o o o o"; + ; + + memory "calibration" + size = 1; + + read = "0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + ; + +#------------------------------------------------------------ +# ATmega3250 +#------------------------------------------------------------ + +part parent "m325" + id = "m3250"; + desc = "ATmega3250"; + signature = 0x1E 0x95 0x06; + + ocdrev = 3; + ; + +#------------------------------------------------------------ +# ATmega6450 +#------------------------------------------------------------ + +part parent "m645" + id = "m6450"; + desc = "ATmega6450"; + signature = 0x1E 0x96 0x06; + + ocdrev = 3; + ; + +#------------------------------------------------------------ +# AVR XMEGA family common values +#------------------------------------------------------------ + +part + id = ".xmega"; + desc = "AVR XMEGA family common values"; + has_pdi = yes; + nvm_base = 0x01c0; + mcu_base = 0x0090; + + memory "signature" + size = 3; + offset = 0x1000090; + ; + + memory "prodsig" + size = 0x32; + offset = 0x8e0200; + page_size = 0x32; + readsize = 0x32; + ; + + memory "fuse1" + size = 1; + offset = 0x8f0021; + ; + + memory "fuse2" + size = 1; + offset = 0x8f0022; + ; + + memory "fuse4" + size = 1; + offset = 0x8f0024; + ; + + memory "fuse5" + size = 1; + offset = 0x8f0025; + ; + + memory "lock" + size = 1; + offset = 0x8f0027; + ; + + memory "data" + # SRAM, only used to supply the offset + offset = 0x1000000; + ; +; + +#------------------------------------------------------------ +# ATxmega16A4U +#------------------------------------------------------------ + +part parent ".xmega" + id = "x16a4u"; + desc = "ATxmega16A4U"; + signature = 0x1e 0x94 0x41; + usbpid = 0x2fe3; + + memory "eeprom" + size = 0x400; + offset = 0x8c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x4000; + offset = 0x800000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "apptable" + size = 0x1000; + offset = 0x803000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "boot" + size = 0x1000; + offset = 0x804000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "flash" + size = 0x5000; + offset = 0x800000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "usersig" + size = 0x100; + offset = 0x8e0400; + page_size = 0x100; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATxmega16C4 +#------------------------------------------------------------ + +part parent "x16a4u" + id = "x16c4"; + desc = "ATxmega16C4"; + signature = 0x1e 0x94 0x43; +; + +#------------------------------------------------------------ +# ATxmega16D4 +#------------------------------------------------------------ + +part parent "x16a4u" + id = "x16d4"; + desc = "ATxmega16D4"; + signature = 0x1e 0x94 0x42; +; + +#------------------------------------------------------------ +# ATxmega16A4 +#------------------------------------------------------------ + +part parent "x16a4u" + id = "x16a4"; + desc = "ATxmega16A4"; + signature = 0x1e 0x94 0x41; + has_jtag = yes; + + memory "fuse0" + size = 1; + offset = 0x8f0020; + ; +; + +#------------------------------------------------------------ +# ATxmega32A4U +#------------------------------------------------------------ + +part parent ".xmega" + id = "x32a4u"; + desc = "ATxmega32A4U"; + signature = 0x1e 0x95 0x41; + usbpid = 0x2fe4; + + memory "eeprom" + size = 0x400; + offset = 0x8c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x8000; + offset = 0x800000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "apptable" + size = 0x1000; + offset = 0x807000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "boot" + size = 0x1000; + offset = 0x808000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "flash" + size = 0x9000; + offset = 0x800000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "usersig" + size = 0x100; + offset = 0x8e0400; + page_size = 0x100; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATxmega32C4 +#------------------------------------------------------------ + +part parent "x32a4u" + id = "x32c4"; + desc = "ATxmega32C4"; + signature = 0x1e 0x95 0x44; +; + +#------------------------------------------------------------ +# ATxmega32D4 +#------------------------------------------------------------ + +part parent "x32a4u" + id = "x32d4"; + desc = "ATxmega32D4"; + signature = 0x1e 0x95 0x42; +; + +#------------------------------------------------------------ +# ATxmega32A4 +#------------------------------------------------------------ + +part parent "x32a4u" + id = "x32a4"; + desc = "ATxmega32A4"; + signature = 0x1e 0x95 0x41; + has_jtag = yes; + + memory "fuse0" + size = 1; + offset = 0x8f0020; + ; +; + +#------------------------------------------------------------ +# ATxmega64A4U +#------------------------------------------------------------ + +part parent ".xmega" + id = "x64a4u"; + desc = "ATxmega64A4U"; + signature = 0x1e 0x96 0x46; + usbpid = 0x2fe5; + + memory "eeprom" + size = 0x800; + offset = 0x8c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x10000; + offset = 0x800000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "apptable" + size = 0x1000; + offset = 0x80f000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "boot" + size = 0x1000; + offset = 0x810000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "flash" + size = 0x11000; + offset = 0x800000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "usersig" + size = 0x100; + offset = 0x8e0400; + page_size = 0x100; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATxmega64C3 +#------------------------------------------------------------ + +part parent "x64a4u" + id = "x64c3"; + desc = "ATxmega64C3"; + signature = 0x1e 0x96 0x49; + usbpid = 0x2fd6; +; + +#------------------------------------------------------------ +# ATxmega64D3 +#------------------------------------------------------------ + +part parent "x64a4u" + id = "x64d3"; + desc = "ATxmega64D3"; + signature = 0x1e 0x96 0x4a; +; + +#------------------------------------------------------------ +# ATxmega64D4 +#------------------------------------------------------------ + +part parent "x64a4u" + id = "x64d4"; + desc = "ATxmega64D4"; + signature = 0x1e 0x96 0x47; +; + +#------------------------------------------------------------ +# ATxmega64A1 +#------------------------------------------------------------ + +part parent "x64a4u" + id = "x64a1"; + desc = "ATxmega64A1"; + signature = 0x1e 0x96 0x4e; + has_jtag = yes; + + memory "fuse0" + size = 1; + offset = 0x8f0020; + ; +; + +#------------------------------------------------------------ +# ATxmega64A1U +#------------------------------------------------------------ + +part parent "x64a1" + id = "x64a1u"; + desc = "ATxmega64A1U"; + signature = 0x1e 0x96 0x4e; + usbpid = 0x2fe8; +; + +#------------------------------------------------------------ +# ATxmega64A3 +#------------------------------------------------------------ + +part parent "x64a1" + id = "x64a3"; + desc = "ATxmega64A3"; + signature = 0x1e 0x96 0x42; +; + +#------------------------------------------------------------ +# ATxmega64A3U +#------------------------------------------------------------ + +part parent "x64a1" + id = "x64a3u"; + desc = "ATxmega64A3U"; + signature = 0x1e 0x96 0x42; + usbpid = 0x2fe5; +; + +#------------------------------------------------------------ +# ATxmega64A4 +#------------------------------------------------------------ + +part parent "x64a1" + id = "x64a4"; + desc = "ATxmega64A4"; + signature = 0x1e 0x96 0x46; +; + +#------------------------------------------------------------ +# ATxmega64B1 +#------------------------------------------------------------ + +part parent "x64a1" + id = "x64b1"; + desc = "ATxmega64B1"; + signature = 0x1e 0x96 0x52; + usbpid = 0x2fe1; +; + +#------------------------------------------------------------ +# ATxmega64B3 +#------------------------------------------------------------ + +part parent "x64a1" + id = "x64b3"; + desc = "ATxmega64B3"; + signature = 0x1e 0x96 0x51; + usbpid = 0x2fdf; +; + +#------------------------------------------------------------ +# ATxmega128C3 +#------------------------------------------------------------ + +part parent ".xmega" + id = "x128c3"; + desc = "ATxmega128C3"; + signature = 0x1e 0x97 0x52; + usbpid = 0x2fd7; + + memory "eeprom" + size = 0x800; + offset = 0x8c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x20000; + offset = 0x800000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "apptable" + size = 0x2000; + offset = 0x81e000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "boot" + size = 0x2000; + offset = 0x820000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "flash" + size = 0x22000; + offset = 0x800000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "usersig" + size = 0x200; + offset = 0x8e0400; + page_size = 0x200; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATxmega128D3 +#------------------------------------------------------------ + +part parent "x128c3" + id = "x128d3"; + desc = "ATxmega128D3"; + signature = 0x1e 0x97 0x48; +; + +#------------------------------------------------------------ +# ATxmega128D4 +#------------------------------------------------------------ + +part parent "x128c3" + id = "x128d4"; + desc = "ATxmega128D4"; + signature = 0x1e 0x97 0x47; +; + +#------------------------------------------------------------ +# ATxmega128A1 +#------------------------------------------------------------ + +part parent "x128c3" + id = "x128a1"; + desc = "ATxmega128A1"; + signature = 0x1e 0x97 0x4c; + has_jtag = yes; + + memory "fuse0" + size = 1; + offset = 0x8f0020; + ; +; + +#------------------------------------------------------------ +# ATxmega128A1 revision D +#------------------------------------------------------------ + +part parent "x128a1" + id = "x128a1d"; + desc = "ATxmega128A1revD"; + signature = 0x1e 0x97 0x41; +; + +#------------------------------------------------------------ +# ATxmega128A1U +#------------------------------------------------------------ + +part parent "x128a1" + id = "x128a1u"; + desc = "ATxmega128A1U"; + signature = 0x1e 0x97 0x4c; + usbpid = 0x2fed; +; + +#------------------------------------------------------------ +# ATxmega128A3 +#------------------------------------------------------------ + +part parent "x128a1" + id = "x128a3"; + desc = "ATxmega128A3"; + signature = 0x1e 0x97 0x42; +; + +#------------------------------------------------------------ +# ATxmega128A3U +#------------------------------------------------------------ + +part parent "x128a1" + id = "x128a3u"; + desc = "ATxmega128A3U"; + signature = 0x1e 0x97 0x42; + usbpid = 0x2fe6; +; + +#------------------------------------------------------------ +# ATxmega128A4 +#------------------------------------------------------------ + +part parent ".xmega" + id = "x128a4"; + desc = "ATxmega128A4"; + signature = 0x1e 0x97 0x46; + has_jtag = yes; + + memory "eeprom" + size = 0x800; + offset = 0x8c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x20000; + offset = 0x800000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "apptable" + size = 0x1000; + offset = 0x81f000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "boot" + size = 0x2000; + offset = 0x820000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "flash" + size = 0x22000; + offset = 0x800000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "usersig" + size = 0x200; + offset = 0x8e0400; + page_size = 0x200; + readsize = 0x100; + ; + + memory "fuse0" + size = 1; + offset = 0x8f0020; + ; +; + +#------------------------------------------------------------ +# ATxmega128A4U +#------------------------------------------------------------ + +part parent ".xmega" + id = "x128a4u"; + desc = "ATxmega128A4U"; + signature = 0x1e 0x97 0x46; + usbpid = 0x2fde; + + memory "eeprom" + size = 0x800; + offset = 0x8c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x20000; + offset = 0x800000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "apptable" + size = 0x1000; + offset = 0x81f000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "boot" + size = 0x2000; + offset = 0x820000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "flash" + size = 0x22000; + offset = 0x800000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "usersig" + size = 0x100; + offset = 0x8e0400; + page_size = 0x100; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATxmega128B1 +#------------------------------------------------------------ + +part parent ".xmega" + id = "x128b1"; + desc = "ATxmega128B1"; + signature = 0x1e 0x97 0x4d; + usbpid = 0x2fea; + has_jtag = yes; + + memory "eeprom" + size = 0x800; + offset = 0x8c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x20000; + offset = 0x800000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "apptable" + size = 0x2000; + offset = 0x81e000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "boot" + size = 0x2000; + offset = 0x820000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "flash" + size = 0x22000; + offset = 0x800000; + page_size = 0x100; + readsize = 0x100; + ; + + memory "usersig" + size = 0x100; + offset = 0x8e0400; + page_size = 0x100; + readsize = 0x100; + ; + + memory "fuse0" + size = 1; + offset = 0x8f0020; + ; +; + +#------------------------------------------------------------ +# ATxmega128B3 +#------------------------------------------------------------ + +part parent "x128b1" + id = "x128b3"; + desc = "ATxmega128B3"; + signature = 0x1e 0x97 0x4b; + usbpid = 0x2fe0; +; + +#------------------------------------------------------------ +# ATxmega192C3 +#------------------------------------------------------------ + +part parent ".xmega" + id = "x192c3"; + desc = "ATxmega192C3"; + signature = 0x1e 0x97 0x51; + # usbpid = 0x2f??; + + memory "eeprom" + size = 0x800; + offset = 0x8c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x30000; + offset = 0x800000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "apptable" + size = 0x2000; + offset = 0x82e000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "boot" + size = 0x2000; + offset = 0x830000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "flash" + size = 0x32000; + offset = 0x800000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "usersig" + size = 0x200; + offset = 0x8e0400; + page_size = 0x200; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATxmega192D3 +#------------------------------------------------------------ + +part parent "x192c3" + id = "x192d3"; + desc = "ATxmega192D3"; + signature = 0x1e 0x97 0x49; +; + +#------------------------------------------------------------ +# ATxmega192A1 +#------------------------------------------------------------ + +part parent "x192c3" + id = "x192a1"; + desc = "ATxmega192A1"; + signature = 0x1e 0x97 0x4e; + has_jtag = yes; + + memory "fuse0" + size = 1; + offset = 0x8f0020; + ; +; + +#------------------------------------------------------------ +# ATxmega192A3 +#------------------------------------------------------------ + +part parent "x192a1" + id = "x192a3"; + desc = "ATxmega192A3"; + signature = 0x1e 0x97 0x44; +; + +#------------------------------------------------------------ +# ATxmega192A3U +#------------------------------------------------------------ + +part parent "x192a1" + id = "x192a3u"; + desc = "ATxmega192A3U"; + signature = 0x1e 0x97 0x44; + usbpid = 0x2fe7; +; + +#------------------------------------------------------------ +# ATxmega256C3 +#------------------------------------------------------------ + +part parent ".xmega" + id = "x256c3"; + desc = "ATxmega256C3"; + signature = 0x1e 0x98 0x46; + usbpid = 0x2fda; + + memory "eeprom" + size = 0x1000; + offset = 0x8c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x40000; + offset = 0x800000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "apptable" + size = 0x2000; + offset = 0x83e000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "boot" + size = 0x2000; + offset = 0x840000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "flash" + size = 0x42000; + offset = 0x800000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "usersig" + size = 0x200; + offset = 0x8e0400; + page_size = 0x200; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATxmega256D3 +#------------------------------------------------------------ + +part parent "x256c3" + id = "x256d3"; + desc = "ATxmega256D3"; + signature = 0x1e 0x98 0x44; +; + +#------------------------------------------------------------ +# ATxmega256A1 +#------------------------------------------------------------ + +part parent "x256c3" + id = "x256a1"; + desc = "ATxmega256A1"; + signature = 0x1e 0x98 0x46; + has_jtag = yes; + + memory "fuse0" + size = 1; + offset = 0x8f0020; + ; +; + +#------------------------------------------------------------ +# ATxmega256A3 +#------------------------------------------------------------ + +part parent "x256a1" + id = "x256a3"; + desc = "ATxmega256A3"; + signature = 0x1e 0x98 0x42; +; + +#------------------------------------------------------------ +# ATxmega256A3U +#------------------------------------------------------------ + +part parent "x256a1" + id = "x256a3u"; + desc = "ATxmega256A3U"; + signature = 0x1e 0x98 0x42; + usbpid = 0x2fec; +; + +#------------------------------------------------------------ +# ATxmega256A3B +#------------------------------------------------------------ + +part parent "x256a1" + id = "x256a3b"; + desc = "ATxmega256A3B"; + signature = 0x1e 0x98 0x43; +; + +#------------------------------------------------------------ +# ATxmega256A3BU +#------------------------------------------------------------ + +part parent "x256a1" + id = "x256a3bu"; + desc = "ATxmega256A3BU"; + signature = 0x1e 0x98 0x43; + usbpid = 0x2fe2; +; + +#------------------------------------------------------------ +# ATxmega384C3 +#------------------------------------------------------------ + +part parent ".xmega" + id = "x384c3"; + desc = "ATxmega384C3"; + signature = 0x1e 0x98 0x45; + usbpid = 0x2fdb; + + memory "eeprom" + size = 0x1000; + offset = 0x8c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x60000; + offset = 0x800000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "apptable" + size = 0x2000; + offset = 0x85e000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "boot" + size = 0x2000; + offset = 0x860000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "flash" + size = 0x62000; + offset = 0x800000; + page_size = 0x200; + readsize = 0x100; + ; + + memory "usersig" + size = 0x200; + offset = 0x8e0400; + page_size = 0x200; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATxmega384D3 +#------------------------------------------------------------ + +part parent "x384c3" + id = "x384d3"; + desc = "ATxmega384D3"; + signature = 0x1e 0x98 0x47; +; + +#------------------------------------------------------------ +# ATxmega8E5 +#------------------------------------------------------------ + +part parent ".xmega" + id = "x8e5"; + desc = "ATxmega8E5"; + signature = 0x1e 0x93 0x41; + + memory "eeprom" + size = 0x0200; + offset = 0x08c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x2000; + offset = 0x0800000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "apptable" + size = 0x800; + offset = 0x00801800; + page_size = 0x80; + readsize = 0x100; + ; + + memory "boot" + size = 0x800; + offset = 0x00802000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "flash" + size = 0x2800; + offset = 0x0800000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "usersig" + size = 0x80; + offset = 0x8e0400; + page_size = 0x80; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATxmega16E5 +#------------------------------------------------------------ + +part parent ".xmega" + id = "x16e5"; + desc = "ATxmega16E5"; + signature = 0x1e 0x94 0x45; + + memory "eeprom" + size = 0x0200; + offset = 0x08c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x4000; + offset = 0x0800000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "apptable" + size = 0x1000; + offset = 0x00803000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "boot" + size = 0x1000; + offset = 0x00804000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "flash" + size = 0x5000; + offset = 0x0800000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "usersig" + size = 0x80; + offset = 0x8e0400; + page_size = 0x80; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATxmega32E5 +#------------------------------------------------------------ + +part parent ".xmega" + id = "x32e5"; + desc = "ATxmega32E5"; + signature = 0x1e 0x95 0x4c; + + memory "eeprom" + size = 0x0400; + offset = 0x08c0000; + page_size = 0x20; + readsize = 0x100; + ; + + memory "application" + size = 0x8000; + offset = 0x0800000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "apptable" + size = 0x1000; + offset = 0x00807000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "boot" + size = 0x1000; + offset = 0x00808000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "flash" + size = 0x9000; + offset = 0x0800000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "usersig" + size = 0x80; + offset = 0x8e0400; + page_size = 0x80; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# AVR32UC3A0512 +#------------------------------------------------------------ + +part + id = "uc3a0512"; + desc = "AT32UC3A0512"; + signature = 0xED 0xC0 0x3F; + has_jtag = yes; + is_avr32 = yes; + + memory "flash" + paged = yes; + page_size = 512; # bytes + readsize = 512; # bytes + num_pages = 1024; # could be set dynamicly + size = 0x00080000; # could be set dynamicly + offset = 0x80000000; + ; +; + +part parent "uc3a0512" + id = "ucr2"; + desc = "deprecated, use 'uc3a0512'"; +; + +#------------------------------------------------------------ +# ATtiny1634. +#------------------------------------------------------------ + +part + id = "t1634"; + desc = "ATtiny1634"; + has_debugwire = yes; + flash_instr = 0xB6, 0x01, 0x11; + eeprom_instr = 0xBD, 0xF2, 0xBD, 0xE1, 0xBB, 0xCF, 0xB4, 0x00, + 0xBE, 0x01, 0xB6, 0x01, 0xBC, 0x00, 0xBB, 0xBF, + 0x99, 0xF9, 0xBB, 0xAF; + stk500_devcode = 0x86; + # avr910_devcode = 0x; + signature = 0x1e 0x94 0x12; + pagel = 0xB3; + bs2 = 0xB1; + reset = io; + chip_erase_delay = 9000; + pgm_enable = "1 0 1 0 1 1 0 0 0 1 0 1 0 0 1 1", + "x x x x x x x x x x x x x x x x"; + + chip_erase = "1 0 1 0 1 1 0 0 1 0 0 x x x x x", + "x x x x x x x x x x x x x x x x"; + + timeout = 200; + stabdelay = 100; + cmdexedelay = 25; + synchloops = 32; + bytedelay = 0; + pollindex = 3; + pollvalue = 0x53; + predelay = 1; + postdelay = 1; + pollmethod = 1; + + pp_controlstack = + 0x0E, 0x1E, 0x0E, 0x1E, 0x2E, 0x3E, 0x2E, 0x3E, + 0x4E, 0x5E, 0x4E, 0x5E, 0x6E, 0x7E, 0x6E, 0x7E, + 0x26, 0x36, 0x66, 0x76, 0x2A, 0x3A, 0x6A, 0x7A, + 0x2E, 0xFD, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + hventerstabdelay = 100; + progmodedelay = 0; + latchcycles = 0; + togglevtg = 1; + poweroffdelay = 15; + resetdelayms = 1; + resetdelayus = 0; + hvleavestabdelay = 15; + resetdelay = 15; + chiperasepulsewidth = 0; + chiperasepolltimeout = 10; + programfusepulsewidth = 0; + programfusepolltimeout = 5; + programlockpulsewidth = 0; + programlockpolltimeout = 5; + + memory "eeprom" + paged = no; + page_size = 4; + size = 256; + min_write_delay = 3600; + max_write_delay = 3600; + readback_p1 = 0xff; + readback_p2 = 0xff; + read = " 1 0 1 0 0 0 0 0", + " 0 0 0 x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + write = " 1 1 0 0 0 0 0 0", + " 0 0 0 x x x x a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_lo = " 1 1 0 0 0 0 0 1", + " 0 0 0 0 0 0 0 0", + " 0 0 0 0 0 0 a1 a0", + " i i i i i i i i"; + + writepage = " 1 1 0 0 0 0 1 0", + " 0 0 x x x x x a8", + " a7 a6 a5 a4 a3 a2 0 0", + " x x x x x x x x"; + + mode = 0x41; + delay = 5; + blocksize = 4; + readsize = 256; + ; + + memory "flash" + paged = yes; + size = 16384; + page_size = 32; + num_pages = 512; + min_write_delay = 4500; + max_write_delay = 4500; + readback_p1 = 0xff; + readback_p2 = 0xff; + read_lo = " 0 0 1 0 0 0 0 0", + " 0 0 0 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + read_hi = " 0 0 1 0 1 0 0 0", + " 0 0 0 a12 a11 a10 a9 a8", + " a7 a6 a5 a4 a3 a2 a1 a0", + " o o o o o o o o"; + + loadpage_lo = " 0 1 0 0 0 0 0 0", + " 0 0 0 x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + loadpage_hi = " 0 1 0 0 1 0 0 0", + " 0 0 0 x x x x x", + " x x a5 a4 a3 a2 a1 a0", + " i i i i i i i i"; + + writepage = " 0 1 0 0 1 1 0 0", + " 0 0 0 a12 a11 a10 a9 a8", + " a7 a6 x x x x x x", + " x x x x x x x x"; + + mode = 0x41; + delay = 6; + blocksize = 128; + readsize = 256; + + ; + + memory "lfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "hfuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 1 0 0 0", + "x x x x x x x x i i i i i i i i"; + ; + + memory "efuse" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0", + "x x x x x x x x o o o o o o o o"; + + write = "1 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0", + "x x x x x x x x x x x i i i i i"; + ; + + memory "lock" + size = 1; + min_write_delay = 4500; + max_write_delay = 4500; + read = "0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0", + "x x x x x x x x x x x x x x o o"; + + write = "1 0 1 0 1 1 0 0 1 1 1 x x x x x", + "x x x x x x x x 1 1 1 1 1 1 i i"; + ; + + memory "calibration" + size = 1; + read = "0 0 1 1 1 0 0 0 0 0 0 x x x x x", + "0 0 0 0 0 0 0 0 o o o o o o o o"; + ; + + memory "signature" + size = 3; + read = "0 0 1 1 0 0 0 0 0 0 0 x x x x x", + "x x x x x x a1 a0 o o o o o o o o"; + ; +; + +#------------------------------------------------------------ +# Common values for reduced core tinys (4/5/9/10/20/40) +#------------------------------------------------------------ + +part + id = ".reduced_core_tiny"; + desc = "Common values for reduced core tinys"; + has_tpi = yes; + + memory "signature" + size = 3; + offset = 0x3fc0; + page_size = 16; + ; + + memory "fuse" + size = 1; + offset = 0x3f40; + page_size = 16; + blocksize = 4; + ; + + memory "calibration" + size = 1; + offset = 0x3f80; + page_size = 16; + ; + + memory "lockbits" + size = 1; + offset = 0x3f00; + page_size = 16; + ; +; + +#------------------------------------------------------------ +# ATtiny4 +#------------------------------------------------------------ + +part parent ".reduced_core_tiny" + id = "t4"; + desc = "ATtiny4"; + signature = 0x1e 0x8f 0x0a; + + memory "flash" + size = 512; + offset = 0x4000; + page_size = 16; + blocksize = 128; + ; +; + +#------------------------------------------------------------ +# ATtiny5 +#------------------------------------------------------------ + +part parent "t4" + id = "t5"; + desc = "ATtiny5"; + signature = 0x1e 0x8f 0x09; +; + +#------------------------------------------------------------ +# ATtiny9 +#------------------------------------------------------------ + +part parent ".reduced_core_tiny" + id = "t9"; + desc = "ATtiny9"; + signature = 0x1e 0x90 0x08; + + memory "flash" + size = 1024; + offset = 0x4000; + page_size = 16; + blocksize = 128; + ; +; + +#------------------------------------------------------------ +# ATtiny10 +#------------------------------------------------------------ + +part parent "t9" + id = "t10"; + desc = "ATtiny10"; + signature = 0x1e 0x90 0x03; +; + +#------------------------------------------------------------ +# ATtiny20 +#------------------------------------------------------------ + +part parent ".reduced_core_tiny" + id = "t20"; + desc = "ATtiny20"; + signature = 0x1e 0x91 0x0F; + + memory "flash" + size = 2048; + offset = 0x4000; + page_size = 16; + blocksize = 128; + ; +; + +#------------------------------------------------------------ +# ATtiny40 +#------------------------------------------------------------ + +part parent ".reduced_core_tiny" + id = "t40"; + desc = "ATtiny40"; + signature = 0x1e 0x92 0x0E; + + memory "flash" + size = 4096; + offset = 0x4000; + page_size = 64; + blocksize = 128; + ; +; + +#------------------------------------------------------------ +# ATmega406 +#------------------------------------------------------------ + +part + id = "m406"; + desc = "ATMEGA406"; + has_jtag = yes; + signature = 0x1e 0x95 0x07; + + # STK500 parameters (parallel programming IO lines) + pagel = 0xa7; + bs2 = 0xa0; + serial = no; + parallel = yes; + + # STK500v2 HV programming parameters, from XML + pp_controlstack = 0x0e, 0x1e, 0x0f, 0x1f, 0x2e, 0x3e, 0x2f, 0x3f, + 0x4e, 0x5e, 0x4f, 0x5f, 0x6e, 0x7e, 0x6f, 0x7f, + 0x66, 0x76, 0x67, 0x77, 0x6a, 0x7a, 0x6b, 0x7b, + 0xbe, 0xfd, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00; + + # JTAG ICE mkII parameters, also from XML files + allowfullpagebitstream = no; + enablepageprogramming = yes; + idr = 0x51; + rampz = 0x00; + spmcr = 0x57; + eecr = 0x3f; + + memory "eeprom" + paged = no; + size = 512; + page_size = 4; + blocksize = 4; + readsize = 4; + num_pages = 128; + ; + + memory "flash" + paged = yes; + size = 40960; + page_size = 128; + blocksize = 128; + readsize = 128; + num_pages = 320; + ; + + memory "hfuse" + size = 1; + ; + + memory "lfuse" + size = 1; + ; + + memory "lockbits" + size = 1; + ; + + memory "signature" + size = 3; + ; +; + +#------------------------------------------------------------ +# AVR8X family common values +#------------------------------------------------------------ + +part + id = ".avr8x"; + desc = "AVR8X family common values"; + has_updi = yes; + nvm_base = 0x1000; + ocd_base = 0x0F80; + + memory "signature" + size = 3; + offset = 0x1100; + ; + + memory "prodsig" + size = 0x3D; + offset = 0x1103; + page_size = 0x3D; + readsize = 0x3D; + ; + + memory "fuses" + size = 9; + offset = 0x1280; + ; + + memory "fuse0" + size = 1; + offset = 0x1280; + ; + + memory "fuse1" + size = 1; + offset = 0x1281; + ; + + memory "fuse2" + size = 1; + offset = 0x1282; + ; + + memory "fuse4" + size = 1; + offset = 0x1284; + ; + + memory "fuse5" + size = 1; + offset = 0x1285; + ; + + memory "fuse6" + size = 1; + offset = 0x1286; + ; + + memory "fuse7" + size = 1; + offset = 0x1287; + ; + + memory "fuse8" + size = 1; + offset = 0x1288; + ; + + memory "lock" + size = 1; + offset = 0x128a; + ; + + memory "data" + # SRAM, only used to supply the offset + offset = 0x1000000; + ; +; + +#------------------------------------------------------------ +# AVR8X tiny family common values +#------------------------------------------------------------ + +part parent ".avr8x" + id = ".avr8x_tiny"; + desc = "AVR8X tiny family common values"; + family_id = "tinyAVR"; + + memory "usersig" + size = 0x20; + offset = 0x1300; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# AVR8X mega family common values +#------------------------------------------------------------ + +part parent ".avr8x" + id = ".avr8x_mega"; + desc = "AVR8X mega family common values"; + family_id = "megaAVR"; + + memory "usersig" + size = 0x40; + offset = 0x1300; + page_size = 0x40; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny202 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t202"; + desc = "ATtiny202"; + signature = 0x1E 0x91 0x23; + + memory "flash" + size = 0x800; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x40; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny204 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t204"; + desc = "ATtiny204"; + signature = 0x1E 0x91 0x22; + + memory "flash" + size = 0x800; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x40; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny402 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t402"; + desc = "ATtiny402"; + signature = 0x1E 0x92 0x23; + + memory "flash" + size = 0x1000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny404 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t404"; + desc = "ATtiny404"; + signature = 0x1E 0x92 0x26; + + memory "flash" + size = 0x1000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny406 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t406"; + desc = "ATtiny406"; + signature = 0x1E 0x92 0x25; + + memory "flash" + size = 0x1000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny804 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t804"; + desc = "ATtiny804"; + signature = 0x1E 0x93 0x25; + + memory "flash" + size = 0x2000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny806 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t806"; + desc = "ATtiny806"; + signature = 0x1E 0x93 0x24; + + memory "flash" + size = 0x2000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny807 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t807"; + desc = "ATtiny807"; + signature = 0x1E 0x93 0x23; + + memory "flash" + size = 0x2000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny1604 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t1604"; + desc = "ATtiny1604"; + signature = 0x1E 0x94 0x25; + + memory "flash" + size = 0x4000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny1606 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t1606"; + desc = "ATtiny1606"; + signature = 0x1E 0x94 0x24; + + memory "flash" + size = 0x4000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny1607 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t1607"; + desc = "ATtiny1607"; + signature = 0x1E 0x94 0x23; + + memory "flash" + size = 0x4000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny212 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t212"; + desc = "ATtiny212"; + signature = 0x1E 0x91 0x21; + + memory "flash" + size = 0x800; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x40; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny214 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t214"; + desc = "ATtiny214"; + signature = 0x1E 0x91 0x20; + + memory "flash" + size = 0x800; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x40; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny412 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t412"; + desc = "ATtiny412"; + signature = 0x1E 0x92 0x23; + + memory "flash" + size = 0x1000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + + +#------------------------------------------------------------ +# ATtiny414 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t414"; + desc = "ATtiny414"; + signature = 0x1E 0x92 0x22; + + memory "flash" + size = 0x1000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny416 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t416"; + desc = "ATtiny416"; + signature = 0x1E 0x92 0x21; + + memory "flash" + size = 0x1000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + + +#------------------------------------------------------------ +# ATtiny417 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t417"; + desc = "ATtiny417"; + signature = 0x1E 0x92 0x20; + + memory "flash" + size = 0x1000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + + +#------------------------------------------------------------ +# ATtiny814 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t814"; + desc = "ATtiny814"; + signature = 0x1E 0x93 0x22; + + memory "flash" + size = 0x2000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + + +#------------------------------------------------------------ +# ATtiny816 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t816"; + desc = "ATtiny816"; + signature = 0x1E 0x93 0x21; + + memory "flash" + size = 0x2000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny817 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t817"; + desc = "ATtiny817"; + signature = 0x1E 0x93 0x20; + + memory "flash" + size = 0x2000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x80; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny1614 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t1614"; + desc = "ATtiny1614"; + signature = 0x1E 0x94 0x22; + + memory "flash" + size = 0x4000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny1616 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t1616"; + desc = "ATtiny1616"; + signature = 0x1E 0x94 0x21; + + memory "flash" + size = 0x4000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny1617 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t1617"; + desc = "ATtiny1617"; + signature = 0x1E 0x94 0x20; + + memory "flash" + size = 0x4000; + offset = 0x8000; + page_size = 0x40; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x20; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny3214 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t3214"; + desc = "ATtiny3214"; + signature = 0x1E 0x95 0x20; + + memory "flash" + size = 0x8000; + offset = 0x8000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x40; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny3216 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t3216"; + desc = "ATtiny3216"; + signature = 0x1E 0x95 0x21; + + memory "flash" + size = 0x8000; + offset = 0x8000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x40; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATtiny3217 +#------------------------------------------------------------ + +part parent ".avr8x_tiny" + id = "t3217"; + desc = "ATtiny3217"; + signature = 0x1E 0x95 0x22; + + memory "flash" + size = 0x8000; + offset = 0x8000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x40; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATmega3208 +#------------------------------------------------------------ + +part parent ".avr8x_mega" + id = "m3208"; + desc = "ATmega3208"; + signature = 0x1E 0x95 0x52; + + memory "flash" + size = 0x8000; + offset = 0x4000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x40; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATmega3209 +#------------------------------------------------------------ + +part parent ".avr8x_mega" + id = "m3209"; + desc = "ATmega3209"; + signature = 0x1E 0x95 0x53; + + memory "flash" + size = 0x8000; + offset = 0x4000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x40; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATmega4808 +#------------------------------------------------------------ + +part parent ".avr8x_mega" + id = "m4808"; + desc = "ATmega4808"; + signature = 0x1E 0x96 0x50; + + memory "flash" + size = 0xC000; + offset = 0x4000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x40; + readsize = 0x100; + ; +; + +#------------------------------------------------------------ +# ATmega4809 +#------------------------------------------------------------ + +part parent ".avr8x_mega" + id = "m4809"; + desc = "ATmega4809"; + signature = 0x1E 0x96 0x51; + + memory "flash" + size = 0xC000; + offset = 0x4000; + page_size = 0x80; + readsize = 0x100; + ; + + memory "eeprom" + size = 0x100; + offset = 0x1400; + page_size = 0x40; + readsize = 0x100; + ; +; diff --git a/tools/2024/bootonly.sh b/tools/2024/bootonly.sh new file mode 100755 index 0000000..8571a24 --- /dev/null +++ b/tools/2024/bootonly.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Burn only the bootloader via ICSP (no application). +: "${WII5_AVRDUDE:=avrdude}" + +"$WII5_AVRDUDE" \ + -C./avrdude.conf \ + -v \ + -patmega2560 \ + -cstk500v2 \ + -Pusb \ + -Ulock:w:0x0F:m \ + -Uflash:w:optiboot_flash_atmega2560_UART0_230400_11059200L_BIGBOOT.hex diff --git a/tools/2024/ui.txt b/tools/2024/ui.txt new file mode 100644 index 0000000..421f806 --- /dev/null +++ b/tools/2024/ui.txt @@ -0,0 +1,7 @@ +# Reference snippet (not directly executable): the avrdude command for +# burning the stk500v2 mega2560 bootloader. Use parameterized scripts in this +# directory instead. +# +# avrdude -C -v -patmega2560 -cstk500v2 -Pusb \ +# -Uflash:w::i \ +# -Ulock:w:0x0F:m diff --git a/tools/2024/upload.sh b/tools/2024/upload.sh new file mode 100755 index 0000000..d8580d9 --- /dev/null +++ b/tools/2024/upload.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Verify the WII5 sketch with the local Arduino IDE, then rsync the resulting +# .hex to a remote build host. +# Usage: upload.sh +# - the remote-side ssh user is taken from $WII5_REMOTE_USER (default: wii) +# - the local Arduino build dir is ~/Arduino/build by default; override with +# $WII5_BUILD_DIR. +: "${WII5_REMOTE_USER:=wii}" +: "${WII5_BUILD_DIR:=$HOME/Arduino/build}" +: "${WII5_ARDUINO_BIN:=arduino}" + +set -e + +REMOTE=$1 +[ -z "$REMOTE" ] && { echo "Usage: $0 "; exit 1; } + +rm -f "$WII5_BUILD_DIR/wii5_buoy.ino.hex" +"$WII5_ARDUINO_BIN" --verify app/wii5_buoy/wii5_buoy.ino \ + --board WII:5.1.0:wii5_v2_2560_3V:cpu=atmega25603V3_11MHz230400 -v +rsync -av "$WII5_BUILD_DIR/wii5_buoy.ino.hex" "$WII5_REMOTE_USER@$REMOTE:~/" diff --git a/tools/2024/wii5.sh b/tools/2024/wii5.sh new file mode 100755 index 0000000..7da5abf --- /dev/null +++ b/tools/2024/wii5.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Burn WII5 application + bootloader via ICSP. +# See http://eleccelerator.com/fusecalc/fusecalc.php?chip=atmega2560 +# lfuse 0xff - external oscillator +# hfuse 0xd8 +# efuse 0xfd - BOD Level 1 = 2.7V +: "${WII5_AVRDUDE:=avrdude}" + +FULL_HEX=wii5_buoy_latest.hex +CONFIGFILE=./avrdude.conf +BOOT_HEX=optiboot_flash_atmega2560_UART0_230400_11059200L_BIGBOOT.hex + +"$WII5_AVRDUDE" \ + -C"$CONFIGFILE" -v \ + -v \ + -patmega2560 \ + -cstk500v2 \ + -Pusb \ + -Uflash:w:"$FULL_HEX":i \ + -Uboot:w:"$BOOT_HEX":i \ + -Ulock:w:0 diff --git a/tools/bits/bits.c b/tools/bits/bits.c new file mode 100644 index 0000000..f8b3827 --- /dev/null +++ b/tools/bits/bits.c @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +// +// This file is part of WII5 Buoy firmware. +// See LICENSE for full terms. + +#define BitVal(data,y) ( (data>>y) & 1) /** Return Data.Y value **/ +#define SetBit(data,y) data |= (1 << y) /** Set Data.Y to 1 **/ +#define ClearBit(data,y) data &= ~(1 << y) /** Clear Data.Y to 0 **/ + + +#include +#include + +int main() { + + uint32_t out = 0; + + // Main header - where am I, what is my voltss + SetBit(out, 1); + + // Standard WII output + SetBit(out, 11); + SetBit(out, 12); + SetBit(out, 13); + SetBit(out, 14); + + printf("New Number = %d\n", out); + return(0); +} diff --git a/tools/build_local.sh b/tools/build_local.sh new file mode 100755 index 0000000..335ac29 --- /dev/null +++ b/tools/build_local.sh @@ -0,0 +1,35 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Build and flash the WII5 Buoy firmware locally. Configure via env vars: +: "${WII5_SERIAL:?set to your serial port, e.g. /dev/ttyUSB0}" +: "${WII5_FULL_HEX:?path to the built .hex file, e.g. \$BUILD_DIR/wii5_buoy.ino.hex}" +: "${WII5_AVRDUDE_CONF:?path to avrdude.conf}" +: "${WII5_ARDUINO_BIN:=arduino}" +: "${WII5_AVRDUDE_BIN:=avrdude}" + +set -e + +VERSION="dev" +SHORT=$(git log -1 --pretty=format:%h) +VERSION_STRING="WII5Buoy_$SHORT" + +# TODO: Inject VERSION via build_local.sh argument or VERSION header file +"$WII5_ARDUINO_BIN" \ + --pref compiler.cpp.extra_flags="-DWII5_SOFTWARE_VERSION=\"$VERSION_STRING\" -DWII5_SOFTWARE_COMMIT=\"$SHORT\" " \ + --verify app/wii5_buoy/wii5_buoy.ino \ + --board WII:avr:wii5_v2_2560_3V:cpu=atmega25603V3_11MHz230400 + +sleep 1 + +"$WII5_AVRDUDE_BIN" \ + -C"$WII5_AVRDUDE_CONF" -v \ + -patmega2560 \ + -carduino \ + -P"$WII5_SERIAL" -b230400 \ + -D \ + -Uflash:w:"$WII5_FULL_HEX":i diff --git a/tools/build_test.sh b/tools/build_test.sh new file mode 100755 index 0000000..5c160c1 --- /dev/null +++ b/tools/build_test.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Verify a single test sketch under test//.ino. +# Configure with env vars: +: "${WII5_ARDUINO_BIN:=arduino}" +: "${WII5_BUILD_DIR:?set to your Arduino build dir}" +: "${WII5_FIRMWARE_REPO:?path to firmware-archive git repo}" + +set -e + +TEST=$1 +[ -z "$TEST" ] && { echo "Usage: $0 "; exit 1; } + +echo "Building test/$TEST" + +# TODO: Inject WII5_SOFTWARE_VERSION via --pref compiler.cpp.extra_flags +"$WII5_ARDUINO_BIN" \ + --verify "test/$TEST/$TEST.ino" \ + --board WII:avr:wii5_v2_2560_3V:cpu=atmega25603V3_11MHz230400 + +sleep 1 +cp "$WII5_BUILD_DIR/$TEST.ino.hex" "$WII5_FIRMWARE_REPO/" +pushd "$WII5_FIRMWARE_REPO/" +git add "$TEST.ino.hex" +git commit -am "Auto build for TEST = $TEST" +git push +popd diff --git a/tools/build_upload.sh b/tools/build_upload.sh new file mode 100755 index 0000000..b0375d9 --- /dev/null +++ b/tools/build_upload.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Verify the sketch with the Arduino IDE, then rsync the resulting hex to a +# remote build host. Configure with env vars (or copy to a *.local.sh and edit): +: "${WII5_ARDUINO_BIN:=arduino}" # path to Arduino IDE binary +: "${WII5_BUILD_DIR:?set to your Arduino build dir}" +: "${WII5_AVRDUDE_CONF:?path to avrdude.conf}" +: "${WII5_REMOTE:?e.g. user@host}" +: "${WII5_REMOTE_PORT:=22}" + +"$WII5_ARDUINO_BIN" \ + --verify app/wii5_buoy/wii5_buoy.ino \ + --board WII:avr:wii5_v2_2560_3V:cpu=atmega25603V3_11MHz230400 + +rsync -av -e "ssh -p $WII5_REMOTE_PORT" \ + "$WII5_AVRDUDE_CONF" "$WII5_BUILD_DIR"/*.hex \ + "$WII5_REMOTE:~/firmware/" diff --git a/tools/build_version.sh b/tools/build_version.sh new file mode 100755 index 0000000..5d997e6 --- /dev/null +++ b/tools/build_version.sh @@ -0,0 +1,67 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Build a versioned firmware hex and (optionally) commit it into a separate +# firmware-archive repo. +# +# Usage: build_version.sh [VERSION_TAG] +# - With VERSION_TAG: checks out that tag, builds, names the hex +# wii5_buoy_.hex, also creates wii5_buoy_latest.hex symlink. +# - Without: builds the current HEAD as a 'dev' build. +# +# Configure via env vars: +: "${WII5_BUILD_DIR:?set to your Arduino build dir}" +: "${WII5_FIRMWARE_REPO:?path to firmware-archive git repo}" +: "${WII5_ARDUINO_BIN:=arduino}" + +set -e + +VERSION=$1 +SHORT=$(git log -1 --pretty=format:%h) +VERSION_STRING="WII5Buoy_$VERSION" +LINK="" +INTVER="" + +if [[ $VERSION == '' ]] +then + # TODO: keep versions with SHORT and link "latest" -> latest dev + VERSION_STRING="WII5Buoy_dev_$SHORT" + VERSION="dev" + INTVER="3" +else + git checkout $VERSION + LINK="latest" + INTVER=$(perl tools/integer_version.pl $VERSION) +fi + +echo "Getting and Building $VERSION with $VERSION_STRING" + +"$WII5_ARDUINO_BIN" \ + --pref compiler.cpp.extra_flags="-DWII5_SOFTWARE_VERSION=\"$VERSION_STRING\" -DWII5_SOFTWARE_COMMIT=\"$SHORT\" -DWII5_SOFTWARE_INTVER=$INTVER" \ + --verify app/wii5_buoy/wii5_buoy.ino \ + --board WII:avr:wii5_v2_2560_3V:cpu=atmega25603V3_11MHz230400 + +sleep 1 +pushd "$WII5_FIRMWARE_REPO" +git pull +cp "$WII5_BUILD_DIR/wii5_buoy.ino.hex" "$WII5_FIRMWARE_REPO/wii5_buoy_$VERSION.hex" +if [[ $LINK == '' ]] +then + echo "No link to DEV" +else + echo "Creating link to latest" + touch wii5_buoy_$LINK.hex + rm wii5_buoy_$LINK.hex + ln -s wii5_buoy_$VERSION.hex wii5_buoy_$LINK.hex + git add wii5_buoy_$LINK.hex +fi +git add wii5_buoy_$VERSION.hex +git commit -am "Auto build $VERSION $VERSION_STRING" +git push +popd + +git checkout master diff --git a/tools/copy_libs.sh b/tools/copy_libs.sh new file mode 100755 index 0000000..6968c56 --- /dev/null +++ b/tools/copy_libs.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Copy the Arduino libraries this project depends on into a target directory +# (typically your project's src/ tree, or an arduino-cli libraries dir). +# +# Configure with env vars: +: "${WII5_LIB_SRC:?source dir holding all the Arduino library trees, e.g. ~/Arduino/libraries}" +: "${WII5_LIB_DEST:?destination dir, e.g. ./src or ~/Arduino/libraries}" + +cd "$WII5_LIB_SRC" + +rsync -av \ + ADS1247 \ + AltSoftSerial \ + Arduino-EEPROMex \ + ArduinoUniqueID \ + Arduino_JSON \ + ButtonEvent \ + CRC32 \ + CalLib \ + DCF77 \ + DS1307RTC \ + DS3231 \ + DS3231RTC \ + DS3232RTC \ + DallasTemperature \ + DueFlash \ + Enerlib \ + FTRGBLED \ + Filters \ + I2C \ + I2Cdev \ + IridiumSBD \ + LowPower \ + Manchester \ + MemoryFree \ + OneWire \ + PCM \ + PushButton \ + RFM69 \ + RTArduLink \ + RTC \ + RTCTimedEvent \ + RTClib \ + RTIMULib \ + RunningMedian \ + SDBlock \ + SDCore \ + SFE_BMP180 \ + SPIFlash \ + SdFat \ + Sensirion \ + SoftReset \ + Statistic \ + TemperatureController \ + Time \ + TimeAlarms \ + TimedEvent \ + TimerOne \ + TinyGPS \ + TinyGPSPlus \ + TrueRandom-master \ + Ultrasonic \ + XModem \ + elapsedMillis \ + keypad \ + RadioHead \ + SdFs \ + "$WII5_LIB_DEST/" diff --git a/tools/icsp.sh b/tools/icsp.sh new file mode 100644 index 0000000..aeb2b55 --- /dev/null +++ b/tools/icsp.sh @@ -0,0 +1,30 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Burn application + bootloader to an ATmega2560 over an ICSP programmer +# (e.g. AVRISP mkII / Atmel STK500v2). +# +# Configure these env vars: +: "${WII5_AVRDUDE:?path to avrdude binary}" +: "${WII5_AVRDUDE_CONF:?path to avrdude.conf}" +: "${WII5_FULL_HEX:?path to wii5_buoy.ino.hex}" +: "${WII5_BOOT_HEX:?path to optiboot bootloader hex}" + +"$WII5_AVRDUDE" \ + -C"$WII5_AVRDUDE_CONF" -v \ + -patmega2560 \ + -cstk500v2 \ + -Pusb \ + -D \ + -Uflash:w:"$WII5_FULL_HEX" \ + -Uboot:w:"$WII5_BOOT_HEX" \ + -Ulock:w:0x0F:m + +# Notes: +# To set fuses on a fresh chip: +# avrdude ... -e -Ulock:w:0x3F:m -Uefuse:w:0xFD:m -Uhfuse:w:0xd6:m -Ulfuse:w:0xf7:m +# Then to flash bootloader + app, run this script. diff --git a/tools/integer_version.pl b/tools/integer_version.pl new file mode 100644 index 0000000..c793a54 --- /dev/null +++ b/tools/integer_version.pl @@ -0,0 +1,37 @@ +#!/usr/bin/perl +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + + +my $ver = $ARGV[0]; +@BITS = split(/\./, $ver); +$BITS[0] = substr($BITS[0],1); +#print join("\n", @BITS) . "\n"; + +my $newval = (65536 * $BITS[0]); +$newval = $newval + (256 * $BITS[1]); +$newval = $newval + $BITS[2]; +print $newval . "\n"; + +__END__ +if ($ver =~ /(\d+)\.(\d+)\.(\d+)/) { + my $p1 = $1+0; my $p2 = $2+0; my $p3 = $3+0; + print "$p1 $p2 $p3\n"; + my $F1 = $p1*(2^16); + print $F1 . "\n"; + my $F2 = $p2*(2^8); + print "$F1 $F2 $F3\n"; + my $F3 = $p3; + print "$F1 $F2 $F3\n"; + print "$F1 $F2 $F3\n"; + my $ver = $F1 + $F2 + $F3; + + $ver = ($p1*(2^16))+($p2*(2^8))+$p3; + print $ver . "\n"; +} +else { + $ver = 0; +} diff --git a/tools/memory.pl b/tools/memory.pl new file mode 100644 index 0000000..8f668bd --- /dev/null +++ b/tools/memory.pl @@ -0,0 +1,111 @@ +#!/usr/bin/perl +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Group ELF symbols by subsystem (Network, Storage, Sparton, GPS, Iridium, +# Serial, Console, Sh3d, WII5, Other) and report sizes. Useful for understanding +# where the firmware is spending its program/data memory. +# +# Configure with env vars: +# WII5_AVR_READELF = path to avr-readelf binary +# WII5_ELF = path to wii5_buoy.ino.elf +# +# Usage: WII5_AVR_READELF=... WII5_ELF=... perl tools/memory.pl [items_max] + +use strict; +use warnings; +use Data::Dumper; + +my $items_max = $ARGV[0] || 0; +my $exe_read = $ENV{WII5_AVR_READELF} or die "WII5_AVR_READELF not set\n"; +my $elf = $ENV{WII5_ELF} or die "WII5_ELF not set\n"; + +my $fh; +open ($fh, "$exe_read -a $elf |grep OBJECT |") or die "Cannot run readelf: $!\n"; + +my $grouped = { + Serial => {total => 0, items => {}}, + Network => {total => 0, items => {}}, + Console => {total => 0, items => {}}, + Sh3d => {total => 0, items => {}}, + Storage => {total => 0, items => {}}, + Sparton => {total => 0, items => {}}, + GPS => {total => 0, items => {}}, + Iridium => {total => 0, items => {}}, + WII5 => {total => 0, items => {}}, + Other => {total => 0, items => {}}, +}; + +while (<$fh>) { + chomp; + # Sample line layout (column-aligned): + # 1815: 00801796 1 OBJECT GLOBAL HIDDEN 3 _ZN7TwoWire12transmitting + + my $size = substr($_, 16, 6); + my $name = substr($_, 51, 100); + + if ( + ($name =~ /network/i) + || ($name =~ /radio/i) + ) { + $grouped->{Network}{total} += $size; + $grouped->{Network}{items}{$name} = $size; + } + elsif ( + ($name =~ /storage/i) + || ($name =~ /Sd/) + ) { + $grouped->{Storage}{total} += $size; + $grouped->{Storage}{items}{$name} = $size; + } + elsif ($name =~ /sparton/i) { + $grouped->{Sparton}{total} += $size; + $grouped->{Sparton}{items}{$name} = $size; + } + elsif ($name =~ /gps/i) { + $grouped->{GPS}{total} += $size; + $grouped->{GPS}{items}{$name} = $size; + } + elsif ($name =~ /iridium/i) { + $grouped->{Iridium}{total} += $size; + $grouped->{Iridium}{items}{$name} = $size; + } + elsif ($name =~ /serial/i) { + $grouped->{Serial}{total} += $size; + $grouped->{Serial}{items}{$name} = $size; + } + elsif ($name =~ /console/i) { + $grouped->{Console}{total} += $size; + $grouped->{Console}{items}{$name} = $size; + } + elsif ($name =~ /sh3d/i) { + $grouped->{Sh3d}{total} += $size; + $grouped->{Sh3d}{items}{$name} = $size; + } + elsif ($name =~ /wii5/i) { + $grouped->{WII5}{total} += $size; + $grouped->{WII5}{items}{$name} = $size; + } + else { + $grouped->{Other}{total} += $size; + $grouped->{Other}{items}{$name} = $size; + } +} + +foreach my $group_key (sort {$grouped->{$b}{total} <=> $grouped->{$a}{total} } keys %$grouped) { + print "$group_key => " . $grouped->{$group_key}{total} . "\n"; + my $item_count = 0; + foreach my $item_key ( + sort { + $grouped->{$group_key}{items}{$b} <=> $grouped->{$group_key}{items}{$a} + } keys %{$grouped->{$group_key}{items}} + ) { + if ($item_count < $items_max) { + print " $item_key => " . $grouped->{$group_key}{items}{$item_key} . "\n"; + } + $item_count++; + } +} diff --git a/tools/pull.sh b/tools/pull.sh new file mode 100755 index 0000000..f8cc10d --- /dev/null +++ b/tools/pull.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Pull the latest from each related repo. Configure with env vars; missing +# vars are skipped silently so this works regardless of which repos you have +# checked out locally. + +set -e + +for d in "$WII5_LIB_REPO" "$WII5_SDBLOCK_REPO" "$WII5_BINARY_REPO" "$WII5_BUOY_REPO"; do + [ -n "$d" ] && [ -d "$d" ] || continue + pushd "$d" >/dev/null + git pull + popd >/dev/null +done diff --git a/tools/tag_version.sh b/tools/tag_version.sh new file mode 100755 index 0000000..40ec5dc --- /dev/null +++ b/tools/tag_version.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + + +#get highest tag number +VERSION=`git describe --abbrev=0 --tags` +SHORT=$(git log -1 --pretty=format:%h) + +#replace . with space so can split into an array +VERSION_BITS=(${VERSION//./ }) + +#get number parts and increase last one by 1 +VNUM1=${VERSION_BITS[0]} +VNUM2=${VERSION_BITS[1]} +VNUM3=${VERSION_BITS[2]} +VNUM3=$((VNUM3+1)) + +#create new tag +NEW_TAG="$VNUM1.$VNUM2.$VNUM3" + +echo "Updating $VERSION to $NEW_TAG" + +#get current hash and see if it already has a tag +GIT_COMMIT=`git rev-parse HEAD` +NEEDS_TAG=`git describe --contains $GIT_COMMIT 2>/dev/null` + +#only tag if no tag already +if [ -z "$NEEDS_TAG" ]; then + echo "version=$NEW_TAG" > VERSION + echo "short=$SHORT" >> VERSION + git add VERSION + git commit -m 'Auto update version file' + git tag $NEW_TAG + echo "Tagged with $NEW_TAG" + git push --tags +else + echo "Already a tag on this commit" +fi diff --git a/tools/updatelocaldev.sh b/tools/updatelocaldev.sh new file mode 100755 index 0000000..1597fa2 --- /dev/null +++ b/tools/updatelocaldev.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Flash the most-recently-built local hex via the standard arduino bootloader. +# Configure with env vars: +: "${WII5_SERIAL:=/dev/ttyUSB0}" +: "${WII5_FULL_HEX:?path to wii5_buoy.ino.hex}" +: "${WII5_AVRDUDE:=avrdude}" +: "${WII5_AVRDUDE_CONF:?path to avrdude.conf}" + +"$WII5_AVRDUDE" \ + -C"$WII5_AVRDUDE_CONF" -v \ + -patmega2560 \ + -carduino \ + -P"$WII5_SERIAL" -b230400 \ + -D \ + -Uflash:w:"$WII5_FULL_HEX":i diff --git a/tools/upload.sh b/tools/upload.sh new file mode 100755 index 0000000..e25ccc7 --- /dev/null +++ b/tools/upload.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Upload built firmware to a remote build host. +# Set these env vars (or copy this file to upload.local.sh and edit): +: "${WII5_BUILD_DIR:?set to your Arduino build dir, e.g. ~/Documents/Arduino/Build}" +: "${WII5_AVRDUDE_CONF:?path to avrdude.conf}" +: "${WII5_REMOTE:?e.g. user@host}" +: "${WII5_REMOTE_PORT:=22}" + +rsync -av -e "ssh -p $WII5_REMOTE_PORT" \ + "$WII5_AVRDUDE_CONF" "$WII5_BUILD_DIR"/*.hex \ + "$WII5_REMOTE:~/firmware/" diff --git a/tools/variables.sh b/tools/variables.sh new file mode 100755 index 0000000..7aeed75 --- /dev/null +++ b/tools/variables.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +VERSION=$1 +SHORT=$(git log -1 --pretty=format:%h) +VERSION_STRING="WII5Buoy_$VERSION" + +echo "VERSION=$VERSION" +echo "SHORT=$SHORT" +echo "VERSION_STRING=$VERSION_STRONG" diff --git a/tools/verify.sh b/tools/verify.sh new file mode 100755 index 0000000..1ff975a --- /dev/null +++ b/tools/verify.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +# +# This file is part of WII5 Buoy firmware. +# See LICENSE for full terms. + +# Smoke-verify that the main sketch compiles cleanly. +: "${WII5_ARDUINO_BIN:=arduino}" + +# TODO: pass WII5_SOFTWARE_VERSION via --pref compiler.cpp.extra_flags +"$WII5_ARDUINO_BIN" \ + --verify app/wii5_buoy/wii5_buoy.ino \ + --board WII:avr:wii5_v2_2560_3V:cpu=atmega25603V3_11MHz230400 \ + -v