├── .gitignore ├── Makefile ├── PROTOCOL.md ├── README.md ├── TODO.md ├── data ├── ajax-loader.gif ├── chart.min.js.gz ├── chartjs-annotation.min.js.gz ├── distribution.files.lst ├── distribution.files.lst.orig ├── docstrings.js ├── gauge.min.js.gz ├── gauges.html ├── gauges.js ├── icon-check-circle.png ├── icon-trash.png ├── icon-x-square.png ├── index.html ├── index.js ├── inverter.js ├── jquery.core.min.js.gz ├── jquery.knob.min.js.gz ├── log.html ├── log.js ├── logo.png ├── modal.js ├── plot.js ├── refresh.png ├── remote.html ├── style.css ├── syncofs.html ├── ui.js ├── wifi-updated.html ├── wifi.html └── wifi.js ├── doc ├── ARDUINO_IDE_setup.md ├── ARDUINO_IDE_usage.md ├── PLATFORMIO_setup.md └── PLATFORMIO_usage.md ├── esp8266-web-interface.ino ├── platformio-local-override.ini.example ├── platformio.ini ├── src ├── LICENSE ├── arm_debug.cpp ├── arm_debug.h ├── arm_reg.h └── flashloader │ ├── LICENSE │ ├── Makefile │ ├── linker.ld │ ├── stm32f0.h │ ├── stm32f0.s │ ├── stm32x.h │ └── stm32x.s ├── svg ├── activity.svg ├── check-circle.svg ├── cpu.svg ├── database.svg ├── download.svg ├── file-text.svg ├── grid.svg ├── help-circle.svg ├── pause.svg ├── play.svg ├── plus-circle.svg ├── repeat.svg ├── rotate-ccw.svg ├── save.svg ├── slider.svg ├── square.svg ├── target.svg ├── terminal.svg ├── trash-2.svg ├── trash.svg ├── upload-cloud.svg └── wifi.svg └── upload.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .pio/ 3 | platformio-local-override.ini 4 | tmp 5 | 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | cwd := $(shell pwd) 3 | DISTFILES := $(shell cat OpenInverterWeb/data/distribution.files.lst) 4 | 5 | all: install 6 | 7 | install: 8 | @for distfile in $(shell cat OpenInverterWeb/data/distribution.files.lst); do \ 9 | echo $${distfile} ; \ 10 | curl -F "data=@$$distfile" http://${INVERTER_IP}/edit ; \ 11 | done 12 | -------------------------------------------------------------------------------- /PROTOCOL.md: -------------------------------------------------------------------------------- 1 | # Openinverter Web Interface Protocol 2 | 3 | This document describes the protocol used on the serial interface between this ESP8266 module, and 4 | an inverter or VCU. 5 | 6 | ## General 7 | 8 | Commands are sent by the ESP8266. Each command consists of a single line consisting of a command word 9 | followed optionally by parameters and terminates with a newline character. Commands must be echoed back 10 | to the ESP8266. 11 | 12 | Following the echo, the esp8266 will receive an unlimited quantity of response data, terminated by a 13 | 100ms timeout. 14 | 15 | Except where otherwise noted, the responses are free text and should generally contain a single human 16 | readable line indicating success or failure of the operation. 17 | 18 | ## Parameters and other data 19 | 20 | Openinverter makes available two types of data - parameters and non-parameters. 21 | 22 | Parameters are user configurable values, generally used for configuration. They can be stored in 23 | nonvolatile memory and should not change except in response to a user request. 24 | 25 | Other values are made available that are not configurable, but are instead indicative of the immediate 26 | state of the inverter. These are useful for monitoring and debugging. 27 | 28 | ## Commands 29 | 30 | | Command | Description| 31 | |---------|------------| 32 | |`save`|save current parameters to nonvolatile memory| 33 | |`load`|load parameters from nonvolatile memory| 34 | |`fastuart`|increases the baud rate to 921600Response must begin "OK" in success case| 35 | |`set [parameter] [value]`|set the decimal value of a named parameter| 36 | |`can [direction] [name] [canid] [offset] [length] [gain]`|map values to CAN messages| 37 | |`can clear`|clear all can mappings| 38 | |`start [opmode]`|start the inverter in a specified mode. Mode 2 is manual run| 39 | |`stop`|stop the inverter| 40 | |`get [parameter]`|get the value of a parameter| 41 | |`stream [repetitions] [val1,val2,val3]`| repeatedly read and return one or more values| 42 | |`json [hidden]`| return an JSON encoded mapping of all parameters and values - see JSON format below| 43 | |`errors`|print information about all currently active error states, or indicate that everything is okay| 44 | |`reset`|reboot the device| 45 | |`defaults`|restore all parameters to default values| 46 | 47 | Note: This is not an exhaustive list of commands supported by openinverter devices, but does include 48 | all commands currently used by the openinverter web intrface. 49 | 50 | ## JSON Mapping 51 | 52 | The json command requests a dump of the full schema and values of both the configurable parameters 53 | and other available data. The optional "hidden" flag requests data that would not normally be 54 | displayed to the user. 55 | 56 | The following example shows a non-parameter value, a parameter value, and a value that has been 57 | mapped to CAN. 58 | 59 | ```json 60 | { 61 | "udc": {"unit":"V", "value": 400.0, "isparam": false}, 62 | "fweak": {"unit":"Hz", "value": 67.0, "isparam": true, "minimum": 0.0, "maximum": 400.0, "default": 67.0, "category": "Motor (sine)", "i": 8}, 63 | "speed": {"unit":"rpm", "value": 1000.0, "isparam": false, "canid": 123, "canoffset":0, "canlength":32, "cangain":5, "isrx": false} 64 | } 65 | ``` 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | esp8266-web-interface 2 | ===================== 3 | Web interface for Huebner inverter 4 | 5 | # Table of Contents 6 |
7 | Click to open TOC 8 | 9 | 10 | - [About](#about) 11 | - [Usage](#usage) 12 | - [Wifi network](#wifi-network) 13 | - [Reaching the board](#reaching-the-board) 14 | - [Hardware](#hardware) 15 | - [Firmware](#firmware) 16 | - [Flashing / Upgrading](#flashing--upgrading) 17 | - [Wirelessly](#wirelessly) 18 | - [Wired](#wired) 19 | - [Documentations](#documentations) 20 | - [Development](#development) 21 | - [Arduino](#arduino) 22 | - [PlatformIO](#platformio) 23 | 24 | 25 |
26 | 27 | # About 28 | This repository hosts the source code for the Web Interface for the Huebner inverter, and derivated projects: 29 | * [OpenInverter Sine (and FOC) firmware](https://github.com/jsphuebner/stm32-sine) 30 | * [Vehicle Control Unit for Electric Vehicle Conversion Projects](https://github.com/damienmaguire/Stm32-vcu) 31 | * [OpenInverter buck or boost mode charger firmware](https://github.com/jsphuebner/stm32-charger) 32 | * [OpenInverter non-grid connected inverter](https://github.com/jsphuebner/stm32-island) 33 | * [BMS project firmware](https://github.com/jsphuebner/bms-software) 34 | * ... 35 | 36 | It is written with the Arduino development environment and libraries. 37 | 38 | # Usage 39 | To use the web interface 2 things are needed : 40 | * You need to have a computer on the same WiFi network as the board, 41 | * You need to 'browse' the web interface page. 42 | 43 | ## Wifi network 44 | There are 2 possibilities: 45 | * Either you connect to an Access Point generated by the board. The default name for this access point is 'ESP-xxxxx' but can be customized. In that case, the board will have a fixed IP address of `192.168.4.1` (and will be reachable on http://192.168.4.1/) 46 | * Or you can configure the board to join your own WiFi network ; and in that case you may need to tweak your network configuration to provide a fixed address to the board (not necessary). 47 | 48 | ## Reaching the board 49 | The board announces itself to the world using mDNS protocol (aka Bonjour, or Rendezvous, or Zeroconf), so you may be able to reach the board using a local name of `inverter.local`. 50 | So first try to reach it on http://inverter.local/ 51 | 52 | # Hardware 53 | The web interface has been initially designed to run on ESP8266 boards, such as: 54 | * [Olimex MOD-WIFI-ESP8266](https://www.olimex.com/Products/IoT/ESP8266/MOD-WIFI-ESP8266/open-source-hardware) 55 | 56 | (Pay attention to the SPI flash chip on your board: some need a special mode `QOUT` instead of `QIO` for programming) 57 | 58 | You can buy pre-programmed boards: 59 | * [OpenInverter shop](https://openinverter.org/shop/index.php?route=product/product&path=59&product_id=56) 60 | * [EVBMW Shop](https://www.evbmw.com/index.php/evbmw-webshop/vcu-boards/wifi-progged) (Note : you can choose between OpenInverter or Lexus VCU firmware version ) 61 | 62 | # Firmware 63 | You can find pre-compiled versions of the firmware on the [OpenInverter forum](https://openinverter.org/forum), or can compile it yourself 64 | by following the [instructions below](#development). 65 | 66 | # Flashing / Upgrading 67 | ## Wirelessly 68 | If your board is already programmed with this esp8266-web-interface firmware, or with a firmware that has either ESP8266HTTPUpdateServer or ArduinoOTA components compiled in (it may be the case with the default firmware when you buy a new module), and if you can already reach it (WiFi + network); then you can use one of these approachs: 69 | 70 | * Using ESP8266HTTPUpdateServer component 71 | * Either go to http://inverter.local/update and upload the binary firmware file 72 | * Or use this command line `curl -F "image=@firmware.bin" inverter.local/update` 73 | * Using the ArduinoOTA component 74 | * Use the `espota.py` tool (available in the Arduino tools) to upload either a binary firmware file, or a binary filesystem file: 75 | * Firmware: `python ..../Arduino15/packages/esp8266/hardware/esp8266/3.0.2/tools/espota.py -i esp8266-761bb8.local --progress --file firmware.bin` 76 | * Filesystem: `python ..../Arduino15/packages/esp8266/hardware/esp8266/3.0.2/tools/espota.py -i esp8266-761bb8.local --spiffs --progress --file spiffs.bin` 77 | * Using your development environment (see the [instructions below](#development)) 78 | 79 | ## Wired 80 | If your board is new and unprogrammed, or if you want to fully re-program it, you'll need to have a wired connection between your computer and the board. 81 | Assuming you're using the original Olimex board, you'll need : 82 | * A 3.3v capable USB / Serial adapter 83 | * the following connections: 84 | 85 | Pin# | ESP8266 Board Function | USB / Serial adapter 86 | ----- | ---------------------- | -------------------- 87 | 1 | +3.3v input | (Some adapters provide a +3.3v output, you can use it) 88 | 2 | GND | GND 89 | 3 | RXD input | TXD output 90 | 4 | TXD output | RXD input 91 | 92 | Then you would use any of the the [development tool below](#development) ; or the `esptool.py` tool to upload either a binary firmware file, or a binary filesystem file. 93 | 94 | # Documentations 95 | * [Openinverter Web Interface Protocol](PROTOCOL.md) 96 | 97 | # Development 98 | You can choose between the following tools: 99 | 100 | ## Arduino 101 | [Arduino IDE](https://www.arduino.cc/en/software) is an easy-to-use desktop IDE, which provides a quick and integrated way to develop and update your board. 102 | * [Initial setup](doc/ARDUINO_IDE_setup.md) 103 | * [Day to day usage](doc/ARDUINO_IDE_usage.md) 104 | 105 | ## PlatformIO 106 | [PlatformIO](https://platformio.org/) is a set of tools, among which [PlatformIO Core (CLI)](https://docs.platformio.org/en/latest/core/index.html) is a command line interface that can be used to build many kind of projects. In particular Arduino-based projects like this one. 107 | (Note: even if PlatformIO provides an IDE, these instructions only target the CLI.) 108 | * [Initial setup](doc/PLATFORMIO_setup.md) 109 | * [Day to day usage](doc/PLATFORMIO_usage.md) 110 | 111 | # OpenInverter esp8266 Web Interface (Huebner Inverter) 112 | 113 | This repository contains the software which runs on the esp8266 WiFi modules 114 | used as part of the OpenInverter project. Its purpose is to provide a web 115 | interface for the configuration and monitoring of an OpenInverter based system. 116 | 117 | There are two parts to the esp8266 WiFi module software - a firmware and a web 118 | application. The firmware implements a HTTP API and a small web server. The HTTP 119 | API sits on top of a serial communication protocol between the esp8266 and the 120 | OpenInverter board. The web server hosts the HTML/css/js files which make up the 121 | end user web application. 122 | 123 | ## Connecting an esp8266 to your laptop/desktop for programming 124 | 125 | You can use an FTDI board to connect your esp8266 module to your laptop/desktop 126 | in order to program it. Only four cables are required between the FTDI and the 127 | esp8266 - 3.3V, GND, RX,and TX. 128 | 129 | TX on the esp8266 connects to RX on the FTDI and vice versa. 130 | 131 | Connect the USB port of the FTDI to your laptop/desktop. 132 | 133 | You can find the pinout of the Olimex esp8266 module here: https://github.com/OLIMEX/ESP8266/ 134 | 135 | Warning: be sure your FTDI board is set to 3.3v and not 5v mode. If it is in 5v 136 | mode, it may damage your esp8266 module. 137 | 138 | 139 | ## Setting up your laptop/desktop to program an esp8266 board 140 | 141 | 1. Install the Arduino application 142 | 2. Add esp8266 board support in the Arduino application. See 143 | https://github.com/esp8266/Arduino for more details on how to do this. 144 | 145 | 146 | 147 | ## Installing this software on an esp8266 148 | 149 | 1. Fetch the code in this repository. 150 | 151 | Download the zip file at this link: 152 | https://github.com/jsphuebner/esp8266-web-interface/archive/refs/heads/master.zip 153 | 154 | ~~ OR ~~ 155 | 156 | Clone the code with git. 157 | 158 | ``` 159 | git clone https://github.com/jsphuebner/esp8266-web-interface.git 160 | ``` 161 | 162 | 2. Open OpenInverterWeb/OpenInverterWeb.ino in the Arduino application. 163 | 164 | 3. Ensure you have the correct settings in Arduino for the esp8266. 165 | 166 | - Tools > Board > "Olimex MOD-WIFI-ESP8266(-DEV)"" 167 | - Tools > Upload speed > 115200 168 | - Tools > Flash Size > 2MB 169 | - Tools > Debug Port > Disabled 170 | - Tools > Port > Whatever serial port your esp8266 appears on 171 | 172 | 4. Install the firmware. 173 | 174 | Sketch > upload. 175 | 176 | 5. Install the HTML/CSS/js files on the esp8266 which make up the web interface. 177 | 178 | Tools > ESP8266 Sketch Data Upload 179 | 180 | 181 | ## Makefile-based installation 182 | 183 | If you already have a copy of the firmware/web interface installed and just want 184 | to update the web interface to the latest version, you can use the makefile 185 | included here. This can be useful when developing against the esp8266 for 186 | iterating changes quickly. 187 | 188 | 1. Install the 'make' tool on your PC. E.g. sudo apt-get install build-essential 189 | Debian/Ubuntu. 190 | 191 | 2. Set the INVERTER_IP environment variable to the IP of your esp8266. If you 192 | are connecting your PC directly to the esp8266, the IP will be 192.168.4.1 193 | usually. 194 | 195 | ``` 196 | export INVERTER_IP=192.168.4.1 197 | ``` 198 | 199 | 3. Upload the web interface files 200 | 201 | ``` 202 | make install 203 | ``` 204 | 205 | 206 | ## Related links 207 | * https://github.com/esp8266/Arduino 208 | * https://arduino-esp8266.readthedocs.io/en/latest/index.html 209 | 210 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # To Do List 2 | - [x] Context-specific help? 3 | - [x] Hitting enter when focus is on the command box should send the command (currently have to click the button) 4 | - [x] pin table header on spot values and parameters pages 5 | - [x] Separate 'can mapping' page 6 | - [x] Move spot value selection logic from spot values page to plot page 7 | - [ ] Documentation on how to use the development environment 8 | - [x] WiFi Settings page 9 | - [x] Fix data logger page 10 | - [x] Make dashboard content active and automatically updated 11 | - [ ] /wifi mocks? 12 | - [x] Support page 13 | - [ ] Make it all work on a small screen (menu could collapse to just icons and/or popout on mouseover) 14 | - [x] Show version string at bottom of menu 15 | - [ ] Visual feedback that changes have been accepted when modifying params 16 | - [x] Visual feedback that parameters have been saved 17 | - [ ] Auto updates for web interface 18 | - [x] Experimental features toggle 19 | - [x] Hide the SWD form by default 20 | - [x] Erase flash confirm dialog 21 | - [x] Hard reset confirm dialog 22 | - [x] Freeze params when selected 23 | - [x] Restore params from flash confirm 24 | - [ ] Submit params to openinverter 25 | - [ ] Subscribe to parameter set finish 26 | 27 | - [ ] Download flash - doesn't work, even on old UI 28 | - [ ] Download bootloader - doesn't work, even on old UI -------------------------------------------------------------------------------- /data/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/ajax-loader.gif -------------------------------------------------------------------------------- /data/chart.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/chart.min.js.gz -------------------------------------------------------------------------------- /data/chartjs-annotation.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/chartjs-annotation.min.js.gz -------------------------------------------------------------------------------- /data/distribution.files.lst: -------------------------------------------------------------------------------- 1 | index.html 2 | index.js 3 | inverter.js 4 | style.css 5 | log.js 6 | wifi.js 7 | plot.js 8 | icon-eye.png 9 | icon-tool.png 10 | -------------------------------------------------------------------------------- /data/distribution.files.lst.orig: -------------------------------------------------------------------------------- 1 | icon-sliders.png 2 | icon-grid.png 3 | ajax-loader.gif 4 | chart.min.js 5 | chartjs-annotation.min.js.gz 6 | gauge.min.js 7 | gauges.html 8 | gauges.js 9 | icon-activity.png 10 | icon-check-circle.png 11 | icon-cpu.png 12 | icon-database.png 13 | icon-download.png 14 | icon-file-text.png 15 | icon-grid.png 16 | icon-help-circle.png 17 | icon-pause.png 18 | icon-play.png 19 | icon-plus-circle.png 20 | icon-repeat.png 21 | icon-rotate-ccw.png 22 | icon-save.png 23 | icon-sliders.png 24 | icon-square.png 25 | icon-target.png 26 | icon-terminal.png 27 | icon-trash-2.png 28 | icon-trash.png 29 | icon-upload-cloud.png 30 | icon-wifi.png 31 | index.html 32 | index.js 33 | inverter.js 34 | jquery.core.min.js.gz 35 | jquery.knob.min.js.gz 36 | log.html 37 | log.js 38 | logo.png 39 | refresh.png 40 | remote.html 41 | style.css 42 | syncofs.html 43 | wifi-updated.html 44 | wifi.html 45 | icon-upload.png 46 | icon-zap.png 47 | wifi.js 48 | plot.js 49 | icon-clock.png 50 | icon-download-cloud.png 51 | icon-tool.png 52 | -------------------------------------------------------------------------------- /data/docstrings.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the esp8266 web interface 3 | * 4 | * Copyright (C) 2018 Johannes Huebner 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | var docstrings = { 22 | data: { 23 | /* spot values */ 24 | version: "Firmware version.", 25 | hwver: "Hardware version", 26 | opmode: "Operating mode. 0=Off, 1=Run, 2=Manual_run, 3=Boost, 4=Buck, 5=Sine, 6=2 Phase sine", 27 | lasterr: "Last error message", 28 | status: "", 29 | udc: "Voltage on the DC side of the inverter. a.k.a, battery voltage.", 30 | idc: "Current passing through the DC side of the inverter (calculated).", 31 | il1: "Current passing through the first current sensor on the AC side.", 32 | il2: "Current passing through the second current sensor on the AC side.", 33 | id: "", 34 | iq: "", 35 | ud: "", 36 | uq: "", 37 | heatcur: "", 38 | fstat: "Stator frequency", 39 | speed: "The speed (rpm) of the motor.", 40 | cruisespeed: "", 41 | turns: "Number of turns the motor has completed since startup.", 42 | amp: "Sine amplitude, 37813=max", 43 | angle: "Motor rotor angle, 0-360°. When using the SINE software, the slip is added to the rotor position.", 44 | pot: "Pot value, 4095=max", 45 | pot2: "Regen Pot value, 4095=max", 46 | potnom: "Scaled pot value, 0 accel", 47 | dir: "Rotation direction. -1=REV, 0=Neutral, 1=FWD", 48 | tmphs: "Inverter heatsink temperature", 49 | tmpm: "Motor temperature", 50 | uaux: "Auxiliary voltage (i.e. 12V system). Measured on pin 11 (mprot)", 51 | pwmio: "Raw state of PWM outputs at power up", 52 | canio: "Digital IO bits received via CAN", 53 | din_cruise: "Cruise Control. This pin activates the cruise control with the current speed. Pressing again updates the speed set point.", 54 | din_start: "State of digital input \"start\". This pin starts inverter operation", 55 | din_brake: "State of digital input \"brake\". This pin sets maximum regen torque (brknompedal). Cruise control is disabled.", 56 | din_mprot: "State of digital input \"motor protection switch\". Shuts down the inverter when = 0", 57 | din_forward: "Direction forward.", 58 | din_reverse: "Direction backward.", 59 | din_emcystop: "State of digital input \"emergency stop\". Shuts down the inverter when = 0", 60 | din_ocur: "Over current detected.", 61 | din_desat: "", 62 | din_bms: "BMS over voltage/under voltage.", 63 | cpuload: "CPU load for everything except communication", 64 | /* parameters */ 65 | curkp: "Current controller proportional gain", 66 | curki: "Current controller integral gain", 67 | curkifrqgain: "Current controllers integral gain frequency coefficient", 68 | fwkp: "Cross comparison field weakening controller gain", 69 | dmargin: "Margin for residual torque producing current (so field weakening current doesn't use up the entire amplitude)", 70 | syncadv: "Shifts \"syncofs\" downwards/upwards with frequency", 71 | boost: "0 Hz Boost in digit. 1000 digit ~ 2.5%", 72 | fweak: "Frequency where V/Hz reaches its peak", 73 | fconst: "Frequency where slip frequency is derated to form a constant power region. Only has an effect when < fweak", 74 | udcnom: "Nominal voltage for fweak and boost. fweak and boost are scaled to the actual dc voltage. 0=don't scale", 75 | fslipmin: "Slip frequency at minimum throttle", 76 | fslipmax: "Slip frequency at maximum throttle", 77 | fslipconstmax: "Slip frequency at maximum throttle and fconst", 78 | fmin: "Below this frequency no voltage is generated", 79 | polepairs: "Pole pairs of motor (e.g. 4-pole motor: 2 pole pairs)", 80 | respolepairs: "Pole pairs of resolver (normally same as polepairs of motor, but sometimes 1)", 81 | encflt: "Filter constant between pulse encoder and speed calculation. Makes up for slightly uneven pulse distribution", 82 | encmode: "0=single channel encoder, 1=quadrature encoder, 2=quadrature /w index pulse, 3=SPI (deprecated), 4=Resolver, 5=sin/cos chip", 83 | fmax: "At this frequency rev limiting kicks in", 84 | numimp: "Pulse encoder pulses per turn", 85 | dirchrpm: "Motor speed at which direction change is allowed", 86 | dirmode: "0=button (momentary pulse selects forward/reverse), 1=switch (forward or reverse signal must be constantly high)", 87 | syncofs: "Phase shift of sine wave after receiving index pulse", 88 | snsm: "Motor temperature sensor. 12=KTY83, 13=KTY84, 14=Leaf, 15=KTY81", 89 | pwmfrq: "PWM frequency. 0=17.6kHz, 1=8.8kHz, 2=4.4kHz, 3=2.2kHz. Needs PWM restart", 90 | pwmpol: "PWM polarity. 0=active high, 1=active low. DO NOT PLAY WITH THIS! Needs PWM restart", 91 | deadtime: "Deadtime between highside and lowside pulse. 28=800ns, 56=1.5µs. Not always linear, consult STM32 manual. Needs PWM restart", 92 | ocurlim: "Hardware over current limit. RMS-current times sqrt(2) + some slack. Set negative if il1gain and il2gain are negative.", 93 | minpulse: "Narrowest or widest pulse, all other mapped to full off or full on, respectively", 94 | il1gain: "Digits per A of current sensor L1", 95 | il2gain: "Digits per A of current sensor L2", 96 | udcgain: "Digits per V of DC link", 97 | udcofs: "DC link 0V offset", 98 | udclim: "High voltage at which the PWM is shut down", 99 | snshs: "Heatsink temperature sensor. 0=JCurve, 1=Semikron, 2=MBB600, 3=KTY81, 4=PT1000, 5=NTCK45+2k2, 6=Leaf", 100 | pinswap: "Swap pins (only \"FOC\" software). Multiple bits can be set. 1=Swap Current Inputs, 2=Swap Resolver sin/cos, 4=Swap PWM output 1/3\n001 = 1 Swap Currents only\n010 = 2 Swap Resolver only\n011 = 3 Swap Resolver and Currents\n100 = 4 Swap PWM only\n101 = 5 Swap PWM and Currents\n110 = 6 Swap PWM and Resolve\n111 = 7 Swap PWM and Resolver and Currents", 101 | bmslimhigh: "Positive throttle limit on BMS under voltage", 102 | bmslimlow: "Regen limit on BMS over voltage", 103 | udcmin: "Minimum battery voltage", 104 | udcmax: "Maximum battery voltage", 105 | iacmax: "Maximum peak AC current", 106 | idcmax: "Maximum DC input current", 107 | idcmin: "Maximum DC output current (regen)", 108 | throtmax: "Throttle limit", 109 | throtmin: "Throttle regen limit", 110 | ifltrise: "Controls how quickly slip and amplitude recover. The greater the value, the slower", 111 | ifltfall: "Controls how quickly slip and amplitude are reduced on over current. The greater the value, the slower", 112 | chargemode: "0=Off, 3=Boost, 4=Buck", 113 | chargecur: "Charge current setpoint. Boost mode: charger INPUT current. Buck mode: charger output current", 114 | chargekp: "Charge controller gain. Lower if you have oscillation, raise if current set point is not met", 115 | chargeflt: "Charge current filtering. Raise if you have oscillations", 116 | chargemax: "Charge mode duty cycle limit. Especially in boost mode this makes sure you don't overvolt you IGBTs if there is no battery connected.", 117 | potmin: "Value of \"pot\" when pot isn't pressed at all", 118 | potmax: "Value of \"pot\" when pot is pushed all the way in", 119 | pot2min: "Value of \"pot2\" when regen pot is in 0 position", 120 | pot2max: "Value of \"pot2\" when regen pot is in full on position", 121 | potmode: "0=Pot 1 is throttle and pot 2 is regen strength preset. 1=Pot 2 is proportional to pot 1 (redundancy) 2=Throttle controlled via CAN", 122 | throtramp: "Max positive throttle slew rate", 123 | throtramprpm: "No throttle ramping above this speed", 124 | ampmin: "Minimum relative sine amplitude (only \"sine\" software)", 125 | slipstart: "% positive throttle travel at which slip is increased (only \"sine\" software)", 126 | throtcur: "Motor current per % of throttle travel (only \"FOC\" software)", 127 | brknompedal: "Foot on brake pedal regen torque", 128 | brkpedalramp: "Ramp speed when entering regen. E.g. when you set brkmax to 20% and brknompedal to -60% and brkpedalramp to 1, it will take 400ms to arrive at brake force of -60%", 129 | brknom: "Range of throttle pedal travel allocated to regen", 130 | brkmax: "Foot-off throttle regen torque", 131 | brkrampstr: "Below this frequency the regen torque is reduced linearly with the frequency", 132 | brkout: "Activate brake light output at this amount of braking force", 133 | idlespeed: "Motor idle speed. Set to -100 to disable idle function. When idle speed controller is enabled, brake pedal must be pressed on start.", 134 | idlethrotlim: "Throttle limit of idle speed controller", 135 | idlemode: "Motor idle speed mode. 0=always run idle speed controller, 1=only run it when brake pedal is released, 2=like 1 but only when cruise switch is on", 136 | speedkp: "Speed controller gain (Cruise and idle speed). Decrease if speed oscillates. Increase for faster load regulation", 137 | speedflt: "Filter before cruise controller", 138 | cruisemode: "0=button (set when button pressed, reset with brake pedal), 1=switch (set when switched on, reset when switched off or brake pedal)", 139 | udcsw: "Voltage at which the DC contactor is allowed to close", 140 | udcswbuck: "Voltage at which the DC contactor is allowed to close in buck charge mode", 141 | tripmode: "What to do with relays at a shutdown event. 0=All off, 1=Keep DC switch closed, 2=close precharge relay", 142 | pwmfunc: "Quantity that controls the PWM output. 0=tmpm, 1=tmphs, 2=speed", 143 | pwmgain: "Gain of PWM output", 144 | pwmofs: "Offset of PWM output, 4096=full on", 145 | canspeed: "Baud rate of CAN interface 0=250k, 1=500k, 2=800k, 3=1M", 146 | canperiod: "0=send configured CAN messages every 100ms, 1=every 10ms", 147 | fslipspnt: "Slip setpoint in mode 2. Written by software in mode 1", 148 | ampnom: "Nominal amplitude in mode 2. Written by software in mode 1", 149 | }, 150 | 151 | get: function(item) 152 | { 153 | if ( item in docstrings.data ) 154 | { 155 | return docstrings.data[item]; 156 | } 157 | return ""; 158 | } 159 | 160 | }; 161 | -------------------------------------------------------------------------------- /data/gauge.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/gauge.min.js.gz -------------------------------------------------------------------------------- /data/gauges.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | Huebner Inverter Management Console 26 | 62 | 63 | 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /data/gauges.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the esp8266 web interface 3 | * 4 | * Copyright (C) 2018 Johannes Huebner 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | var gauges = {}; 22 | var items = new Array(); 23 | 24 | function onLoad() 25 | { 26 | createGauges(); 27 | acquire(); 28 | } 29 | 30 | /** 31 | * @brief Creates gauges for all canvases found on the page 32 | */ 33 | function createGauges() 34 | { 35 | var div = document.getElementById("gauges"); 36 | var paramPart = document.location.href.split("items="); 37 | items = paramPart[1].split(","); 38 | 39 | for (var i = 0; i < items.length; i++) 40 | { 41 | var name = items[i]; 42 | var canvas = document.createElement("CANVAS"); 43 | canvas.setAttribute("id", name); 44 | div.appendChild(canvas); 45 | 46 | var gauge = new RadialGauge( 47 | { 48 | renderTo: name, 49 | title: name, 50 | width: 300, 51 | height: 300, 52 | minValue: 0, 53 | maxValue: 1, 54 | majorTicks: [0, 1] 55 | }); 56 | 57 | gauge.draw(); 58 | gauges[name] = gauge; 59 | } 60 | } 61 | 62 | function calcTicks(min, max) 63 | { 64 | var N = 6; 65 | var ticks = [ min ]; 66 | var dist = (max - min) / N; 67 | var tick = min; 68 | 69 | for (var i = 0; i < N; i++) 70 | { 71 | tick += dist; 72 | ticks.push(Math.round(tick)); 73 | } 74 | return ticks; 75 | } 76 | 77 | function acquire() 78 | { 79 | if (!items.length) return; 80 | 81 | inverter.getValues(items, 1, 82 | function(values) 83 | { 84 | for (var name in values) 85 | { 86 | var val = values[name][0]; 87 | gauges[name].options.minValue = Math.min(gauges[name].options.minValue, Math.floor(val * 0.7)); 88 | gauges[name].options.maxValue = Math.max(gauges[name].options.maxValue, Math.ceil(val * 1.5)); 89 | gauges[name].options.majorTicks = calcTicks(gauges[name].options.minValue, gauges[name].options.maxValue); 90 | gauges[name].value = val; 91 | gauges[name].update(); 92 | } 93 | acquire(); 94 | }); 95 | } 96 | -------------------------------------------------------------------------------- /data/icon-check-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/icon-check-circle.png -------------------------------------------------------------------------------- /data/icon-trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/icon-trash.png -------------------------------------------------------------------------------- /data/icon-x-square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/icon-x-square.png -------------------------------------------------------------------------------- /data/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the esp8266 web interface 3 | * 4 | * Copyright (C) 2018 Johannes Huebner 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | var chart; 22 | var items = {}; 23 | var stop; 24 | var imgid = 0; 25 | var subscription; 26 | 27 | function sleep(ms) { 28 | return new Promise(resolve => setTimeout(resolve, ms)); 29 | } 30 | 31 | 32 | 33 | /** @brief uploads file to web server, Flash using Serial-Wire-Debug. Start address bootloader = 0x08000000, firmware = 0x08001000*/ 34 | function uploadSWDFile() 35 | { 36 | var xmlhttp = new XMLHttpRequest(); 37 | var form = document.getElementById('swdform'); 38 | 39 | if (form.getFormData) 40 | var fd = form.getFormData(); 41 | else 42 | var fd = new FormData(form); 43 | var file = document.getElementById('swdfile').files[0]; 44 | 45 | xmlhttp.onload = function() 46 | { 47 | var xhr = new XMLHttpRequest(); 48 | xhr.seenBytes = 0; 49 | xhr.seenTotalPages = 0; 50 | xhr.onreadystatechange = function() { 51 | if(xhr.readyState == 3) { 52 | var data = xhr.response.substr(xhr.seenBytes); 53 | 54 | if(data.indexOf("Error") != -1) { 55 | document.getElementById("swdbar").style.width = "100%"; 56 | document.getElementById("swdbar").innerHTML = "

" + data + "

"; 57 | }else{ 58 | var s = data.split('\n'); 59 | xhr.seenTotalPages += (s.length - 1) * 16; 60 | //console.log("pages: " + s.length + " Size: " + ((s.length -1) * 16)); 61 | 62 | var progress = Math.round(100 * xhr.seenTotalPages / file.size); 63 | document.getElementById("swdbar").style.width = progress + "%"; 64 | document.getElementById("swdbar").innerHTML = "

" + progress + "%

"; 65 | 66 | xhr.seenBytes = xhr.responseText.length; 67 | } 68 | } 69 | }; 70 | if (file.name.endsWith('loader.bin')) 71 | { 72 | xhr.open('GET', '/swd/mem/flash?bootloader&file=' + file.name, true); 73 | }else{ 74 | xhr.open('GET', '/swd/mem/flash?flash&file=' + file.name, true); 75 | } 76 | xhr.send(); 77 | } 78 | xmlhttp.open("POST", "/edit"); 79 | xmlhttp.send(fd); 80 | } 81 | 82 | -------------------------------------------------------------------------------- /data/inverter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the esp8266 web interface 3 | * 4 | * Copyright (C) 2018 Johannes Huebner 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | 22 | /** @brief this is a little cache to store the current params/spot values. This 23 | * is here so that different functions can do look-ups without making a full 24 | * HTTP call to the inverter each time. */ 25 | 26 | var paramsCache = { 27 | 28 | data: undefined, 29 | dataById: {}, 30 | failedFetchCount: 0, 31 | 32 | get: function(name) { 33 | if ( paramsCache.data !== undefined ) 34 | { 35 | if ( name in paramsCache.data ) { 36 | if ( paramsCache.data[name].enums ) { 37 | return paramsCache.data[name].enums[paramsCache.data[name].value]; 38 | } else { 39 | return paramsCache.data[name].value; 40 | } 41 | } 42 | } 43 | return null; 44 | }, 45 | 46 | getEntry: function(name) { 47 | return paramsCache.data[name]; 48 | }, 49 | 50 | getData: function() { return paramsCache.data; }, 51 | 52 | setData: function(data) { 53 | paramsCache.data = data; 54 | 55 | for (var key in data) { 56 | paramsCache.dataById[data[key].id] = data[key]; 57 | paramsCache.dataById[data[key].id]['name'] = key; 58 | } 59 | }, 60 | 61 | getJson: function() { return JSON.stringify(paramsCache.data); }, 62 | 63 | getById: function(id) { 64 | return paramsCache.dataById[id]; 65 | } 66 | } 67 | 68 | var inverter = { 69 | 70 | firmwareVersion: 0, 71 | 72 | /** @brief send a command to the inverter */ 73 | sendCmd: function(cmd, replyFunc, repeat) 74 | { 75 | var xmlhttp=new XMLHttpRequest(); 76 | var req = "/cmd?cmd=" + cmd; 77 | 78 | xmlhttp.onload = function() 79 | { 80 | if (replyFunc) replyFunc(this.responseText); 81 | } 82 | 83 | if (repeat) 84 | req += "&repeat=" + repeat; 85 | 86 | xmlhttp.open("GET", req, true); 87 | xmlhttp.send(); 88 | }, 89 | 90 | /** @brief get the params from the inverter */ 91 | getParamList: function(replyFunc, includeHidden) 92 | { 93 | var cmd = includeHidden ? "json hidden" : "json"; 94 | 95 | inverter.sendCmd(cmd, function(reply) { 96 | var params = {}; 97 | try 98 | { 99 | params = JSON.parse(reply); 100 | 101 | for (var name in params) 102 | { 103 | var param = params[name]; 104 | param.enums = inverter.parseEnum(param.unit); 105 | 106 | if (name == "version") 107 | inverter.firmwareVersion = parseFloat(param.value); 108 | } 109 | paramsCache.failedFetchCount = 0; 110 | } 111 | catch(ex) 112 | { 113 | paramsCache.failedFetchCount += 1; 114 | if ( paramsCache.failedFetchCount >= 2 ){ 115 | ui.showCommunicationErrorBar(); 116 | } 117 | } 118 | if ( paramsCache.failedFetchCount < 2 ) 119 | { 120 | ui.hideCommunicationErrorBar(); 121 | } 122 | paramsCache.setData(params); 123 | if (replyFunc) replyFunc(params); 124 | }); 125 | }, 126 | 127 | getValues: function(items, repeat, replyFunc) 128 | { 129 | var process = function(reply) 130 | { 131 | var expr = /(\-{0,1}[0-9]+\.[0-9]*)/mg; 132 | var signalIdx = 0; 133 | var values = {}; 134 | 135 | for (var res = expr.exec(reply); res; res = expr.exec(reply)) 136 | { 137 | var val = parseFloat(res[1]); 138 | 139 | if (!values[items[signalIdx]]) 140 | values[items[signalIdx]] = new Array() 141 | values[items[signalIdx]].push(val); 142 | signalIdx = (signalIdx + 1) % items.length; 143 | } 144 | replyFunc(values); 145 | }; 146 | 147 | if (inverter.firmwareVersion < 3.53 || items.length > 10) 148 | inverter.sendCmd("get " + items.join(','), process, repeat); 149 | else 150 | inverter.sendCmd("stream " + repeat + " " + items.join(','), process); 151 | }, 152 | 153 | 154 | /** @brief given the 'unit' string provided by the inverter api, parse out 155 | * the key value pairs and return them in an array. 156 | * @param unit, e.g. "0=None, 1=UdcLow, 2=UdcHigh, 4=UdcBelowUdcSw" 157 | * Example return : ['None', 'UdcLow', 'UdcHigh',,'udcBelowUdcSw']. Note, 158 | * the extra comma is intentional. The position in the array is determined 159 | * by the index on the left hand side of the equals in the 'unit' string. 160 | */ 161 | parseEnum: function(unit) 162 | { 163 | var expr = /(\-{0,1}[0-9\.]+)=([a-zA-Z0-9_\-\.]+)[,\s]{0,2}|([a-zA-Z0-9_\-\.]+)[,\s]{1,2}/g; 164 | var enums = {}; 165 | var res = expr.exec(unit); 166 | 167 | if (res) 168 | { 169 | do 170 | { 171 | enums[res[1]] = res[2]; 172 | } while (res = expr.exec(unit)) 173 | //console.log('enums : ' + enums); 174 | return enums; 175 | } 176 | return false; 177 | }, 178 | 179 | /** @brief helper function, from a list of parameters send parameter with given index to inverter 180 | * @param params map of parameters (name -> value) 181 | * @param index numerical index which parameter to set */ 182 | setParam: function(params, index) 183 | { 184 | var keys = Object.keys(params); 185 | 186 | if (index < keys.length) 187 | { 188 | var key = keys[index]; 189 | modal.appendToModal('large', "Setting " + key + " to " + params[key] + "
"); 190 | inverter.sendCmd("set " + key + " " + params[key], function(reply) { 191 | modal.appendToModal('large', reply + "
"); 192 | // auto-scroll text in modal as it is added 193 | modal.largeModalScrollToBottom(); 194 | inverter.setParam(params, index + 1); 195 | }); 196 | } 197 | }, 198 | 199 | /** @brief Add/Delete a CAN mapping 200 | * @param direction, tx, rx, or del 201 | * @param name, spot value name 202 | * @param id, canid of message 203 | * @param pos, offset within frame 204 | * @param bits, length of field 205 | * @param gain, multiplier 206 | */ 207 | canMapping: function(direction, name, id, pos, bits, gain) 208 | { 209 | var cmd = "can " + direction + " " + name + " " + id + " " + pos + " " + bits + " " + gain; 210 | inverter.sendCmd(cmd); 211 | }, 212 | 213 | /** @brief get a list of files in the spiffs filesystem on the esp8266 */ 214 | getFiles: function(replyFunc) 215 | { 216 | var filesRequest = new XMLHttpRequest(); 217 | filesRequest.onload = function() 218 | { 219 | var filesJson = JSON.parse(this.responseText); 220 | replyFunc(filesJson); 221 | } 222 | filesRequest.onerror = function() 223 | { 224 | alert("error"); 225 | } 226 | filesRequest.open("GET", "/list", true); 227 | filesRequest.send(); 228 | }, 229 | 230 | /** @brief delete a file from the spiffs filessytem on the esp8266 */ 231 | deleteFile: function(filename, replyFunc) 232 | { 233 | var deleteFileRequest = new XMLHttpRequest(); 234 | deleteFileRequest.onload = function() 235 | { 236 | var responseJson = JSON.parse(this.responseText); 237 | replyFunc(responseJson); 238 | } 239 | deleteFileRequest.onerror = function() 240 | { 241 | alert("error"); 242 | } 243 | deleteFileRequest.open("DELETE", "/edit?f=" + filename, true); 244 | deleteFileRequest.send(); 245 | } 246 | 247 | 248 | }; 249 | -------------------------------------------------------------------------------- /data/jquery.core.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/jquery.core.min.js.gz -------------------------------------------------------------------------------- /data/jquery.knob.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/jquery.knob.min.js.gz -------------------------------------------------------------------------------- /data/log.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | Huebner Inverter Management Console - Datalogger 26 | 27 | 28 | 29 | 30 |

Data Logger

31 |

32 | 33 | 34 | 35 | 36 |

37 | 38 | 39 |

40 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /data/log.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the esp8266 web interface 3 | * 4 | * Copyright (C) 2018 Johannes Huebner 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | var log = { 22 | 23 | items: [], 24 | samples: 0, 25 | textArea: undefined, 26 | minmax: false, 27 | stopLogging: true, 28 | 29 | /* @brief add field to data logger */ 30 | addLogItem: function() 31 | { 32 | var dataLoggerConfiguration = document.getElementById('data-logger-configuration'); 33 | 34 | // container for the drop down and the delete button 35 | var selectDiv = document.createElement("div"); 36 | selectDiv.classList.add('logger-field'); 37 | dataLoggerConfiguration.appendChild(selectDiv); 38 | 39 | // Create a drop down and populate it with the possible spot values 40 | var selectSpotValue = document.createElement("select"); 41 | selectSpotValue.classList.add('logger-field-select'); 42 | for ( var key in paramsCache.getData() ) 43 | { 44 | if ( ! paramsCache.getEntry(key).isparam ) 45 | { 46 | var option = document.createElement("option"); 47 | option.value = key; 48 | option.text = key; 49 | selectSpotValue.appendChild(option); 50 | } 51 | } 52 | selectDiv.appendChild(selectSpotValue); 53 | 54 | // Add the delete button 55 | var deleteButton = document.createElement("button"); 56 | var deleteButtonImg = document.createElement('img'); 57 | deleteButtonImg.src = '/icon-trash.png'; 58 | deleteButton.appendChild(deleteButtonImg); 59 | deleteButton.onclick = function() { this.parentNode.remove(); }; 60 | selectDiv.appendChild(deleteButton); 61 | }, 62 | 63 | /** @brief return a list of fields currently configured for logger */ 64 | getLogItems: function() 65 | { 66 | log.items = []; 67 | var formItems = document.forms["data-logger-configuration"].elements; 68 | for ( var i = 0; i < formItems.length; i++ ) 69 | { 70 | if ( formItems[i].type === 'select-one' && formItems[i].classList.contains('logger-field-select') ) 71 | { 72 | log.items.push(formItems[i].value); 73 | } 74 | } 75 | }, 76 | 77 | /* @brief start collecting log data */ 78 | start: function() 79 | { 80 | log.stopLogging = false; 81 | log.getLogItems(); 82 | log.textArea = document.getElementById("data-logger-text-area"); 83 | log.samples = document.getElementById("data-logger-samples").value; 84 | log.minmax = document.getElementById("data-logger-minmax").checked; 85 | log.textArea.innerHTML = "Timestamp" 86 | 87 | if (log.minmax) 88 | { 89 | for (var i = 0; i < log.items.length; i++) 90 | { 91 | log.textArea.innerHTML += "," + log.items[i] + " (avg)," + log.items[i] + " (min)," + log.items[i] + " (max)"; 92 | } 93 | } 94 | else 95 | { 96 | log.textArea.innerHTML += "," + log.items; 97 | } 98 | 99 | log.textArea.innerHTML += "\r\n"; 100 | log.acquire(log.samples); 101 | }, 102 | 103 | stop: function() 104 | { 105 | log.stopLogging = true; 106 | }, 107 | 108 | save: function() 109 | { 110 | var textToWrite = document.getElementById('data-logger-text-area').innerHTML; 111 | var textFileAsBlob = new Blob([ textToWrite ], { type: 'text/csv' }); 112 | var fileNameToSaveAs = "log.csv"; 113 | 114 | var downloadLink = document.createElement("a"); 115 | downloadLink.download = fileNameToSaveAs; 116 | downloadLink.innerHTML = "Download File"; 117 | if (window.webkitURL != null) 118 | { 119 | // Chrome allows the link to be clicked without actually adding it to the DOM. 120 | downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob); 121 | } else { 122 | // Firefox requires the link to be added to the DOM before it can be clicked. 123 | downloadLink.href = window.URL.createObjectURL(textFileAsBlob); 124 | downloadLink.onclick = function(event) {document.body.removeChild(event.target)}; 125 | downloadLink.style.display = "none"; 126 | document.body.appendChild(downloadLink); 127 | } 128 | 129 | downloadLink.click(); 130 | }, 131 | 132 | acquire: function(samples) 133 | { 134 | if ( log.stopLogging ){ return; } 135 | 136 | if (!log.items.length) return; 137 | 138 | inverter.getValues(log.items, log.samples, 139 | function(values) 140 | { 141 | var tzoffset = (new Date()).getTimezoneOffset() * 60000; //offset in milliseconds 142 | var localISOTime = (new Date(Date.now() - tzoffset)).toISOString().slice(0, -1); 143 | var line = localISOTime; 144 | for (var name in values) 145 | { 146 | var avg = values[name].reduce((acc, c) => acc + c, 0) / log.samples; 147 | 148 | if (log.minmax) 149 | { 150 | line += "," + avg.toFixed(2) + "," + Math.min(...values[name]) + "," + Math.max(...values[name]); 151 | } 152 | else 153 | { 154 | line += "," + avg.toFixed(2); 155 | } 156 | } 157 | line += "\r\n"; 158 | log.textArea.innerHTML += line; 159 | log.textArea.scrollTop = log.textArea.scrollHeight; 160 | log.acquire(samples); 161 | }); 162 | }, 163 | } 164 | -------------------------------------------------------------------------------- /data/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/logo.png -------------------------------------------------------------------------------- /data/modal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the esp8266 web interface 3 | * 4 | * Copyright (C) 2018 Johannes Huebner 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | var modal = { 22 | 23 | /** @brief Show the large dialog 24 | * @param modal - name of the modal. Valid values : large, small */ 25 | showModal: function(modal) 26 | { 27 | var m = document.getElementById(modal + "-modal-overlay"); 28 | if ( m !== undefined ){ 29 | m.style.display = 'block'; 30 | } 31 | else { 32 | console.log("warning, showModal, bad modal choice"); 33 | } 34 | }, 35 | 36 | /** @brief Hide the modal dialog 37 | * @param modal - name of the modal. Valid values : large, small */ 38 | hideModal: function(modal) 39 | { 40 | var m = document.getElementById(modal + "-modal-overlay"); 41 | if ( m !== undefined ){ 42 | m.style.display = 'none'; 43 | } 44 | else { 45 | console.log("warning, hideModal, bad modal choice"); 46 | } 47 | }, 48 | 49 | /** @brief Set the header text on the modal 50 | * @param modal - name of the modal. Valid values : large, small 51 | * @param headerText - string to place into the header field of the modal */ 52 | setModalHeader: function(modal, headerText) 53 | { 54 | var m = document.getElementById(modal + "-modal-header"); 55 | if ( m !== undefined ){ 56 | m.innerHTML = headerText; 57 | } 58 | else { 59 | console.log("warning, setModalHeader, bad modal choice"); 60 | } 61 | }, 62 | 63 | /** @brief Empty the contents of the large modal 64 | * @param modal - name of the modal. Valid values : large, small */ 65 | emptyModal: function(modal) 66 | { 67 | var m = document.getElementById(modal + "-modal-content"); 68 | if ( m !== undefined ){ 69 | m.innerHTML = ""; 70 | } 71 | else { 72 | console.log("warning, emptyModal, bad modal choice"); 73 | } 74 | }, 75 | 76 | /** @brief Append content to large modal 77 | * @param modal - name of the modal. Valid values : large, small 78 | * @appendText - string to append to the body of the modal. Note, can be html. */ 79 | appendToModal: function(modal, appendText) 80 | { 81 | var modalId = modal + "-modal-content"; 82 | //console.log("appendToModal : looking for div " + modalId); 83 | var modalContent = document.getElementById(modalId); 84 | if ( modalContent !== undefined ){ 85 | modalContent.innerHTML += appendText; 86 | } 87 | else { 88 | console.log("warning, appendToModal, bad modal choice"); 89 | } 90 | }, 91 | 92 | //* @brief auto-scroll large modal content */ 93 | largeModalScrollToBottom(){ 94 | var largeModalContent = document.getElementById("large-modal-content"); 95 | largeModalContent.scrollTop = largeModalContent.scrollHeight; 96 | }, 97 | 98 | }; -------------------------------------------------------------------------------- /data/plot.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the esp8266 web interface 3 | * 4 | * Copyright (C) 2018 Johannes Huebner 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | var plot = { 22 | 23 | stop: true, 24 | 25 | /** @brief launch new tab showing gauges based fields selected from the plot&gauge page */ 26 | launchGauges: function() 27 | { 28 | var items = ui.getPlotItems(); 29 | var req = "gauges.html?items=" + items.names.join(',') 30 | window.open(req); 31 | }, 32 | 33 | /** @brief generates chart at bottom of page */ 34 | generateChart: function() 35 | { 36 | chart = new Chart("canvas", { 37 | type: "line", 38 | options: { 39 | animation: { 40 | duration: 0 41 | }, 42 | scales: { 43 | yAxes: [{ 44 | type: "linear", 45 | display: true, 46 | position: "left", 47 | id: "left" 48 | }, { 49 | type: "linear", 50 | display: true, 51 | position: "right", 52 | id: "right", 53 | gridLines: { drawOnChartArea: false } 54 | }] 55 | } 56 | } }); 57 | }, 58 | 59 | /** @brief start plotting selected spot values */ 60 | startPlot: function() 61 | { 62 | items = ui.getPlotItems(); 63 | var colours = [ 'rgb(255, 99, 132)', 'rgb(54, 162, 235)', 'rgb(255, 159, 64)', 'rgb(153, 102, 255)', 'rgb(255, 205, 86)', 'rgb(75, 192, 192)' ]; 64 | 65 | chart.config.data.datasets = new Array(); 66 | 67 | for (var signalIdx = 0; signalIdx < items.names.length; signalIdx++) 68 | { 69 | var newDataset = { 70 | label: items.names[signalIdx], 71 | data: [], 72 | borderColor: colours[signalIdx % colours.length], 73 | backgroundColor: colours[signalIdx % colours.length], 74 | fill: false, 75 | pointRadius: 0, 76 | yAxisID: items.axes[signalIdx] 77 | }; 78 | chart.config.data.datasets.push(newDataset); 79 | } 80 | 81 | ui.setAutoReload(false); 82 | time = 0; 83 | chart.update(); 84 | plot.stop = false; 85 | document.getElementById("pauseButton").disabled = false; 86 | plot.acquire(); 87 | }, 88 | 89 | /** @brief Stop plotting */ 90 | stopPlot: function() 91 | { 92 | plot.stop = true; 93 | document.getElementById("pauseButton").disabled = false; 94 | ui.setAutoReload(true); 95 | }, 96 | 97 | /** @brief pause or resume plotting */ 98 | pauseResumePlot: function() 99 | { 100 | if (plot.stop) 101 | { 102 | plot.stop = false; 103 | plot.acquire(); 104 | } 105 | else 106 | { 107 | plot.stop = true; 108 | } 109 | }, 110 | 111 | acquire: function() 112 | { 113 | if (plot.stop) return; 114 | if (!items.names.length) return; 115 | var burstLength = document.getElementById('burstLength').value; 116 | var maxValues = document.getElementById('maxValues').value; 117 | 118 | inverter.getValues(items.names, burstLength, 119 | function(values) 120 | { 121 | for (var i = 0; i < burstLength; i++) 122 | { 123 | chart.config.data.labels.push(time); 124 | time++; 125 | } 126 | chart.config.data.labels.splice(0, Math.max(chart.config.data.labels.length - maxValues, 0)); 127 | 128 | for (var name in values) 129 | { 130 | var data = chart.config.data.datasets.find(function(element) { return element.label == name }).data; 131 | 132 | for (var i = 0; i < values[name].length; i++) 133 | { 134 | data.push(values[name][i]) 135 | data.splice(0, Math.max(data.length - maxValues, 0)); 136 | } 137 | } 138 | 139 | chart.update(); 140 | plot.acquire(); 141 | }); 142 | }, 143 | 144 | 145 | } 146 | -------------------------------------------------------------------------------- /data/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsphuebner/esp8266-web-interface/4594becbddcd36f3cf293ef232b31cd7221bc9df/data/refresh.png -------------------------------------------------------------------------------- /data/remote.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | openinverter.org remote support module 4 | 5 | 6 | 7 | 8 |

Remote Support Module

9 | Welcome to remote support! In order for this module to work, this page must be opened on 10 | a device that is connected to both the inverter AND the internet. There are multiple ways 11 | to achieve this. Pick the one that suits you best. 12 |
    13 |
  • Open it on a smart phone that is connected to the internet via mobile data and to the inverter via wifi. Make sure your phone never enters standby mode during the support session. 14 |
  • Open it on a regular PC/laptop that is connected to the internet via Ethernet and connected to the inverter via wifi 15 |
  • Make the inverter wifi module connect to your local hotspot and access this wifi interface via the hotpot assigned IP address 16 |
17 | Below you can see which commands are issued by remote support. 18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /data/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the esp8266 web interface 3 | * 4 | * Copyright (C) 2018 Johannes Huebner 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | 21 | * { 22 | transition: 0.5s; 23 | -ms-overflow-style: none; 24 | scrollbar-width: none; 25 | } 26 | 27 | a { 28 | text-decoration: none; 29 | color: #4CAF50; 30 | } 31 | 32 | a:visited { 33 | color: #4CAF50; 34 | } 35 | 36 | p { 37 | margin: 0em 0em 0.5em 0rem ; 38 | } 39 | 40 | h2 { 41 | padding-top: 5px; 42 | } 43 | 44 | h3 { 45 | padding: 20px 0px 10px 0px; 46 | margin: 0px; 47 | } 48 | 49 | .underline { 50 | border-bottom: solid 1px #CCC; 51 | } 52 | 53 | html, body { 54 | font-family: sans-serif; 55 | font-size: 12pt; 56 | -webkit-text-size-adjust: none; 57 | -moz-text-size-adjust: none; 58 | -ms-text-size-adjust: none; 59 | margin:0; 60 | padding:0; 61 | height: 100%; 62 | } 63 | 64 | 65 | 66 | .graph { 67 | width: 298px; /* width and height are arbitrary, just make sure the #bar styles are changed accordingly */ 68 | height: 30px; 69 | border: 1px solid #888; 70 | background: rgb(168,168,168); 71 | background: -moz-linear-gradient(top, rgba(168,168,168,1) 0%, rgba(204,204,204,1) 23%); 72 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(168,168,168,1)), color-stop(23%,rgba(204,204,204,1))); 73 | background: -webkit-linear-gradient(top, rgba(168,168,168,1) 0%,rgba(204,204,204,1) 23%); 74 | background: -o-linear-gradient(top, rgba(168,168,168,1) 0%,rgba(204,204,204,1) 23%); 75 | background: -ms-linear-gradient(top, rgba(168,168,168,1) 0%,rgba(204,204,204,1) 23%); 76 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#a8a8a8', endColorstr='#cccccc',GradientType=0 ); 77 | background: linear-gradient(top, rgba(168,168,168,1) 0%,rgba(204,204,204,1) 23%); 78 | position: relative; 79 | } 80 | 81 | #upload-firmware-bar { 82 | height: 29px; /* Not 30px because the 1px top-border brings it up to 30px to match #graph */ 83 | background: rgb(255,197,120); 84 | background: -moz-linear-gradient(top, rgba(255,197,120,1) 0%, rgba(244,128,38,1) 100%); 85 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,197,120,1)), color-stop(100%,rgba(244,128,38,1))); 86 | background: -webkit-linear-gradient(top, rgba(255,197,120,1) 0%,rgba(244,128,38,1) 100%); 87 | background: -o-linear-gradient(top, rgba(255,197,120,1) 0%,rgba(244,128,38,1) 100%); 88 | background: -ms-linear-gradient(top, rgba(255,197,120,1) 0%,rgba(244,128,38,1) 100%); 89 | background: linear-gradient(top, rgba(255,197,120,1) 0%,rgba(244,128,38,1) 100%); 90 | border-top: 1px solid #fceabb; 91 | } 92 | 93 | #upload-firmware-bar p { 94 | position: absolute; 95 | text-align: center; 96 | width: 100%; 97 | margin: 0; 98 | line-height: 30px; 99 | } 100 | 101 | .error { 102 | /* These styles are arbitrary */ 103 | background-color: #fceabb; 104 | padding: 1em; 105 | font-weight: bold; 106 | color: red; 107 | border: 1px solid red; 108 | } 109 | 110 | 111 | 112 | /* Navbar */ 113 | 114 | #navbar { 115 | background-color: #4CAF50; 116 | min-height: 100vh; 117 | position: fixed; 118 | width: 180px; 119 | overflow-y: scroll; 120 | height:100%; 121 | overflow-x:hidden; 122 | } 123 | 124 | #navbar a { 125 | text-decoration: none; 126 | display: block; 127 | margin:0; 128 | color: black; 129 | float: left; 130 | } 131 | 132 | #navbar img { 133 | display: block; 134 | float: left; 135 | padding: 5px 10px 5px 10px; 136 | } 137 | 138 | #navbar p { 139 | margin: 0; 140 | vertical-align: middle; 141 | } 142 | 143 | /* Logo */ 144 | 145 | #logo { 146 | padding: 10px 0 10px 0; 147 | height: 100px; 148 | margin: 0; 149 | } 150 | 151 | #logo img { 152 | width: 50%; 153 | margin-left: auto; 154 | margin-right: auto; 155 | display: block; 156 | float: none; 157 | } 158 | 159 | 160 | /* content-wrapper */ 161 | 162 | #content-wrapper { 163 | display: block; 164 | width: calc(100% - 180px); 165 | flex: 1; 166 | height: 100%; 167 | min-height: 100vh; 168 | position: absolute; 169 | top: 0; 170 | right: 0; 171 | bottom: 0; 172 | left: 180px; 173 | } 174 | 175 | #content-wrapper-inner { 176 | display: flex; 177 | flex-direction: row; 178 | flex-wrap: nowrap; 179 | width: 100%; 180 | min-height: 100vh; 181 | position: relative; 182 | align-items: stretch; 183 | } 184 | 185 | /* Button & forms */ 186 | 187 | textarea { 188 | width: 98%; 189 | } 190 | 191 | input[type=number]{ 192 | width: 6em; 193 | } 194 | 195 | form { 196 | margin:0px; padding:0px; display:inline; 197 | } 198 | 199 | .butt { 200 | font-size: 100%; 201 | padding: 5px; 202 | padding-right: 10px; 203 | background-color: #4CAF50; 204 | color: black; 205 | -webkit-transition-duration: 0.4s; /* Safari */ 206 | transition-duration: 0.4s; 207 | border: 2px solid #307533; 208 | margin: 5px 5px 5px 0px; 209 | display: flex; 210 | justify-content: safe left; 211 | align-items: center; 212 | flex-grow: 0; 213 | border-radius: 4px; 214 | } 215 | 216 | .butt label:hover { 217 | background-color: #efefef; 218 | color: black; 219 | } 220 | 221 | button { 222 | font-size: 100%; 223 | padding: 5px; 224 | padding-right: 10px; 225 | background-color: #4CAF50; 226 | color: black; 227 | -webkit-transition-duration: 0.4s; /* Safari */ 228 | transition-duration: 0.4s; 229 | border: 2px solid #307533; 230 | margin: 5px 5px 5px 0px; 231 | display: flex; 232 | justify-content: center; 233 | align-items: center; 234 | border-radius: 4px; 235 | } 236 | 237 | button:hover, input[type=file]:hover, input[type=button]:hover { 238 | background-color: #efefef; 239 | color: black; 240 | } 241 | 242 | .buttonimg { 243 | padding: 0 10px 0 10px; 244 | width: 20px; 245 | } 246 | 247 | input[type=file], input[type=button] { 248 | } 249 | 250 | select { 251 | appearance: none; 252 | font-size: 12pt; 253 | margin: 5px; 254 | padding: 5px; 255 | border: 2px solid #4CAF50; 256 | -webkit-appearance: none; 257 | -moz-appearance: none; 258 | background: #FFF; 259 | } 260 | 261 | input[type=number] { 262 | margin: 5px; 263 | padding: 5px; 264 | border: 2px solid #4CAF50; 265 | border-radius: 4px; 266 | font-size: 12pt; 267 | } 268 | 269 | /* tables */ 270 | 271 | thead tr th { 272 | position: sticky; 273 | top: 0; 274 | padding: 7px; 275 | } 276 | 277 | table { 278 | border-collapse: collapse; 279 | width: 100%; 280 | } 281 | 282 | tbody { 283 | overflow: auto; 284 | } 285 | 286 | td { 287 | padding: 2px; 288 | text-align: left; 289 | border-bottom: 1px solid #ddd; 290 | } 291 | 292 | th { 293 | padding: 2px; 294 | text-align: left; 295 | border-bottom: 1px solid #ddd; 296 | background-color:#e5e5e5; 297 | z-index: 1; 298 | } 299 | 300 | tr:hover { 301 | background-color:#f5f5f5; 302 | } 303 | 304 | .tabdiv { 305 | display: none; 306 | margin-left: 60px; 307 | padding-left: 20px; 308 | float: left; 309 | background-color:#FFFFFF; 310 | } 311 | 312 | .tablink { 313 | width: 200px; 314 | display: flex; 315 | justify-content: center; 316 | align-items: center; 317 | line-height: 30px; 318 | padding: 0px; 319 | } 320 | 321 | .fullheight { 322 | height: 90vh; 323 | } 324 | 325 | /* content */ 326 | 327 | #content { 328 | display: flex; 329 | height: 100%; 330 | } 331 | 332 | /* paramaters */ 333 | 334 | #params table { 335 | width: 100%; 336 | } 337 | 338 | #canvas { 339 | width: 100%; 340 | } 341 | 342 | /* content divs */ 343 | 344 | .main-content { 345 | order: 2; 346 | margin: 0px; 347 | flex: 10 1 300px; 348 | height: 100vh; 349 | } 350 | 351 | .main-left { 352 | order: 2; 353 | flex: 10 1 200px; 354 | margin-right: 10px; 355 | height: 100vh; 356 | overflow-y: auto; 357 | } 358 | 359 | .main-right { 360 | height: 100%; 361 | padding-top: 10px; 362 | order: 3; 363 | flex: 0 0 15%; 364 | } 365 | 366 | /* dashboard */ 367 | 368 | .dash-row { 369 | display: flex; 370 | } 371 | 372 | .dash-box { 373 | padding: 15px; 374 | margin: 5px; 375 | border-radius: 10px; 376 | border: 2px solid #444; 377 | height: 200px; 378 | flex: 1 1 300px; 379 | } 380 | 381 | .dash-box h3 { 382 | padding: 5px 0px 10px 0px; 383 | } 384 | 385 | /* CAN Mapping */ 386 | 387 | #addcanmapping { 388 | display: none; 389 | padding: 10px; 390 | border-radius: 20px; 391 | background: #DDD; 392 | margin-top: 30px; 393 | } 394 | 395 | #addcanmapping h3 { 396 | padding: 8px; 397 | } 398 | 399 | #canmapping tbody { 400 | padding: 100px; 401 | } 402 | 403 | /* modal */ 404 | 405 | .modal-overlay { 406 | display: none; 407 | position: fixed; 408 | width: 100%; 409 | height: 100%; 410 | z-index: 5; 411 | left: 0; 412 | top: 0; 413 | background-color: rgb(0,0,0); 414 | background-color: rgba(0,0,0,0.4); 415 | //overflow: auto; 416 | } 417 | 418 | .large-modal-container { 419 | background-color: #fefefe; 420 | margin: 5% auto; 421 | padding: 40px; 422 | border: 1px solid #888; 423 | border-radius: 10px; 424 | width: 50%; 425 | height: 50%; 426 | } 427 | 428 | .large-modal-container h2 { 429 | padding-bottom: 10px; 430 | display: inline; 431 | } 432 | 433 | #large-modal-header-div { 434 | padding: 0 0 1em 0; 435 | } 436 | 437 | .modal-content { 438 | //border: 1px solid #DDD; 439 | height: 90%; 440 | width: 100%; 441 | overflow: auto; 442 | } 443 | 444 | .modal-close { 445 | color: #aaaaaa; 446 | float: right; 447 | font-size: 28px; 448 | font-weight: bold; 449 | } 450 | 451 | .small-modal-container { 452 | background-color: #fefefe; 453 | margin: 5% auto; 454 | padding: 20px 40px 20px 40px; 455 | border: 1px solid #888; 456 | border-radius: 10px; 457 | width: 300px; 458 | } 459 | 460 | .can-mapping-modal-container { 461 | background-color: #fefefe; 462 | margin: 5% auto; 463 | padding: 40px; 464 | border: 1px solid #888; 465 | width: 50%; 466 | border-radius: 5px; 467 | } 468 | 469 | #commandoutput { 470 | height: calc(100% - 60px); 471 | overflow: auto; 472 | margin-bottom: 5px; 473 | } 474 | 475 | #commandinput { 476 | width: 80%; 477 | } 478 | 479 | #wifiInner { 480 | display: block; 481 | } 482 | 483 | /* plot */ 484 | 485 | .plotField { 486 | display: flex; 487 | } 488 | 489 | /* logger */ 490 | 491 | .logger-field { 492 | display: flex; 493 | } 494 | 495 | /* controls below navbar */ 496 | #nodeid-div input { 497 | width: 3em; 498 | } 499 | 500 | .control { 501 | margin-left: 10px; 502 | margin-top: 10px; 503 | } 504 | 505 | .beta-feature { 506 | display: none; 507 | } 508 | /* version */ 509 | 510 | #version { 511 | padding: 0px; 512 | padding-top: 5px; 513 | padding-bottom: 5px; 514 | margin: 0px; 515 | margin-left: 10px; 516 | margin-bottom: 10px; 517 | text-align: center; 518 | float: left; 519 | width: 160px; 520 | font-size: 10pt; 521 | border: 1px solid black; 522 | border-radius: 4px; 523 | height: 30px; 524 | 525 | } 526 | 527 | #version p { 528 | font-size: 12px; 529 | padding: 0px; 530 | margin: 0px; 531 | color: #444; 532 | margin-bottom: auto; 533 | } 534 | /* switch */ 535 | 536 | /* The switch - the box around the slider */ 537 | .switch { 538 | position: relative; 539 | display: inline-block; 540 | width: 30px; 541 | height: 17px; 542 | margin-right: 10px; 543 | } 544 | 545 | /* Hide default HTML checkbox */ 546 | .switch input { 547 | opacity: 0; 548 | width: 0; 549 | height: 0; 550 | } 551 | 552 | /* The slider */ 553 | 554 | .slider { 555 | position: absolute; 556 | cursor: pointer; 557 | top: 0; 558 | left: 0; 559 | right: 0; 560 | bottom: 0; 561 | background-color: #ccc; 562 | -webkit-transition: .4s; 563 | transition: .4s; 564 | } 565 | 566 | .slider:before { 567 | position: absolute; 568 | content: ""; 569 | height: 9px; 570 | width: 9px; 571 | left: 4px; 572 | bottom: 4px; 573 | background-color: white; 574 | -webkit-transition: .4s; 575 | transition: .4s; 576 | } 577 | 578 | input:checked + .slider { 579 | background-color: orange; 580 | } 581 | 582 | input:focus + .slider { 583 | box-shadow: 0 0 1px #2196F3; 584 | } 585 | 586 | input:checked + .slider:before { 587 | -webkit-transform: translateX(13px); 588 | -ms-transform: translateX(13px); 589 | transform: translateX(13px); 590 | } 591 | 592 | /* Rounded sliders */ 593 | .slider.round { 594 | border-radius: 17px; 595 | } 596 | 597 | .slider.round:before { 598 | border-radius: 50%; 599 | } 600 | 601 | 602 | /** TOOLTIP */ 603 | 604 | 605 | .tooltip { 606 | position: relative; 607 | display: inline-block; 608 | border-bottom: 1px dotted black; /* If you want dots under the hoverable text */ 609 | } 610 | 611 | .tooltip .tooltiptext { 612 | visibility: hidden; 613 | width: 120px; 614 | background-color: black; 615 | color: #fff; 616 | text-align: center; 617 | padding: 10px; 618 | border-radius: 6px; 619 | position: absolute; 620 | z-index: 10; 621 | top: -5px; 622 | left: 120%; 623 | } 624 | 625 | .tooltip:hover .tooltiptext { 626 | visibility: visible; 627 | } 628 | 629 | @media only screen and (max-width: 1000px) { 630 | #logo { 631 | width: 80px; 632 | height: 50px; 633 | } 634 | .buttonimg { 635 | width: 60px; 636 | } 637 | } 638 | 639 | /* notification bar */ 640 | 641 | .communication-error-bar { 642 | display: none; 643 | padding: 15px; 644 | background-color: #f44336; 645 | color: white; 646 | z-index: 100; 647 | margin: 0 auto; 648 | position: relative; 649 | width: 30%; 650 | top: 0px; 651 | clear: left; 652 | height: 20px; 653 | border-radius: 0px 0px 20px 20px; 654 | } 655 | 656 | .communication-error-bar p { 657 | text-align: center; 658 | } 659 | -------------------------------------------------------------------------------- /data/syncofs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | Syncofs 26 | 27 | 28 | 29 | 30 | 31 | 235 | 243 | 244 | 245 | 246 | 247 | 250 | 253 | 254 | 255 | 260 | 261 | 262 | 265 | 268 | 269 | 270 | 271 | 272 |
248 |

syncofs

249 |
251 |

manualid

252 |
256 | 257 | 258 | 259 |
263 | 264 | 266 | 267 |
273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /data/wifi-updated.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | Modify Wifi settings 26 | 27 | 28 | 29 | Wifi Settings updated. Return to main page. 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /data/wifi.html: -------------------------------------------------------------------------------- 1 | 20 |
21 |

Access Point Settings

22 | Here you can specify SSID and password of the access point that is created by the inverter. 23 |
24 |

25 |


26 | 27 | 29 |
30 |
31 |

Station Settings

32 | Here you can specify a Wifi network that the inverter joins. 33 |
34 |

35 |

36 | Current IP Address: %staIP% 37 | 38 | 40 |
41 |
-------------------------------------------------------------------------------- /data/wifi.js: -------------------------------------------------------------------------------- 1 | var wifi = { 2 | 3 | wifiValidatePasswordLength: function(pw) 4 | { 5 | document.getElementById("apsubmit").disabled = pw.length < 8; 6 | }, 7 | 8 | populateWiFiTab: function() 9 | { 10 | var wifiTab = document.getElementById("wifi"); 11 | var wifiFetchRequest = new XMLHttpRequest(); 12 | wifiFetchRequest.onload = function() 13 | { 14 | wifiTab.innerHTML = this.responseText; 15 | } 16 | wifiFetchRequest.open("GET", "/wifi"); 17 | wifiFetchRequest.send(); 18 | }, 19 | 20 | } -------------------------------------------------------------------------------- /doc/ARDUINO_IDE_setup.md: -------------------------------------------------------------------------------- 1 | Arduino IDE setup 2 | ================= 3 | 4 | # Table of Contents 5 |
6 | Click to open TOC 7 | 8 | 9 | - [About Arduino IDE](#about-arduino-ide) 10 | - [Installing Arduino IDE and plugins](#installing-arduino-ide-and-plugins) 11 | - [Configuring Arduino IDE](#configuring-arduino-ide) 12 | 13 | 14 |
15 | 16 | # About Arduino IDE 17 | 18 | Arduino IDE is an open-source integrated development environment with support for multiple platforms. 19 | 20 | Learn more : https://www.arduino.cc/en/software 21 | 22 | # Installing Arduino IDE and plugins 23 | 24 | [Download](https://www.arduino.cc/en/software#download) the IDE, and follow the [Getting Started](https://www.arduino.cc/en/Guide) 25 | guide. 26 | 27 | Additionally, install (by following the instructions in the following links) the 2 following IDE plugins: 28 | * https://github.com/esp8266/arduino-esp8266fs-plugin 29 | * https://github.com/earlephilhower/arduino-esp8266littlefs-plugin 30 | 31 | When you start the Arduino IDE, you should now have two additional options in the `Tools` menu: 32 | * ESP8266 LittleFS Data Upload 33 | * ESP8266 Sketch Data Upload 34 | 35 | # Configuring Arduino IDE 36 | 37 | In the `Preferences` pane for the IDE, look for `Additional Boards Manager URLs`, click on the button on the right, and append the following URL: 38 | `https://arduino.esp8266.com/stable/package_esp8266com_index.json` 39 | 40 | In the `Tools` menu, select the `Board` entry, click on the `Boards Manager...` submenu, enter `esp8266` in the search box and press Enter. 41 | 42 | You should have one entry named `esp8266 by ESP8266 Community` ; click on `Install` and wait for installation. 43 | 44 | Open the project `File` > `Open` and navigate to the `FSBrowser.ino` file, and open it. 45 | 46 | Go back to the `Tools` menu, and in the `Board` entry select the `ESP8266 Boards` choose your board (`Olimex MOD-WIFI-ESP8266(-DEV)) 47 | 48 | Configure the other parameters the following way: 49 | 50 | * Upload Speed : 921600 51 | * CPU Frequency : 80MHz 52 | * Flash Size: 2MB (FS:512KB OTA:~768KB) 53 | * Debug port: Disabled 54 | * Debug level: None 55 | * lwIP Variant: v2 Lower Memory 56 | * VTables: Flash 57 | * C++ Exceptions: Disabled (new aborts on oom) 58 | * Stack Protection: Disabled 59 | * Erase Flash: Only Sketch 60 | * SSL Support: All SSL Ciphers (most compatible) 61 | * MMU: 32KB cache + 32KB IRAM (balanced) 62 | * Non-32-bit access: Use pgm_read macros for IRAM/PROGMEM 63 | * Port: (_lookup the port on which your USB/Serial adapter is. You can also choose the board if it's up, connected to your WiFi, for OTA flashing_) 64 | 65 | That's it ! Your IDE should now be configured for your day to day operations. 66 | -------------------------------------------------------------------------------- /doc/ARDUINO_IDE_usage.md: -------------------------------------------------------------------------------- 1 | Arduino IDE usage 2 | ================= 3 | 4 | # Table of Contents 5 |
6 | Click to open TOC 7 | 8 | 9 | - [Compilation](#compilation) 10 | - [Compile and Flash board](#compile-and-flash-board) 11 | - [Flashing the filesystem](#flashing-the-filesystem) 12 | - [Debugging](#debugging) 13 | 14 | 15 |
16 | 17 | # Compilation 18 | To compile, you need to choose, in the `Sketch` menu, `Verify / Compile` 19 | 20 | # Compile and Flash board 21 | Choose, in the `Sketch` menu, `Upload`. It will compile and send to the board with the chosen method (serial / OTA) 22 | 23 | # Flashing the filesystem 24 | Choose, in the `Tools` menu, the `ESP8266 Sketch Data Upload` option, which will package, and flash the files in the `data` directory (HTML files for the web interface) 25 | 26 | # Debugging 27 | In case you're debugging and have enabled (`Tools` > `Debug port` / `Tools` > `Debug level`) some kind of debugging output, you may use the `Tools` / `Serial monitor` to check the debug messages. 28 | 29 | Please note that the serial port is intended to be attached to the inverter (or other application), so do not leave debug messages in normal operation. -------------------------------------------------------------------------------- /doc/PLATFORMIO_setup.md: -------------------------------------------------------------------------------- 1 | # About PlatformIO 2 | 3 | PlatformIO is an open-source development environment with support for multiple platforms. 4 | 5 | Learn more : https://docs.platformio.org/ 6 | 7 | # Installing PlatformIO Core (Command line) 8 | 9 | Follow these instructions for the initial install: 10 | * [PlatformIO Core](http://docs.platformio.org/page/core.html) 11 | 12 | Ensure that the tools are properly installed, and that you can test the following command: 13 | ``` 14 | $ pio --version 15 | PlatformIO Core, version 6.1.0 16 | ``` 17 | 18 | # Customization of the project configuration file 19 | 20 | In some cases you will want to customize some aspects of the project configuration file `platformio.ini`. 21 | 22 | If these aspects are useful to others, then you should consider doing a PR to share those change. 23 | 24 | However in some cases there are some changes that are specific to your working environment, for example the 25 | name of the serial port on which you communicate with the ESP8266 board, etc... 26 | 27 | To that end, you can create a local file named `platformio-local-override.ini` ; which is explicitly ignored 28 | by the git version control (cf `.gitignore`). 29 | In this file you'll be able to override the settings of the main `platformio.ini` file without having pending 30 | file modifications. 31 | 32 | As a starting point you can create it from the existing `platformio-local-override.ini.example`: 33 | ``` 34 | cp platformio-local-override.ini.example platformio-local-override.ini 35 | ``` 36 | and modify the file according to your own tastes / needs. 37 | -------------------------------------------------------------------------------- /doc/PLATFORMIO_usage.md: -------------------------------------------------------------------------------- 1 | PlatformIO usage 2 | ================ 3 | 4 | # Table of Contents 5 |
6 | Click to open TOC 7 | 8 | 9 | - [Existing targets and environments](#existing-targets-and-environments) 10 | - [Building the code](#building-the-code) 11 | - [Flashing resulting firmware to the ESP8266 board](#flashing-resulting-firmware-to-the-esp8266-board) 12 | - [Check device output](#check-device-output) 13 | - [ROM bootloader messages](#rom-bootloader-messages) 14 | - [Normal output to the inverter](#normal-output-to-the-inverter) 15 | - [Building and flashing the filesystem \(optional\)](#building-and-flashing-the-filesystem-optional) 16 | - [Building the filesystem](#building-the-filesystem) 17 | - [Flashing the filesystem](#flashing-the-filesystem) 18 | - [Clean build files if needed](#clean-build-files-if-needed) 19 | 20 | 21 |
22 | 23 | All the following commands assume that you are at the 'root' of the project, i.e. 24 | in the same directory as the `platformio.ini` file. 25 | 26 | # Existing targets and environments 27 | You can list existing target and environments with 28 | ``` 29 | $ pio run --list-targets 30 | Environment Group Name Title Description 31 | ------------- -------- ----------- --------------------------- ---------------------- 32 | debug Platform buildfs Build Filesystem Image 33 | debug Platform erase Erase Flash 34 | debug Platform size Program Size Calculate program size 35 | debug Platform upload Upload 36 | debug Platform uploadfs Upload Filesystem Image 37 | debug Platform uploadfsota Upload Filesystem Image OTA 38 | 39 | release Platform buildfs Build Filesystem Image 40 | release Platform erase Erase Flash 41 | release Platform size Program Size Calculate program size 42 | release Platform upload Upload 43 | release Platform uploadfs Upload Filesystem Image 44 | release Platform uploadfsota Upload Filesystem Image OTA 45 | ``` 46 | 47 | Some additional targets exist but are not show in this list: 48 | * `clean` : clean all built files 49 | * `envdump` : dump current build environment 50 | * `monitor` : automatically start pio device monitor after successful build operation. 51 | 52 | You will be able to select an environment with the `-e`, or `--environment` command line flag. The default environment is `release` 53 | You will be able to select an target with the `-t`, or `--target` command line flag. With no flag, the default behaviour is to build the sources. 54 | 55 | # Building the code 56 | Run this command (need to do this after each update of the files): 57 | 58 | ``` 59 | $ pio run 60 | Processing release (platform: espressif8266; framework: arduino; board: modwifi) 61 | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 62 | Verbose mode can be enabled via `-v, --verbose` option 63 | CONFIGURATION: https://docs.platformio.org/page/boards/espressif8266/modwifi.html 64 | PLATFORM: Espressif 8266 (4.0.1) > Olimex MOD-WIFI-ESP8266(-DEV) 65 | HARDWARE: ESP8266 80MHz, 80KB RAM, 2MB Flash 66 | PACKAGES: 67 | - framework-arduinoespressif8266 @ 3.30002.0 (3.0.2) 68 | - tool-esptool @ 1.413.0 (4.13) 69 | - tool-esptoolpy @ 1.30300.0 (3.3.0) 70 | - toolchain-xtensa @ 2.100300.210717 (10.3.0) 71 | Converting FSBrowser.ino 72 | LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf 73 | LDF Modes: Finder ~ chain, Compatibility ~ soft 74 | Found 35 compatible libraries 75 | Scanning dependencies... 76 | Dependency Graph 77 | |-- ArduinoOTA @ 1.0 78 | | |-- ESP8266WiFi @ 1.0 79 | | |-- ESP8266mDNS @ 1.2 80 | | | |-- ESP8266WiFi @ 1.0 81 | |-- ESP8266HTTPUpdateServer @ 1.0 82 | | |-- ESP8266WebServer @ 1.0 83 | | | |-- ESP8266WiFi @ 1.0 84 | | |-- ESP8266WiFi @ 1.0 85 | |-- ESP8266WebServer @ 1.0 86 | | |-- ESP8266WiFi @ 1.0 87 | |-- ESP8266WiFi @ 1.0 88 | |-- ESP8266mDNS @ 1.2 89 | | |-- ESP8266WiFi @ 1.0 90 | |-- Ticker @ 1.0 91 | Building in release mode 92 | Compiling .pio/build/release/src/FSBrowser.ino.cpp.o 93 | Compiling .pio/build/release/src/src/arm_debug.cpp.o 94 | ... 95 | 96 | ... 97 | Compiling .pio/build/release/FrameworkArduino/umm_malloc/umm_malloc.cpp.o 98 | Compiling .pio/build/release/FrameworkArduino/umm_malloc/umm_poison.c.o 99 | Archiving .pio/build/release/libFrameworkArduino.a 100 | Indexing .pio/build/release/libFrameworkArduino.a 101 | Linking .pio/build/release/firmware.elf 102 | Retrieving maximum program size .pio/build/release/firmware.elf 103 | Checking size .pio/build/release/firmware.elf 104 | Advanced Memory Usage is available via "PlatformIO Home > Project Inspect" 105 | RAM: [==== ] 38.9% (used 31896 bytes from 81920 bytes) 106 | Flash: [==== ] 36.9% (used 385089 bytes from 1044464 bytes) 107 | Building .pio/build/release/firmware.bin 108 | Creating BIN file ".pio/build/release/firmware.bin" using "..../.platformio/packages/framework-arduinoespressif8266/bootloaders/eboot/eboot.elf" and ".pio/build/release/firmware.elf" 109 | ====================================================================================================================================== [SUCCESS] Took 9.97 seconds ====================================================================================================================================== 110 | 111 | Environment Status Duration 112 | ------------- -------- ------------ 113 | release SUCCESS 00:00:09.971 114 | ====================================================================================================================================== 115 | ``` 116 | 117 | 118 | # Flashing resulting firmware to the ESP8266 board 119 | Note: you should first setup the ESP8266 in UART mode. (In general, keep the button depressed when applying power, then release the button) 120 | 121 | ``` 122 | $ pio run --target upload 123 | Processing release (platform: espressif8266; framework: arduino; board: modwifi) 124 | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 125 | Verbose mode can be enabled via `-v, --verbose` option 126 | CONFIGURATION: https://docs.platformio.org/page/boards/espressif8266/modwifi.html 127 | PLATFORM: Espressif 8266 (4.0.1) > Olimex MOD-WIFI-ESP8266(-DEV) 128 | HARDWARE: ESP8266 80MHz, 80KB RAM, 2MB Flash 129 | PACKAGES: 130 | - framework-arduinoespressif8266 @ 3.30002.0 (3.0.2) 131 | - tool-esptool @ 1.413.0 (4.13) 132 | - tool-esptoolpy @ 1.30300.0 (3.3.0) 133 | - tool-mklittlefs @ 1.203.210628 (2.3) 134 | - tool-mkspiffs @ 1.200.0 (2.0) 135 | - toolchain-xtensa @ 2.100300.210717 (10.3.0) 136 | Converting FSBrowser.ino 137 | LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf 138 | LDF Modes: Finder ~ chain, Compatibility ~ soft 139 | Found 35 compatible libraries 140 | Scanning dependencies... 141 | Dependency Graph 142 | |-- ArduinoOTA @ 1.0 143 | | |-- ESP8266WiFi @ 1.0 144 | | |-- ESP8266mDNS @ 1.2 145 | | | |-- ESP8266WiFi @ 1.0 146 | |-- ESP8266HTTPUpdateServer @ 1.0 147 | | |-- ESP8266WebServer @ 1.0 148 | | | |-- ESP8266WiFi @ 1.0 149 | | |-- ESP8266WiFi @ 1.0 150 | |-- ESP8266WebServer @ 1.0 151 | | |-- ESP8266WiFi @ 1.0 152 | |-- ESP8266WiFi @ 1.0 153 | |-- ESP8266mDNS @ 1.2 154 | | |-- ESP8266WiFi @ 1.0 155 | |-- Ticker @ 1.0 156 | Building in release mode 157 | Compiling .pio/build/release/src/FSBrowser.ino.cpp.o 158 | ... 159 | 160 | ... 161 | Retrieving maximum program size .pio/build/release/firmware.elf 162 | Checking size .pio/build/release/firmware.elf 163 | Advanced Memory Usage is available via "PlatformIO Home > Project Inspect" 164 | RAM: [==== ] 38.9% (used 31896 bytes from 81920 bytes) 165 | Flash: [==== ] 36.9% (used 385089 bytes from 1044464 bytes) 166 | Configuring upload protocol... 167 | AVAILABLE: espota, esptool 168 | CURRENT: upload_protocol = esptool 169 | Looking for upload port... 170 | Using manually specified: /dev/cu.usbserial-DGAJb113318 171 | Uploading .pio/build/release/firmware.bin 172 | esptool.py v3.3 173 | Serial port /dev/cu.usbserial-DGAJb113318 174 | WARNING: Pre-connection option "no_reset" was selected. Connection may fail if the chip is not in bootloader or flasher stub mode. 175 | Connecting.... 176 | Chip is ESP8266EX 177 | Features: WiFi 178 | Crystal is 26MHz 179 | MAC: c4:5b:be:76:1b:b8 180 | Uploading stub... 181 | Running stub... 182 | Stub running... 183 | Changing baud rate to 921600 184 | Changed. 185 | Configuring flash size... 186 | Flash will be erased from 0x00000000 to 0x0005ffff... 187 | Compressed 389248 bytes to 277860... 188 | Writing at 0x00000000... (5 %) 189 | Writing at 0x00005b7b... (11 %) 190 | Writing at 0x0000b880... (17 %) 191 | Writing at 0x00011ae4... (23 %) 192 | Writing at 0x00017987... (29 %) 193 | Writing at 0x0001d5ad... (35 %) 194 | Writing at 0x00022dbe... (41 %) 195 | Writing at 0x00028689... (47 %) 196 | Writing at 0x0002df5c... (52 %) 197 | Writing at 0x000330f0... (58 %) 198 | Writing at 0x000380f6... (64 %) 199 | Writing at 0x0003d293... (70 %) 200 | Writing at 0x00042b6d... (76 %) 201 | Writing at 0x000482ec... (82 %) 202 | Writing at 0x0004d696... (88 %) 203 | Writing at 0x00052cb4... (94 %) 204 | Writing at 0x0005986c... (100 %) 205 | Wrote 389248 bytes (277860 compressed) at 0x00000000 in 3.8 seconds (effective 816.7 kbit/s)... 206 | Hash of data verified. 207 | 208 | Leaving... 209 | Soft resetting... 210 | ====================================================================================================================================== [SUCCESS] Took 8.37 seconds ====================================================================================================================================== 211 | 212 | Environment Status Duration 213 | ------------- -------- ------------ 214 | release SUCCESS 00:00:08.371 215 | ====================================================================================================================================== 1 succeeded in 00:00:08.371 ====================================================================================================================================== 216 | ``` 217 | 218 | 219 | # Check device output 220 | 221 | ## ROM bootloader messages 222 | 223 | Most of the ESP8266 boards have a 26MHz crystal (instead of the standard 40MHz) so the ROM bootloader outputs at 115200 * 26/40 = 74880. 224 | 225 | These messages are not very useful but can help you to confirm that the board is working as expected. 226 | ``` 227 | $ pio device monitor --baud 74880 228 | --- Terminal on /dev/cu.usbserial-DGAJb113318 | 74880 8-N-1 229 | --- Available filters and text transformations: colorize, debug, default, direct, esp8266_exception_decoder, hexlify, log2file, nocontrol, printable, send_on_enter, time 230 | --- More details at https://bit.ly/pio-monitor-filters 231 | --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H 232 | 233 | ets Jan 8 2013,rst cause:1, boot mode:(3,0) 234 | 235 | load 0x4010f000, len 3460, room 16 236 | tail 4 237 | chksum 0xcc 238 | load 0x3fff20b8, len 40, room 4 239 | tail 4 240 | chksum 0xc9 241 | csum 0xc9 242 | v0005f080 243 | ~ld 244 | ``` 245 | 246 | ## Normal output to the inverter 247 | 248 | In normal operations, the Web Interface wants to talk to an inverter. The following will show the messages that it tries to send to the inverter: 249 | ``` 250 | $ pio device monitor 251 | --- Terminal on /dev/cu.usbserial-DGAJb113318 | 115200 8-N-1 252 | --- Available filters and text transformations: colorize, debug, default, direct, esp8266_exception_decoder, hexlify, log2file, nocontrol, printable, send_on_enter, time 253 | --- More details at https://bit.ly/pio-monitor-filters 254 | --- Quit: Ctrl+C | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H 255 | fastuart 256 | 257 | json 258 | ``` 259 | 260 | # Building and flashing the filesystem (optional) 261 | It's also possible to automate the building of the filesystem, and its uploading. 262 | 263 | (It's optional as you can also do it by using the `/edit` endpoint of the main application - there's an `upload.sh` file for this.) 264 | 265 | ## Building the filesystem 266 | This will only build it. 267 | ``` 268 | $ pio run --target buildfs 269 | Processing release (platform: espressif8266; framework: arduino; board: modwifi) 270 | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 271 | Verbose mode can be enabled via `-v, --verbose` option 272 | CONFIGURATION: https://docs.platformio.org/page/boards/espressif8266/modwifi.html 273 | PLATFORM: Espressif 8266 (4.0.1) > Olimex MOD-WIFI-ESP8266(-DEV) 274 | HARDWARE: ESP8266 80MHz, 80KB RAM, 2MB Flash 275 | PACKAGES: 276 | - framework-arduinoespressif8266 @ 3.30002.0 (3.0.2) 277 | - tool-esptool @ 1.413.0 (4.13) 278 | - tool-esptoolpy @ 1.30300.0 (3.3.0) 279 | - tool-mklittlefs @ 1.203.210628 (2.3) 280 | - tool-mkspiffs @ 1.200.0 (2.0) 281 | - toolchain-xtensa @ 2.100300.210717 (10.3.0) 282 | Converting FSBrowser.ino 283 | LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf 284 | LDF Modes: Finder ~ chain, Compatibility ~ soft 285 | Found 35 compatible libraries 286 | Scanning dependencies... 287 | Dependency Graph 288 | |-- ArduinoOTA @ 1.0 289 | | |-- ESP8266WiFi @ 1.0 290 | | |-- ESP8266mDNS @ 1.2 291 | | | |-- ESP8266WiFi @ 1.0 292 | |-- ESP8266HTTPUpdateServer @ 1.0 293 | | |-- ESP8266WebServer @ 1.0 294 | | | |-- ESP8266WiFi @ 1.0 295 | | |-- ESP8266WiFi @ 1.0 296 | |-- ESP8266WebServer @ 1.0 297 | | |-- ESP8266WiFi @ 1.0 298 | |-- ESP8266WiFi @ 1.0 299 | |-- ESP8266mDNS @ 1.2 300 | | |-- ESP8266WiFi @ 1.0 301 | |-- Ticker @ 1.0 302 | Building in release mode 303 | Building file system image from 'FSBrowser/data' directory to .pio/build/release/spiffs.bin 304 | /wifi-updated.html 305 | /gauges.html 306 | /ajax-loader.gif 307 | /index.html 308 | /inverter.js 309 | /remote.html 310 | /chart.min.js.gz 311 | /syncofs.html 312 | /gauge.min.js.gz 313 | /log.js 314 | /index.js 315 | /jquery.core.min.js.gz 316 | /chartjs-annotation.min.js.gz 317 | /wifi.html 318 | /log.html 319 | /style.css 320 | /gauges.js 321 | /jquery.knob.min.js.gz 322 | /refresh.png 323 | ====================================================================================================================================== [SUCCESS] Took 1.38 seconds ====================================================================================================================================== 324 | 325 | Environment Status Duration 326 | ------------- -------- ------------ 327 | release SUCCESS 00:00:01.383 328 | ====================================================================================================================================== 1 succeeded in 00:00:01.383 ====================================================================================================================================== 329 | ``` 330 | 331 | ## Flashing the filesystem 332 | This action does the build + flash steps in one operation. 333 | ``` 334 | $ pio run --target uploadfs 335 | Processing release (platform: espressif8266; framework: arduino; board: modwifi) 336 | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 337 | Verbose mode can be enabled via `-v, --verbose` option 338 | CONFIGURATION: https://docs.platformio.org/page/boards/espressif8266/modwifi.html 339 | PLATFORM: Espressif 8266 (4.0.1) > Olimex MOD-WIFI-ESP8266(-DEV) 340 | HARDWARE: ESP8266 80MHz, 80KB RAM, 2MB Flash 341 | PACKAGES: 342 | - framework-arduinoespressif8266 @ 3.30002.0 (3.0.2) 343 | - tool-esptool @ 1.413.0 (4.13) 344 | - tool-esptoolpy @ 1.30300.0 (3.3.0) 345 | - tool-mklittlefs @ 1.203.210628 (2.3) 346 | - tool-mkspiffs @ 1.200.0 (2.0) 347 | - toolchain-xtensa @ 2.100300.210717 (10.3.0) 348 | Converting FSBrowser.ino 349 | LDF: Library Dependency Finder -> https://bit.ly/configure-pio-ldf 350 | LDF Modes: Finder ~ chain, Compatibility ~ soft 351 | Found 35 compatible libraries 352 | Scanning dependencies... 353 | Dependency Graph 354 | |-- ArduinoOTA @ 1.0 355 | | |-- ESP8266WiFi @ 1.0 356 | | |-- ESP8266mDNS @ 1.2 357 | | | |-- ESP8266WiFi @ 1.0 358 | |-- ESP8266HTTPUpdateServer @ 1.0 359 | | |-- ESP8266WebServer @ 1.0 360 | | | |-- ESP8266WiFi @ 1.0 361 | | |-- ESP8266WiFi @ 1.0 362 | |-- ESP8266WebServer @ 1.0 363 | | |-- ESP8266WiFi @ 1.0 364 | |-- ESP8266WiFi @ 1.0 365 | |-- ESP8266mDNS @ 1.2 366 | | |-- ESP8266WiFi @ 1.0 367 | |-- Ticker @ 1.0 368 | Building in release mode 369 | Building file system image from 'FSBrowser/data' directory to .pio/build/release/spiffs.bin 370 | /wifi-updated.html 371 | /gauges.html 372 | /ajax-loader.gif 373 | /index.html 374 | /inverter.js 375 | /remote.html 376 | /chart.min.js.gz 377 | /syncofs.html 378 | /gauge.min.js.gz 379 | /log.js 380 | /index.js 381 | /jquery.core.min.js.gz 382 | /chartjs-annotation.min.js.gz 383 | /wifi.html 384 | /log.html 385 | /style.css 386 | /gauges.js 387 | /jquery.knob.min.js.gz 388 | /refresh.png 389 | Looking for upload port... 390 | Using manually specified: /dev/cu.usbserial-DGAJb113318 391 | Uploading .pio/build/release/spiffs.bin 392 | esptool.py v3.3 393 | Serial port /dev/cu.usbserial-DGAJb113318 394 | WARNING: Pre-connection option "no_reset" was selected. Connection may fail if the chip is not in bootloader or flasher stub mode. 395 | Connecting.... 396 | Chip is ESP8266EX 397 | Features: WiFi 398 | Crystal is 26MHz 399 | MAC: c4:5b:be:76:1b:b8 400 | Uploading stub... 401 | Running stub... 402 | Stub running... 403 | Changing baud rate to 921600 404 | Changed. 405 | Configuring flash size... 406 | Flash will be erased from 0x00180000 to 0x001f9fff... 407 | Compressed 499712 bytes to 140809... 408 | Writing at 0x00180000... (11 %) 409 | Writing at 0x0018c88a... (22 %) 410 | Writing at 0x001945b8... (33 %) 411 | Writing at 0x0019c553... (44 %) 412 | Writing at 0x001a4604... (55 %) 413 | Writing at 0x001af7a7... (66 %) 414 | Writing at 0x001b9998... (77 %) 415 | Writing at 0x001c5b85... (88 %) 416 | Writing at 0x001cdaa6... (100 %) 417 | Wrote 499712 bytes (140809 compressed) at 0x00180000 in 2.9 seconds (effective 1371.8 kbit/s)... 418 | Hash of data verified. 419 | 420 | Leaving... 421 | Soft resetting... 422 | ====================================================================================================================================== [SUCCESS] Took 5.93 seconds ====================================================================================================================================== 423 | 424 | Environment Status Duration 425 | ------------- -------- ------------ 426 | release SUCCESS 00:00:05.926 427 | ====================================================================================================================================== 1 succeeded in 00:00:05.926 ====================================================================================================================================== 428 | ``` 429 | 430 | # Clean build files if needed 431 | ``` 432 | $ pio run --target clean 433 | Processing release (platform: espressif8266; framework: arduino; board: modwifi) 434 | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 435 | Build environment is clean 436 | Done cleaning 437 | ====================================================================================================================================== [SUCCESS] Took 0.39 seconds ====================================================================================================================================== 438 | 439 | Environment Status Duration 440 | ------------- -------- ------------ 441 | release SUCCESS 00:00:00.389 442 | ====================================================================================================================================== 1 succeeded in 00:00:00.389 ====================================================================================================================================== 443 | ``` 444 | -------------------------------------------------------------------------------- /esp8266-web-interface.ino: -------------------------------------------------------------------------------- 1 | /* 2 | FSWebServer - Example WebServer with SPIFFS backend for esp8266 3 | Copyright (c) 2015 Hristo Gochkov. All rights reserved. 4 | This file is part of the ESP8266WebServer library for Arduino environment. 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this library; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | upload the contents of the data folder with MkSPIFFS Tool ("ESP8266 Sketch Data Upload" in Tools menu in Arduino IDE) 19 | or you can upload the contents of a folder if you CD in that folder and run the following command: 20 | for file in `ls -A1`; do curl -F "file=@$PWD/$file" esp8266fs.local/edit; done 21 | 22 | access the sample web page at http://esp8266fs.local 23 | edit the page by going to http://esp8266fs.local/edit 24 | */ 25 | /* 26 | * This file is part of the esp8266 web interface 27 | * 28 | * Copyright (C) 2018 Johannes Huebner 29 | * 30 | * This program is free software: you can redistribute it and/or modify 31 | * it under the terms of the GNU General Public License as published by 32 | * the Free Software Foundation, either version 3 of the License, or 33 | * (at your option) any later version. 34 | * 35 | * This program is distributed in the hope that it will be useful, 36 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 37 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 38 | * GNU General Public License for more details. 39 | * 40 | * You should have received a copy of the GNU General Public License 41 | * along with this program. If not, see . 42 | * 43 | */ 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | #define DBG_OUTPUT_PORT Serial 54 | 55 | const char* host = "inverter"; 56 | bool fastUart = false; 57 | bool fastUartAvailable = true; 58 | 59 | ESP8266WebServer server(80); 60 | ESP8266HTTPUpdateServer updater; 61 | //holds the current upload 62 | File fsUploadFile; 63 | Ticker sta_tick; 64 | 65 | //SWD over ESP8266 66 | /* 67 | https://github.com/scanlime/esp8266-arm-swd 68 | */ 69 | #include "src/arm_debug.h" 70 | #include 71 | uint32_t addr = 0x08000000; 72 | uint32_t addrEnd = 0x0801ffff; 73 | const uint8_t swd_clock_pin = 4; //GPIO4 (D2) 74 | const uint8_t swd_data_pin = 5; //GPIO5 (D1) 75 | ARMDebug swd(swd_clock_pin, swd_data_pin, ARMDebug::LOG_NONE); 76 | 77 | //format bytes 78 | String formatBytes(size_t bytes){ 79 | if (bytes < 1024){ 80 | return String(bytes)+"B"; 81 | } else if(bytes < (1024 * 1024)){ 82 | return String(bytes/1024.0)+"KB"; 83 | } else if(bytes < (1024 * 1024 * 1024)){ 84 | return String(bytes/1024.0/1024.0)+"MB"; 85 | } else { 86 | return String(bytes/1024.0/1024.0/1024.0)+"GB"; 87 | } 88 | } 89 | 90 | String getContentType(String filename){ 91 | if(server.hasArg("download")) return "application/octet-stream"; 92 | else if(filename.endsWith(".htm")) return "text/html"; 93 | else if(filename.endsWith(".html")) return "text/html"; 94 | else if(filename.endsWith(".css")) return "text/css"; 95 | else if(filename.endsWith(".js")) return "application/javascript"; 96 | else if(filename.endsWith(".png")) return "image/png"; 97 | else if(filename.endsWith(".gif")) return "image/gif"; 98 | else if(filename.endsWith(".jpg")) return "image/jpeg"; 99 | else if(filename.endsWith(".ico")) return "image/x-icon"; 100 | else if(filename.endsWith(".xml")) return "text/xml"; 101 | else if(filename.endsWith(".pdf")) return "application/x-pdf"; 102 | else if(filename.endsWith(".zip")) return "application/x-zip"; 103 | else if(filename.endsWith(".gz")) return "application/x-gzip"; 104 | return "text/plain"; 105 | } 106 | 107 | bool handleFileRead(String path){ 108 | //DBG_OUTPUT_PORT.println("handleFileRead: " + path); 109 | if(path.endsWith("/")) path += "index.html"; 110 | String contentType = getContentType(path); 111 | String pathWithGz = path + ".gz"; 112 | if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){ 113 | if(SPIFFS.exists(pathWithGz)) 114 | path += ".gz"; 115 | File file = SPIFFS.open(path, "r"); 116 | size_t sent = server.streamFile(file, contentType); 117 | file.close(); 118 | return true; 119 | } 120 | return false; 121 | } 122 | 123 | void handleFileUpload(){ 124 | if(server.uri() != "/edit") return; 125 | HTTPUpload& upload = server.upload(); 126 | if(upload.status == UPLOAD_FILE_START){ 127 | String filename = upload.filename; 128 | if(!filename.startsWith("/")) filename = "/"+filename; 129 | //DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename); 130 | fsUploadFile = SPIFFS.open(filename, "w"); 131 | filename = String(); 132 | } else if(upload.status == UPLOAD_FILE_WRITE){ 133 | //DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize); 134 | if(fsUploadFile) 135 | fsUploadFile.write(upload.buf, upload.currentSize); 136 | } else if(upload.status == UPLOAD_FILE_END){ 137 | if(fsUploadFile) 138 | fsUploadFile.close(); 139 | //DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize); 140 | } 141 | } 142 | 143 | void handleFileDelete(){ 144 | if(server.args() == 0) return server.send(500, "text/plain", "BAD ARGS"); 145 | String path = server.arg(0); 146 | //DBG_OUTPUT_PORT.println("handleFileDelete: " + path); 147 | if(path == "/") 148 | return server.send(500, "text/plain", "BAD PATH"); 149 | if(!SPIFFS.exists(path)) 150 | return server.send(404, "text/plain", "FileNotFound"); 151 | SPIFFS.remove(path); 152 | server.send(200, "text/plain", ""); 153 | path = String(); 154 | } 155 | 156 | void handleFileCreate(){ 157 | if(server.args() == 0) 158 | return server.send(500, "text/plain", "BAD ARGS"); 159 | String path = server.arg(0); 160 | //DBG_OUTPUT_PORT.println("handleFileCreate: " + path); 161 | if(path == "/") 162 | return server.send(500, "text/plain", "BAD PATH"); 163 | if(SPIFFS.exists(path)) 164 | return server.send(500, "text/plain", "FILE EXISTS"); 165 | File file = SPIFFS.open(path, "w"); 166 | if(file) 167 | file.close(); 168 | else 169 | return server.send(500, "text/plain", "CREATE FAILED"); 170 | server.send(200, "text/plain", ""); 171 | path = String(); 172 | } 173 | 174 | void handleFileList() { 175 | String path = "/"; 176 | if(server.hasArg("dir")) 177 | String path = server.arg("dir"); 178 | //DBG_OUTPUT_PORT.println("handleFileList: " + path); 179 | Dir dir = SPIFFS.openDir(path); 180 | path = String(); 181 | 182 | String output = "["; 183 | while(dir.next()){ 184 | File entry = dir.openFile("r"); 185 | if (output != "[") output += ','; 186 | bool isDir = false; 187 | output += "{\"type\":\""; 188 | output += (isDir)?"dir":"file"; 189 | output += "\",\"name\":\""; 190 | output += String(entry.name()).substring(1); 191 | output += "\"}"; 192 | entry.close(); 193 | } 194 | 195 | output += "]"; 196 | server.send(200, "text/json", output); 197 | } 198 | 199 | static void sendCommand(String cmd) 200 | { 201 | Serial.print("\n"); 202 | delay(1); 203 | while(Serial.available()) 204 | Serial.read(); //flush all previous output 205 | Serial.print(cmd); 206 | Serial.print("\n"); 207 | Serial.readStringUntil('\n'); //consume echo 208 | } 209 | 210 | static void handleCommand() { 211 | const int cmdBufSize = 128; 212 | if(!server.hasArg("cmd")) {server.send(500, "text/plain", "BAD ARGS"); return;} 213 | 214 | String cmd = server.arg("cmd").substring(0, cmdBufSize); 215 | int repeat = 0; 216 | char buffer[255]; 217 | size_t len = 0; 218 | String output; 219 | 220 | if (server.hasArg("repeat")) 221 | repeat = server.arg("repeat").toInt(); 222 | 223 | if (!fastUart && fastUartAvailable) 224 | { 225 | sendCommand("fastuart"); 226 | Serial.begin(921600); 227 | fastUart = true; 228 | } 229 | 230 | sendCommand(cmd); 231 | do { 232 | memset(buffer,0,sizeof(buffer)); 233 | len = Serial.readBytes(buffer, sizeof(buffer) - 1); 234 | output += buffer; 235 | 236 | if (repeat) 237 | { 238 | repeat--; 239 | Serial.print("!"); 240 | Serial.readBytes(buffer, 1); //consume "!" 241 | } 242 | } while (len > 0); 243 | server.sendHeader("Access-Control-Allow-Origin","*"); 244 | server.send(200, "text/json", output); 245 | } 246 | 247 | static uint32_t crc32_word(uint32_t Crc, uint32_t Data) 248 | { 249 | int i; 250 | 251 | Crc = Crc ^ Data; 252 | 253 | for(i=0; i<32; i++) 254 | if (Crc & 0x80000000) 255 | Crc = (Crc << 1) ^ 0x04C11DB7; // Polynomial used in STM32 256 | else 257 | Crc = (Crc << 1); 258 | 259 | return(Crc); 260 | } 261 | 262 | static uint32_t crc32(uint32_t* data, uint32_t len, uint32_t crc) 263 | { 264 | for (uint32_t i = 0; i < len; i++) 265 | crc = crc32_word(crc, data[i]); 266 | return crc; 267 | } 268 | 269 | 270 | static void handleUpdate() 271 | { 272 | if(!server.hasArg("step") || !server.hasArg("file")) {server.send(500, "text/plain", "BAD ARGS"); return;} 273 | size_t PAGE_SIZE_BYTES = 1024; 274 | int step = server.arg("step").toInt(); 275 | File file = SPIFFS.open(server.arg("file"), "r"); 276 | int pages = (file.size() + PAGE_SIZE_BYTES - 1) / PAGE_SIZE_BYTES; 277 | String message; 278 | 279 | if (server.hasArg("pagesize")) 280 | { 281 | PAGE_SIZE_BYTES = server.arg("pagesize").toInt(); 282 | } 283 | 284 | if (step == -1) 285 | { 286 | int c; 287 | sendCommand("reset"); 288 | 289 | if (fastUart) 290 | { 291 | Serial.begin(115200); 292 | fastUart = false; 293 | fastUartAvailable = true; //retry after reboot 294 | } 295 | do { 296 | c = Serial.read(); 297 | } while (c != 'S' && c != '2'); 298 | 299 | if (c == '2') //version 2 bootloader 300 | { 301 | Serial.write(0xAA); //Send magic 302 | while (Serial.read() != 'S'); 303 | } 304 | 305 | Serial.write(pages); 306 | while (Serial.read() != 'P'); 307 | message = "reset"; 308 | } 309 | else 310 | { 311 | bool repeat = true; 312 | file.seek(step * PAGE_SIZE_BYTES); 313 | char buffer[PAGE_SIZE_BYTES]; 314 | size_t bytesRead = file.readBytes(buffer, sizeof(buffer)); 315 | 316 | while (bytesRead < PAGE_SIZE_BYTES) 317 | buffer[bytesRead++] = 0xff; 318 | 319 | uint32_t crc = crc32((uint32_t*)buffer, PAGE_SIZE_BYTES / 4, 0xffffffff); 320 | 321 | while (repeat) 322 | { 323 | Serial.write(buffer, sizeof(buffer)); 324 | while (!Serial.available()); 325 | char res = Serial.read(); 326 | 327 | if ('C' == res) { 328 | Serial.write((char*)&crc, sizeof(uint32_t)); 329 | while (!Serial.available()); 330 | res = Serial.read(); 331 | } 332 | 333 | switch (res) { 334 | case 'D': 335 | message = "Update Done"; 336 | repeat = false; 337 | fastUartAvailable = true; 338 | break; 339 | case 'E': 340 | while (Serial.read() != 'T'); 341 | break; 342 | case 'P': 343 | message = "Page write success"; 344 | repeat = false; 345 | break; 346 | default: 347 | case 'T': 348 | break; 349 | } 350 | } 351 | } 352 | server.send(200, "text/json", "{ \"message\": \"" + message + "\", \"pages\": " + pages + " }"); 353 | file.close(); 354 | } 355 | 356 | static void handleWifi() 357 | { 358 | bool updated = true; 359 | if(server.hasArg("apSSID") && server.hasArg("apPW")) 360 | { 361 | WiFi.softAP(server.arg("apSSID").c_str(), server.arg("apPW").c_str()); 362 | } 363 | else if(server.hasArg("staSSID") && server.hasArg("staPW")) 364 | { 365 | WiFi.mode(WIFI_AP_STA); 366 | WiFi.begin(server.arg("staSSID").c_str(), server.arg("staPW").c_str()); 367 | } 368 | else 369 | { 370 | File file = SPIFFS.open("/wifi.html", "r"); 371 | String html = file.readString(); 372 | file.close(); 373 | html.replace("%staSSID%", WiFi.SSID()); 374 | html.replace("%apSSID%", WiFi.softAPSSID()); 375 | html.replace("%staIP%", WiFi.localIP().toString()); 376 | server.send(200, "text/html", html); 377 | updated = false; 378 | } 379 | 380 | if (updated) 381 | { 382 | File file = SPIFFS.open("/wifi-updated.html", "r"); 383 | size_t sent = server.streamFile(file, getContentType("wifi-updated.html")); 384 | file.close(); 385 | } 386 | } 387 | 388 | static void handleBaud() 389 | { 390 | if (fastUart) 391 | server.send(200, "text/html", "fastUart on"); 392 | else 393 | server.send(200, "text/html", "fastUart off"); 394 | } 395 | 396 | void staCheck(){ 397 | sta_tick.detach(); 398 | if(!(uint32_t)WiFi.localIP()){ 399 | WiFi.mode(WIFI_AP); //disable station mode 400 | } 401 | } 402 | 403 | void setup(void){ 404 | Serial.begin(115200); 405 | Serial.setTimeout(100); 406 | SPIFFS.begin(); 407 | 408 | //WIFI INIT 409 | #ifdef WIFI_IS_OFF_AT_BOOT 410 | enableWiFiAtBootTime(); 411 | #endif 412 | WiFi.mode(WIFI_AP_STA); 413 | WiFi.begin(); 414 | sta_tick.attach(10, staCheck); 415 | 416 | MDNS.begin(host); 417 | 418 | updater.setup(&server); 419 | 420 | //SERVER INIT 421 | ArduinoOTA.setHostname(host); 422 | ArduinoOTA.begin(); 423 | //list directory 424 | server.on("/list", HTTP_GET, handleFileList); 425 | //load editor 426 | server.on("/edit", HTTP_GET, [](){ 427 | if(!handleFileRead("/edit.htm")) server.send(404, "text/plain", "FileNotFound"); 428 | }); 429 | //create file 430 | server.on("/edit", HTTP_PUT, handleFileCreate); 431 | //delete file 432 | server.on("/edit", HTTP_DELETE, handleFileDelete); 433 | //first callback is called after the request has ended with all parsed arguments 434 | //second callback handles file uploads at that location 435 | server.on("/edit", HTTP_POST, [](){ server.send(200, "text/plain", ""); }, handleFileUpload); 436 | 437 | server.on("/wifi", handleWifi); 438 | server.on("/cmd", handleCommand); 439 | server.on("/fwupdate", handleUpdate); 440 | server.on("/baud", handleBaud); 441 | server.on("/version", [](){ server.send(200, "text/plain", "1.1.R"); }); 442 | server.on("/swd/begin", []() { 443 | // See if we can communicate. If so, return information about the target. 444 | // This shouldn't reset the target, but it does need to communicate, 445 | // and the debug port itself will be reset. 446 | // 447 | // If all is well, this returns some identifying info about the target. 448 | 449 | uint32_t idcode; 450 | 451 | if (swd.begin() && swd.getIDCODE(idcode)) { 452 | 453 | char output[128]; 454 | snprintf(output, sizeof output, "{\"connected\": true, \"idcode\": \"0x%02x\" }", idcode); 455 | server.send(200, "application/json", String(output)); 456 | 457 | } else { 458 | server.send(200, "application/json", "{\"connected\": false}"); 459 | } 460 | }); 461 | server.on("/swd/uid", []() { 462 | // STM32F103 Reference Manual, Chapter 30.2 Unique device ID register (96 bits) 463 | // http://www.st.com/st-web-ui/static/active/en/resource/technical/document/reference_manual/CD00171190.pdf 464 | 465 | uint32_t REG_U_ID = 0x1FFFF7E8; //96 bits long, read using 3 read operations 466 | 467 | uint16_t off0; 468 | uint16_t off2; 469 | uint32_t off4; 470 | uint32_t off8; 471 | 472 | swd.memLoadHalf(REG_U_ID + 0x0, off0); 473 | swd.memLoadHalf(REG_U_ID + 0x2, off2); 474 | swd.memLoad(REG_U_ID + 0x4, off4); 475 | swd.memLoad(REG_U_ID + 0x8, off8); 476 | 477 | char output[128]; 478 | snprintf(output, sizeof output, "{\"uid\": \"0x%04x-0x%04x-0x%08x-0x%08x\" }", off0, off2, off4, off8); 479 | server.send(200, "application/json", String(output)); 480 | }); 481 | server.on("/swd/halt", []() { 482 | if (swd.begin()) { 483 | char output[128]; 484 | snprintf(output, sizeof output, "{\"halt\": \"%s\"}", swd.debugHalt() ? "true" : "false"); 485 | server.send(200, "application/json", String(output)); 486 | } else { 487 | server.send(200, "text/plain", "SWD Error"); 488 | } 489 | }); 490 | server.on("/swd/run", []() { 491 | if (swd.begin()) { 492 | char output[128]; 493 | snprintf(output, sizeof output, "{\"run\": \"%s\"}", swd.debugRun() ? "true" : "false"); 494 | server.send(200, "application/json", String(output)); 495 | } else { 496 | server.send(200, "text/plain", "SWD Error"); 497 | } 498 | }); 499 | server.on("/swd/reset", []() { 500 | if (swd.begin()) { 501 | bool debugHalt = swd.debugHalt(); 502 | bool debugReset = false; 503 | if (server.hasArg("hard")) { 504 | swd.reset(); 505 | debugReset = true; 506 | } else { 507 | debugReset = swd.debugReset(); 508 | } 509 | char output[128]; 510 | snprintf(output, sizeof output, "{\"halt\": \"%s\", \"reset\": \"%s\"}", debugHalt ? "true" : "false", debugReset ? "true" : "false"); 511 | server.send(200, "application/json", String(output)); 512 | } else { 513 | server.send(200, "text/plain", "SWD Error"); 514 | } 515 | }); 516 | server.on("/swd/zero", []() { 517 | 518 | char output[128]; 519 | 520 | if (swd.begin()) { 521 | 522 | uint32_t addrTotal = addrEnd - addr; 523 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 524 | server.send(200, "text/plain", ""); 525 | 526 | swd.debugHalt(); 527 | swd.debugHaltOnReset(1); 528 | swd.reset(); 529 | swd.unlockFlash(); 530 | 531 | //METHOD #1 532 | swd.flashEraseAll(); 533 | 534 | //METHOD #2 535 | // Before programming internal SRAM, the ARM Cortex-M3 should first be reset and halted. 536 | /* 537 | 1. Write 0xA05F0003 to DHCSR. This will halt the core. 538 | 2. Write 1 to bit VC_CORERESET in DEMCR. This will enable halt-on-reset 539 | 3. Write 0xFA050004 to AIRCR. This will reset the core. 540 | */ 541 | //swd.flashloaderSRAM(); 542 | 543 | uint32_t addrNext = addr; 544 | uint32_t addrIndex = 0; 545 | uint32_t addrBuffer = 0x00000000; //Used by METHOD #2 546 | do { 547 | //Serial.printf("------ %08x -> %08x ------\n", addrNext, addrBuffer); 548 | 549 | snprintf(output, sizeof output, "%08x:", addrNext); 550 | server.sendContent(output); 551 | 552 | uint32_t eraseBuffer[4]; 553 | memset(eraseBuffer, 0xff, sizeof(eraseBuffer)); 554 | 555 | for (int i = 0; i < 4; i++) 556 | { 557 | //METHOD #2 558 | //swd.writeBufferSRAM(addrBuffer, eraseBuffer, 1); 559 | 560 | //METHOD #3 561 | //swd.flashWrite(addrNext, eraseBuffer[i]); 562 | 563 | snprintf(output, sizeof output, " | %02x %02x %02x %02x", (uint8_t)(eraseBuffer[i] >> 0), (uint8_t)(eraseBuffer[i] >> 8), (uint8_t)(eraseBuffer[i] >> 16), (uint8_t)(eraseBuffer[i] >> 24)); 564 | server.sendContent(output); 565 | 566 | addrNext += 4; 567 | addrBuffer += 4; 568 | } 569 | 570 | server.sendContent("\n"); 571 | 572 | addrIndex++; 573 | } while (addrNext <= addrEnd); 574 | 575 | //METHOD #2 576 | //swd.flashloaderRUN(addr, addrBuffer); 577 | 578 | swd.debugHaltOnReset(0); 579 | swd.debugReset(); 580 | 581 | server.sendContent(""); //end stream 582 | 583 | } else { 584 | server.send(200, "text/plain", "SWD Error"); 585 | } 586 | }); 587 | server.on("/swd/hex", []() { 588 | 589 | if (swd.begin()) { 590 | 591 | if (server.hasArg("bootloader")) { 592 | addr = 0x08000000; 593 | addrEnd = 0x08000fff; 594 | } else if (server.hasArg("flash")) { 595 | addr = 0x08001000; 596 | addrEnd = 0x0801ffff; 597 | //addrEnd = 0x080011ff; //Quick Debug 598 | } else if (server.hasArg("ram")) { 599 | addr = 0x20000000; 600 | addrEnd = 0x200003ff; //Note: Read is limited to 0x200003ff but you can write to higher portion of RAM 601 | } 602 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 603 | server.send(200, "text/plain", ""); 604 | 605 | uint32_t addrCount = 256; 606 | uint32_t addrNext = addr; 607 | do { 608 | 609 | //Serial.printf("------ %08x ------\n", addrNext); 610 | 611 | StreamString data; 612 | swd.hexDump(addrNext, addrCount, data); 613 | server.sendContent(data.readString()); 614 | 615 | addrNext += (addrCount * 4); //step = count * 4 bytes in int32 word 616 | } while (addrNext <= addrEnd); 617 | 618 | server.sendContent(""); //end stream 619 | 620 | } else { 621 | server.send(200, "text/plain", "SWD Error"); 622 | } 623 | }); 624 | server.on("/swd/bin", []() { 625 | 626 | if (swd.begin()) { 627 | 628 | String filename = "flash.bin"; 629 | 630 | if (server.hasArg("bootloader")) { 631 | addr = 0x08000000; 632 | addrEnd = 0x08000fff; 633 | filename = "bootloader.bin"; 634 | } else if (server.hasArg("flash")) { 635 | addr = 0x08001000; 636 | addrEnd = 0x0801ffff; 637 | } 638 | 639 | server.sendHeader("Content-Disposition", "attachment; filename = \"" + filename + "\""); 640 | server.setContentLength(addrEnd - addr + 1); //CONTENT_LENGTH_UNKNOWN 641 | server.send(200, "application/octet-stream", ""); 642 | 643 | uint32_t addrNext = addr; 644 | do { 645 | 646 | //Serial.printf("------ %08x ------\n", addrNext); 647 | 648 | //uint8_t* buff; 649 | //swd.binDump(addrNext, buff); 650 | //server.sendContent(String((char *)buff)); 651 | 652 | uint8_t byte; 653 | swd.memLoadByte(addrNext, byte); 654 | server.sendContent(String(byte)); 655 | 656 | addrNext++; 657 | } while (addrNext <= addrEnd); 658 | 659 | server.sendContent(""); //end stream 660 | 661 | } else { 662 | server.send(200, "text/plain", "SWD Error"); 663 | } 664 | }); 665 | server.on("/swd/mem/flash", []() { 666 | 667 | char output[128]; 668 | 669 | if (swd.begin()) { 670 | 671 | if (server.hasArg("file")) { 672 | 673 | if (server.hasArg("bootloader")) { 674 | addr = 0x08000000; 675 | addrEnd = 0x08000fff; 676 | } else if (server.hasArg("flash")) { 677 | addr = 0x08001000; 678 | addrEnd = 0x0801ffff; 679 | } 680 | 681 | String filename = server.arg("file"); 682 | File fs = SPIFFS.open("/" + filename, "r"); 683 | if (fs) 684 | { 685 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 686 | server.send(200, "text/plain", ""); 687 | 688 | swd.debugHalt(); 689 | swd.debugHaltOnReset(1); //reset lock into halt 690 | swd.reset(); 691 | swd.unlockFlash(); 692 | 693 | pinMode(LED_BUILTIN, OUTPUT); 694 | 695 | uint32_t addrNext = addr; 696 | uint32_t addrIndex = addr; 697 | uint32_t addrBuffer = 0x00000000; 698 | 699 | while (addrNext < addrEnd && fs.available()) 700 | { 701 | swd.debugHalt(); 702 | if (addrBuffer == 0x00000000) 703 | { 704 | swd.flashloaderSRAM(); //load flashloader to SRAM @ 0x20000000 705 | } 706 | 707 | digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); 708 | 709 | uint8_t PAGE_SIZE = 6; //webserver max chunks 710 | for (uint8_t p = 0; p < PAGE_SIZE; p++) 711 | { 712 | //Serial.printf("------ %08x ------\n", addrIndex); 713 | if (fs.available() == 0) 714 | break; 715 | 716 | snprintf(output, sizeof output, "%08x:", addrIndex); 717 | server.sendContent(output); 718 | 719 | for (int i = 0; i < 4; i++) 720 | { 721 | if (fs.available() == 0) 722 | break; 723 | 724 | char sramBuffer[4]; 725 | fs.readBytes(sramBuffer, 4); 726 | swd.writeBufferSRAM(addrBuffer, (uint8_t*)sramBuffer, sizeof(sramBuffer)); //append to SRAM after flashloader 727 | 728 | snprintf(output, sizeof output, " | %02x %02x %02x %02x", sramBuffer[0], sramBuffer[1], sramBuffer[2], sramBuffer[3]); 729 | server.sendContent(output); 730 | 731 | addrIndex += 4; 732 | addrBuffer += 4; 733 | } 734 | server.sendContent("\n"); 735 | } 736 | swd.flashloaderRUN(addrNext, addrBuffer); 737 | delay(400); //Must wait for flashloader to finish 738 | 739 | addrBuffer = 0x00000000; 740 | addrNext = addrIndex; 741 | } 742 | 743 | swd.debugHaltOnReset(0); //no reset halt lock 744 | swd.reset(); //hard-reset 745 | 746 | fs.close(); 747 | SPIFFS.remove("/" + filename); 748 | 749 | server.sendContent(""); //end stream 750 | digitalWrite(LED_BUILTIN, HIGH); //OFF 751 | } else { 752 | server.send(200, "text/plain", "File Error"); 753 | } 754 | } else { 755 | server.send(200, "text/plain", ".bin File Required"); 756 | } 757 | } else { 758 | server.send(200, "text/plain", "SWD Error"); 759 | } 760 | }); 761 | //called when the url is not defined here 762 | //use it to load content from SPIFFS 763 | server.onNotFound([](){ 764 | if(!handleFileRead(server.uri())) 765 | { 766 | server.sendHeader("Refresh", "6; url=/update"); 767 | server.send(404, "text/plain", "FileNotFound"); 768 | } 769 | }); 770 | 771 | server.begin(); 772 | server.client().setNoDelay(1); 773 | 774 | MDNS.addService("http", "tcp", 80); 775 | } 776 | 777 | void loop(void){ 778 | server.handleClient(); 779 | ArduinoOTA.handle(); 780 | // note: ArduinoOTA.handle() calls MDNS.update(); 781 | } 782 | -------------------------------------------------------------------------------- /platformio-local-override.ini.example: -------------------------------------------------------------------------------- 1 | [env] 2 | board_build.flash_mode = qout 3 | upload_port = /dev/cu.usbserial-DGAJb113318 4 | upload_speed = 921600 5 | monitor_port = /dev/cu.usbserial-DGAJb113318 6 | monitor_speed = 115200 7 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | [platformio] 2 | description = Web interface for Huebner inverter 3 | default_envs = 4 | release 5 | src_dir = . 6 | data_dir = data 7 | extra_configs = 8 | platformio-local-override.ini 9 | 10 | [common] 11 | monitor_speed = 115200 12 | 13 | [env] 14 | platform = espressif8266 15 | ;platform = https://github.com/platformio/platform-espressif8266.git 16 | framework = arduino 17 | platform_packages = 18 | platformio/tool-esptoolpy 19 | board = modwifi 20 | board_build.filesystem = spiffs 21 | board_build.ldscript = eagle.flash.2m512.ld 22 | board_build.flash_mode = qio 23 | build_src_filter = +<*> -<.git/> -<.svn/> - 24 | 25 | upload_speed = 115200 26 | upload_flags = 27 | --after 28 | no_reset_stub 29 | 30 | [env:release] 31 | build_flags = 32 | ${env.build_flags} 33 | -D RELEASE 34 | build_type = release 35 | 36 | [env:debug] 37 | build_flags = 38 | ${env.build_flags} 39 | -D DEBUG 40 | -DDEBUG_ESP_PORT=Serial 41 | -DDEBUG_ESP_CORE 42 | -DDEBUG_ESP_WIFI 43 | build_type = debug 44 | 45 | -------------------------------------------------------------------------------- /src/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Micah Elizabeth Scott 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/arm_debug.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple ARM debug interface for Arduino, using the SWD (Serial Wire Debug) port. 3 | * 4 | * Copyright (c) 2013 Micah Elizabeth Scott 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | * this software and associated documentation files (the "Software"), to deal in 8 | * the Software without restriction, including without limitation the rights to 9 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | * the Software, and to permit persons to whom the Software is furnished to do so, 11 | * subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include "arm_debug.h" 28 | #include "arm_reg.h" 29 | #include "flashloader/stm32x.h" 30 | //#include "flashloader/stm32f0.h" 31 | 32 | /* 33 | Additional Research: 34 | https://research.kudelskisecurity.com/2019/05/16/swd-arms-alternative-to-jtag 35 | https://research.kudelskisecurity.com/2019/07/31/swd-part-2-the-mem-ap 36 | https://www.silabs.com/documents/public/application-notes/an0062.pdf 37 | https://www.silabs.com/documents/public/example-code/an0062-efm32-programming-guide.zip 38 | https://github.com/m-mcgowan/embedded-swd/blob/master/firmware/swd.h 39 | http://markding.github.io/swd_programing_sram 40 | https://github.com/MarkDing/swd_programing_sram 41 | https://www.cnblogs.com/shangdawei/p/4753040.html 42 | https://www.cnblogs.com/shangdawei/p/3164075.html 43 | */ 44 | 45 | ARMDebug::ARMDebug(unsigned clockPin, unsigned dataPin, LogLevel logLevel) 46 | : clockPin(clockPin), dataPin(dataPin), logLevel(logLevel) 47 | {} 48 | 49 | bool ARMDebug::begin() 50 | { 51 | pinMode(clockPin, OUTPUT); 52 | pinMode(dataPin, INPUT_PULLUP); 53 | 54 | // Invalidate cache 55 | cache.select = 0xFFFFFFFF; 56 | 57 | // Put the bus in a known state, and trigger a JTAG-to-SWD transition. 58 | wireWriteTurnaround(); 59 | wireWrite(0xFFFFFFFF, 32); 60 | wireWrite(0xFFFFFFFF, 32); 61 | wireWrite(0xE79E, 16); 62 | wireWrite(0xFFFFFFFF, 32); 63 | wireWrite(0xFFFFFFFF, 32); 64 | wireWrite(0, 32); 65 | wireWrite(0, 32); 66 | 67 | //The host must read IDCODE register after line request sequence. 68 | uint32_t idcode; 69 | if (!getIDCODE(idcode)) 70 | return false; 71 | log(LOG_NORMAL, "Found ARM processor debug port (IDCODE: %08x)", idcode); 72 | 73 | if (!debugPortPowerup()) 74 | return false; 75 | 76 | if (!initMemPort()) 77 | return false; 78 | 79 | return true; 80 | } 81 | 82 | bool ARMDebug::reset() 83 | { 84 | //Force reset even if BOOT0 is tied to GND 85 | if (apWrite(MEM_TAR, REG_SCB_AIRCR)) 86 | if (apWrite(MEM_DRW, 0x05FA0004)) 87 | return true; 88 | 89 | return false; 90 | } 91 | 92 | bool ARMDebug::getIDCODE(uint32_t &idcode) 93 | { 94 | // Retrieve IDCODE 95 | if (!dpRead(IDCODE, false, idcode)) { 96 | log(LOG_ERROR, "No ARM processor detected. Check power and cables?"); 97 | return false; 98 | } 99 | 100 | return true; 101 | } 102 | 103 | bool ARMDebug::debugPortPowerup() 104 | { 105 | //Power up debug domain 106 | //dpWrite(4, 0x50000000); 107 | 108 | // Initialize CTRL/STAT, request system and debugger power-up 109 | if (!dpWrite(CTRLSTAT, false, CSYSPWRUPREQ | CDBGPWRUPREQ)) 110 | return false; 111 | 112 | // Wait for power-up acknowledgment 113 | uint32_t ctrlstat; 114 | if (!dpReadPoll(CTRLSTAT, ctrlstat, CDBGPWRUPACK | CSYSPWRUPACK, -1)) { 115 | log(LOG_ERROR, "ARMDebug: Debug port failed to power on (CTRLSTAT: %08x)", ctrlstat); 116 | return false; 117 | } 118 | 119 | return true; 120 | } 121 | 122 | bool ARMDebug::debugPortReset() 123 | { 124 | // Note: This is an optional feature. 125 | // It's implemented on the Cortex-M3 but not the M0+, for example. 126 | 127 | const uint32_t powerup = CSYSPWRUPREQ | CDBGPWRUPREQ; 128 | const uint32_t reset_request = powerup | CDBGRSTREQ; 129 | 130 | // Reset the debug access port 131 | if (!dpWrite(CTRLSTAT, false, reset_request)) 132 | return false; 133 | 134 | // Wait for reset acknowledgment 135 | uint32_t ctrlstat; 136 | if (!dpReadPoll(CTRLSTAT, ctrlstat, CDBGRSTACK, -1)) { 137 | log(LOG_ERROR, "ARMDebug: Debug port failed to reset (CTRLSTAT: %08x)", ctrlstat); 138 | return false; 139 | } 140 | 141 | // Clear reset request bit (leave power requests on) 142 | if (!dpWrite(CTRLSTAT, false, powerup)) 143 | return false; 144 | 145 | return true; 146 | } 147 | 148 | bool ARMDebug::initMemPort() 149 | { 150 | // Make sure the default debug access port is an AHB (memory bus) port, like we expect 151 | uint32_t idr; 152 | if (!apRead(MEM_IDR, idr)) 153 | return false; 154 | if ((idr & 0xF) != 1) { 155 | log(LOG_ERROR, "ARMDebug: Default access port is not an AHB-AP as expected! (IDR: %08x)", idr); 156 | return false; 157 | } 158 | 159 | // Invalidate CSW cache 160 | cache.csw = -1; 161 | 162 | return true; 163 | } 164 | 165 | bool ARMDebug::debugHalt() 166 | { 167 | /* 168 | * Enable debug, request a halt, and read back status. 169 | * 170 | * This part is somewhat timing critical, since we're racing against the watchdog 171 | * timer. Avoid memWait() by calling the lower-level interface directly. 172 | * 173 | * Since this is expected to fail a bunch before succeeding, mute errors temporarily. 174 | */ 175 | 176 | unsigned haltRetries = 10000; 177 | LogLevel savedLogLevel; 178 | uint32_t dhcsr; 179 | 180 | // Point at the debug halt control/status register. We disable MEM-AP autoincrement, 181 | // and leave TAR pointed at DHCSR for the entire loop. 182 | if (memWriteCSW(CSW_32BIT) && apWrite(MEM_TAR, REG_SCB_DHCSR)) { 183 | 184 | setLogLevel(LOG_NONE, savedLogLevel); 185 | 186 | while (haltRetries) { 187 | haltRetries--; 188 | 189 | //DHCSR bits C_STOP and C_DEBUGEN (2 + 1) 190 | if (!apWrite(MEM_DRW, 0xA05F0003)) //STEP AND HALT 191 | continue; 192 | if (!apRead(MEM_DRW, dhcsr)) 193 | continue; 194 | 195 | if (dhcsr & (1 << 17)) { //checking whether it actually is halted (0x30000) 196 | // Halted! 197 | break; 198 | } 199 | } 200 | 201 | setLogLevel(savedLogLevel); 202 | } 203 | 204 | if (!haltRetries) { 205 | log(LOG_ERROR, "ARMDebug: Failed to put CPU in debug halt state. (DHCSR: %08x)", dhcsr); 206 | return false; 207 | } 208 | 209 | return true; 210 | } 211 | 212 | bool ARMDebug::debugHaltOnReset(uint8_t enable) 213 | { 214 | /* 215 | //Enable TRC (Trace and Debug blocks) (including DWT) 216 | DEMCR &= ~0x01000000; //disable 217 | DEMCR |= 0x01000000; //enable 218 | */ 219 | 220 | // Vector catch is the mechanism for generating a debug event 221 | // and entering Debug state when a particular exception occurs 222 | 223 | uint32_t VC_CORERESET = 0x0; 224 | if(enable == 1) 225 | VC_CORERESET = 0x1; //Halt after Reset set VC_CORERESET bit to 1 226 | 227 | if (apWrite(MEM_TAR, REG_SCB_DEMCR)) 228 | if (apWrite(MEM_DRW, VC_CORERESET)) 229 | return true; 230 | 231 | return false; 232 | } 233 | 234 | bool ARMDebug::debugReset() 235 | { 236 | if (apWrite(MEM_TAR, REG_SCB_AIRCR)) 237 | if (apWrite(MEM_DRW, 0xFA050004)) 238 | return true; 239 | 240 | return false; 241 | } 242 | 243 | bool ARMDebug::debugStep() 244 | { 245 | if (apWrite(MEM_TAR, REG_SCB_DHCSR)) 246 | if (apWrite(MEM_DRW, 0xA05F0005)) 247 | return true; 248 | return false; 249 | } 250 | 251 | bool ARMDebug::debugRun() 252 | { 253 | const uint32_t DHCSR_DEBUG_KEY = 0xA05F0000; 254 | 255 | if (apWrite(MEM_TAR, REG_SCB_DHCSR)) 256 | if (apWrite(MEM_DRW, DHCSR_DEBUG_KEY)) 257 | return true; 258 | return false; 259 | } 260 | 261 | void ARMDebug::flashloaderSRAM() 262 | { 263 | uint32_t i, size, addr = MEMAP_SRAM_START; 264 | 265 | size = sizeof(flashloader_raw); 266 | for (i = 0; i < size; i++){ 267 | memStoreByte(addr + i, flashloader_raw[i]); 268 | memWait(); 269 | } 270 | } 271 | 272 | void ARMDebug::flashloaderRUN(uint32_t addr, unsigned count) 273 | { 274 | uint32_t buf_addr = MEMAP_SRAM_START + sizeof(flashloader_raw); //flashloader end address 275 | 276 | // Update vector table entry in 0xe000ed08 to SRAM start position 0x20000000 277 | //memStore(REG_SCB_VTOR, MEMAP_SRAM_START); //Debugger to check the core VTOR register 278 | 279 | // https://www.st.com/resource/en/programming_manual/cd00228163-stm32f10xxx-20xxx-21xxx-l1xxxx-cortex-m3-programming-manual-stmicroelectronics.pdf 280 | 281 | // Update R15(PC) with reset vector address. It locates at second word position in firmware. 282 | /* 283 | The Program Counter (PC) is register R15. It contains the current program address. 284 | On reset, the processor loads the PC with the value of the reset vector, which is at address 0x00000004. 285 | */ 286 | //regWrite(15, flashloader_raw[1] & 0xFFFFFFFE); // R15 287 | //Serial.printf("Flasloader Program Counter %08x\n", flashloader_raw[1] & 0xFFFFFFFE); 288 | 289 | // Update R13(SP) with stack address defined in first word in firmware. 290 | /* 291 | The Stack Pointer (SP) is register R13. In Thread mode, bit[1] of the CONTROL register indicates the stack pointer to use: 292 | 0 = Main Stack Pointer (MSP). This is the reset value. 293 | 1 = Process Stack Pointer (PSP). 294 | On reset, the processor loads the MSP with the value from address 0x00000000. 295 | */ 296 | //regWrite(13, flashloader_raw[0]); // R13 297 | //Serial.printf("Flasloader Stack Pointer %08x\n", flashloader_raw[0]); 298 | 299 | // Flashloader Variables 300 | // ---------------------- 301 | // Communication implemented by using fixed-address variables that both parties check (flashloader <-> CPU R0, R1, R2, R3) 302 | log(LOG_NORMAL,"Flasloader Buffer Start %08x\n", buf_addr); 303 | log(LOG_NORMAL, "Flasloader Page Count %d\n", count / 2); 304 | 305 | //OpenOCD stm32x.h 306 | regWrite(0, buf_addr); // Source 307 | regWrite(1, addr); // Target 308 | regWrite(2, count / 2); // Count(16 bits half words) 309 | //regWrite(3, 0); // Result 310 | /* 311 | //OpenOCD stm32f1x.h 312 | uint32_t buf_end = buf_addr + addr; //SRAM workarea without flashloader 313 | regWrite(0, REG_FPEC_FLASH_ACR); // Flash Base 314 | regWrite(1, count / 2); // Count(16 bits half words) 315 | regWrite(2, buf_addr); // Workarea start 316 | regWrite(3, buf_end); // Workarea end 317 | regWrite(4, addr); // Target 318 | */ 319 | /* 320 | //ST-Link stm32f0.h 321 | regWrite(0, buf_addr); // Source 322 | regWrite(1, addr); // Target 323 | regWrite(2, count / 2); // Count(16 bits half words) 324 | if (addr >= REG_FPEC_FLASH_BANK2_START) 325 | regWrite(3, REG_FPEC_FLASH_BANK2_OFS); // Flash register offset 326 | else 327 | regWrite(3, 0); 328 | */ 329 | regWrite(15, MEMAP_SRAM_START); 330 | 331 | apWrite(0x40003000, 0xAAAA); //Reset IWDG F0 332 | //apWrite(0x58004800, 0xAAAA); //Reset IWDG H7 333 | /* 334 | //Debug 335 | uint32_t r0, r1, r2, r3, r15; 336 | regRead(0, r0); 337 | regRead(1, r1); 338 | regRead(2, r2); 339 | regRead(3, r3); 340 | regRead(15, r15); 341 | log(LOG_NORMAL, "R0:%08x R1:%08x R2:%08x R3:%08x R15:%08x \n", r0, r1, r2, r3, r15); 342 | */ 343 | //unlockFlash(); 344 | 345 | //Run code 346 | memStore(REG_SCB_DHCSR, 0xA05F0000); 347 | 348 | flashWait(); 349 | } 350 | 351 | bool ARMDebug::flashFinalize(uint32_t addr) 352 | { 353 | uint32_t r13, r15; 354 | 355 | // Set stack 356 | apRead(addr, r13); 357 | regWrite(15, r13); 358 | // Set PC to the reset routine 359 | apRead(addr + 4, r15); 360 | regWrite(15, r15); 361 | 362 | debugRun(); 363 | 364 | return true; 365 | } 366 | 367 | void ARMDebug::writeBufferSRAM(uint32_t addr, const uint8_t *data, unsigned count) 368 | { 369 | // Write the buffer right after the loader 370 | uint32_t i, buf_addr = MEMAP_SRAM_START + sizeof(flashloader_raw) + addr; //flashloader end address 371 | 372 | for (i = 0; i < count; i++) 373 | { 374 | memStoreByte(buf_addr + i, data[i]); 375 | memWait(); 376 | } 377 | } 378 | 379 | bool ARMDebug::lockFlash() 380 | { 381 | uint32_t flashlock; 382 | apRead(REG_FPEC_FLASH_CR, flashlock); 383 | 384 | if (!(flashlock & FLASH_CR_LOCK)) 385 | { 386 | if (apWrite(REG_FPEC_FLASH_CR, FLASH_CR_LOCK)) 387 | return true; 388 | } 389 | return false; 390 | } 391 | 392 | bool ARMDebug::unlockFlash() 393 | { 394 | //https://ioprog.com/2018/05/23/using-flash-memory-on-the-stm32f103 395 | 396 | //FLASH_CR default value is 0x80000000. Bit 7 is reset by hardware after detecting unlock sequence. So expected value is 0x00 397 | /* 398 | uint32_t flashlock; 399 | apRead(REG_FPEC_FLASH_CR, flashlock); 400 | log(LOG_NORMAL, "Flash Unlock Check %08x\n",flashlock); 401 | */ 402 | 403 | //if (flashlock & FLASH_CR_LOCK) { 404 | /* 405 | An unlocking sequence should be written to the FLASH_KEYR register to open up the FPEC block. 406 | This sequence consists of two write cycles, where two key values (KEY1 and KEY2) are written to the FLASH_KEYR address 407 | */ 408 | memStore(REG_FPEC_FLASH_KEYR, REG_FPEC_KEY_KEY1); 409 | memStore(REG_FPEC_FLASH_KEYR, REG_FPEC_KEY_KEY2); 410 | flashWait(); 411 | //Authorize the programming of the option bytes by writing the same set of KEYS (KEY1 and KEY2) to the FLASH_OPTKEYR register 412 | //memStore(REG_FPEC_FLASH_OPTKEYR, REG_FPEC_KEY_KEY1); 413 | //memStore(REG_FPEC_FLASH_OPTKEYR, REG_FPEC_KEY_KEY2); 414 | //flashWait(); 415 | //} 416 | //Any wrong sequence locks up the FPEC block and FLASH_CR register until the next reset. 417 | 418 | return true; 419 | } 420 | 421 | int ARMDebug::flashWait() 422 | { 423 | //Programming error flags. Cleared by writing this value into FLASH_CR 424 | const uint32_t FLASH_SR_ERROR_FLAGS = 0xF0; 425 | const uint32_t FLASH_SR_BUSY = 1<<16; 426 | const uint32_t FLASH_SR_WRPRTERR = 4; 427 | 428 | uint32_t timeout = 10000; 429 | uint32_t start = millis(); 430 | uint32_t status; 431 | 432 | for (;;) 433 | { 434 | apRead(REG_FPEC_FLASH_SR, status); 435 | //if (status & FLASH_SR_WRPRTERR) 436 | // return -2; 437 | if (!(status & FLASH_SR_BUSY)) 438 | break; 439 | if (millis()-start > timeout) 440 | { 441 | log(LOG_ERROR, "Flash Wait Timeout %08x\n",status); 442 | return -1; 443 | } 444 | } 445 | if (status & FLASH_SR_ERROR_FLAGS) 446 | { 447 | // clear the errors 448 | memStore(REG_FPEC_FLASH_SR, status & ~FLASH_SR_ERROR_FLAGS); 449 | } 450 | 451 | return 0; 452 | } 453 | 454 | bool ARMDebug::flashEraseAll() 455 | { 456 | unlockFlash(); 457 | 458 | uint32_t flashcr; 459 | if (apRead(REG_FPEC_FLASH_CR, flashcr)) 460 | { 461 | log(LOG_NORMAL, "FLASH_CR Before %08x\n",flashcr); 462 | flashcr &= ~FLASH_CR_PG; // ensure PG bit low 463 | flashcr &= ~FLASH_CR_PER; // ensure PER is low 464 | log(LOG_NORMAL, "FLASH_CR After %08x\n",flashcr); 465 | 466 | memStore(REG_FPEC_FLASH_CR, flashcr |= FLASH_CR_MER); // set MER bit 467 | memStore(REG_FPEC_FLASH_CR, flashcr |= FLASH_CR_STRT); // set STRT bit 468 | 469 | flashWait(); 470 | 471 | return true; 472 | } 473 | 474 | return false; 475 | } 476 | 477 | bool ARMDebug::flashErase(uint32_t addr) 478 | { 479 | unlockFlash(); 480 | 481 | uint32_t flashcr; 482 | if (apRead(REG_FPEC_FLASH_CR, flashcr)) 483 | { 484 | log(LOG_NORMAL, "FLASH_CR Before %08x\n",flashcr); 485 | flashcr &= ~FLASH_CR_PG; // ensure PG bit low 486 | flashcr &= ~FLASH_CR_MER; // ensure MER is low 487 | log(LOG_NORMAL, "FLASH_CR After %08x\n",flashcr); 488 | 489 | memStore(REG_FPEC_FLASH_CR, flashcr |= FLASH_CR_PER); // set PER bit 490 | memStore(REG_FPEC_FLASH_AR, addr); 491 | memStore(REG_FPEC_FLASH_CR, flashcr |= FLASH_CR_STRT); // set STRT bit 492 | 493 | flashWait(); 494 | return true; 495 | } 496 | 497 | return false; 498 | } 499 | 500 | bool ARMDebug::flashWrite(uint32_t addr, uint32_t data) 501 | { 502 | flashWait(); 503 | 504 | unlockFlash(); 505 | 506 | uint32_t flashcr; 507 | if (apRead(REG_FPEC_FLASH_CR, flashcr)) 508 | { 509 | apWrite(REG_FPEC_FLASH_CR, flashcr &= ~1); // ensure PER is low 510 | apWrite(REG_FPEC_FLASH_CR, flashcr &= ~2); // ensure MER is low 511 | apWrite(REG_FPEC_FLASH_CR, flashcr |= 0); // set PG bit 512 | 513 | //Perform the data write (half-word) at the desired address 514 | memStoreHalf(addr, data >> 16); 515 | memStoreHalf(addr + 2, data & 0x0000FFFF); 516 | 517 | flashWait(); 518 | }else{ 519 | return false; 520 | } 521 | 522 | return true; 523 | } 524 | 525 | bool ARMDebug::regTransactionHandshake() 526 | { 527 | const uint32_t S_REGRDY = 1<<16; 528 | uint32_t dhcsr; 529 | if (!memLoad(REG_SCB_DHCSR, dhcsr)) { 530 | // Lower-level communications error 531 | return false; 532 | } 533 | return (dhcsr & S_REGRDY) != 0; 534 | } 535 | 536 | bool ARMDebug::regWrite(unsigned num, uint32_t data) 537 | { 538 | const uint32_t write = 0x10000; 539 | num &= 0xFFFF; 540 | return memStore(REG_SCB_DCRDR, data) 541 | && memStore(REG_SCB_DCRSR, num | write) 542 | && regTransactionHandshake(); 543 | } 544 | 545 | bool ARMDebug::regRead(unsigned num, uint32_t& data) 546 | { 547 | num &= 0xFFFF; 548 | return memStore(REG_SCB_DCRSR, num) 549 | && regTransactionHandshake() 550 | && memLoad(REG_SCB_DCRDR, data); 551 | } 552 | 553 | bool ARMDebug::memWriteCSW(uint32_t data) 554 | { 555 | // Write to the MEM-AP CSW. Duplicate writes are ignored, and the cache is updated. 556 | 557 | if (data == cache.csw) 558 | return true; 559 | 560 | if (!apWrite(MEM_CSW, data | CSW_DEFAULTS)) 561 | return false; 562 | 563 | cache.csw = data; 564 | return true; 565 | } 566 | 567 | bool ARMDebug::memWait() 568 | { 569 | // Wait for a transaction to complete & the port to be enabled. 570 | 571 | uint32_t csw; 572 | if (!apReadPoll(MEM_CSW, csw, CSW_TRIN_PROG | CSW_DEVICE_EN, CSW_DEVICE_EN)) { 573 | log(LOG_ERROR, "ARMDebug: Error while waiting for memory port"); 574 | return false; 575 | } 576 | 577 | return true; 578 | } 579 | 580 | bool ARMDebug::memStore(uint32_t addr, uint32_t data) 581 | { 582 | return memStore(addr, &data, 1); 583 | } 584 | 585 | bool ARMDebug::memLoad(uint32_t addr, uint32_t &data) 586 | { 587 | return memLoad(addr, &data, 1); 588 | } 589 | 590 | bool ARMDebug::memStoreAndVerify(uint32_t addr, uint32_t data) 591 | { 592 | return memStoreAndVerify(addr, &data, 1); 593 | } 594 | 595 | bool ARMDebug::memStoreAndVerify(uint32_t addr, const uint32_t *data, unsigned count) 596 | { 597 | if (!memStore(addr, data, count)) 598 | return false; 599 | 600 | if (!memWait()) 601 | return false; 602 | if (!apWrite(MEM_TAR, addr)) 603 | return false; 604 | 605 | while (count) { 606 | uint32_t readback; 607 | 608 | if (!memWait()) 609 | return false; 610 | if (!apRead(MEM_DRW, readback)) 611 | return false; 612 | 613 | log(readback == *data ? LOG_TRACE_MEM : LOG_ERROR, 614 | "MEM Verif [%08x] %08x (expected %08x)", addr, readback, *data); 615 | 616 | if (readback != *data) 617 | return false; 618 | 619 | data++; 620 | addr++; 621 | count--; 622 | } 623 | 624 | return true; 625 | } 626 | 627 | bool ARMDebug::memStore(uint32_t addr, const uint32_t *data, unsigned count) 628 | { 629 | if (!memWait()) 630 | return false; 631 | if (!memWriteCSW(CSW_32BIT | CSW_ADDRINC_SINGLE)) 632 | return false; 633 | if (!apWrite(MEM_TAR, addr)) 634 | return false; 635 | 636 | while (count) { 637 | log(LOG_TRACE_MEM, "MEM Store [%08x] %08x", addr, *data); 638 | 639 | if (!memWait()) 640 | return false; 641 | if (!apWrite(MEM_DRW, *data)) 642 | return false; 643 | 644 | data++; 645 | addr++; 646 | count--; 647 | } 648 | 649 | return true; 650 | } 651 | 652 | bool ARMDebug::memLoad(uint32_t addr, uint32_t *data, unsigned count) 653 | { 654 | if (!memWait()) 655 | return false; 656 | if (!memWriteCSW(CSW_32BIT | CSW_ADDRINC_SINGLE)) 657 | return false; 658 | if (!apWrite(MEM_TAR, addr)) 659 | return false; 660 | 661 | while (count) { 662 | if (!memWait()) 663 | return false; 664 | if (!apRead(MEM_DRW, *data)) 665 | return false; 666 | 667 | log(LOG_TRACE_MEM, "MEM Load [%08x] %08x", addr, *data); 668 | 669 | data++; 670 | addr++; 671 | count--; 672 | } 673 | 674 | return true; 675 | } 676 | 677 | bool ARMDebug::memStoreByte(uint32_t addr, uint8_t data) 678 | { 679 | if (!memWait()) 680 | return false; 681 | if (!memWriteCSW(CSW_8BIT)) 682 | return false; 683 | if (!apWrite(MEM_TAR, addr)) 684 | return false; 685 | 686 | log(LOG_TRACE_MEM, "MEM Store [%08x] %02x", addr, data); 687 | 688 | // Replicate across lanes 689 | uint32_t word = data | (data << 8) | (data << 16) | (data << 24); 690 | 691 | return apWrite(MEM_DRW, word); 692 | } 693 | 694 | bool ARMDebug::memLoadByte(uint32_t addr, uint8_t &data) 695 | { 696 | uint32_t word; 697 | if (!memWait()) 698 | return false; 699 | if (!memWriteCSW(CSW_8BIT)) 700 | return false; 701 | if (!apWrite(MEM_TAR, addr)) 702 | return false; 703 | if (!apRead(MEM_DRW, word)) 704 | return false; 705 | 706 | // Select the proper lane 707 | data = word >> ((addr & 3) << 3); 708 | 709 | log(LOG_TRACE_MEM, "MEM Load [%08x] %02x", addr, data); 710 | return true; 711 | } 712 | 713 | bool ARMDebug::memStoreHalf(uint32_t addr, uint16_t data) 714 | { 715 | if (!memWait()) 716 | return false; 717 | if (!memWriteCSW(CSW_16BIT)) 718 | return false; 719 | if (!apWrite(MEM_TAR, addr)) 720 | return false; 721 | 722 | log(LOG_TRACE_MEM, "MEM Store [%08x] %04x", addr, data); 723 | 724 | // Replicate across lanes 725 | uint32_t word = data | (data << 16); 726 | 727 | return apWrite(MEM_DRW, word); 728 | } 729 | 730 | bool ARMDebug::memLoadHalf(uint32_t addr, uint16_t &data) 731 | { 732 | uint32_t word; 733 | if (!memWait()) 734 | return false; 735 | if (!memWriteCSW(CSW_16BIT)) 736 | return false; 737 | if (!apWrite(MEM_TAR, addr)) 738 | return false; 739 | if (!apRead(MEM_DRW, word)) 740 | return false; 741 | 742 | // Select the proper lane 743 | data = word >> ((addr & 2) << 3); 744 | 745 | log(LOG_TRACE_MEM, "MEM Load [%08x] %04x", addr, data); 746 | return true; 747 | } 748 | 749 | bool ARMDebug::apWrite(unsigned addr, uint32_t data) 750 | { 751 | log(LOG_TRACE_AP, "AP Write [%x] %08x", addr, data); 752 | return dpSelect(addr) && dpWrite(addr, true, data); 753 | } 754 | 755 | bool ARMDebug::apRead(unsigned addr, uint32_t &data) 756 | { 757 | /* 758 | * This is really hard to find in the docs, but AP reads are delayed by one transaction. 759 | * So, the very first AP read will return undefined data, the next AP read returns the 760 | * data for the previous address, and so on. 761 | * 762 | * See: 763 | * http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0314h/ch02s04s03.html 764 | * 765 | * Solutions include: 766 | * - Tracking DP reads and their responses asynchronously. Hard but efficient. Not for us. 767 | * - Dummy read, to flush the read through. 768 | * - Better yet, we can do this explicitly with the RDBUFF register in ADI5v1. 769 | */ 770 | 771 | unsigned dummyAddr = (addr & kSelectMask) | MEM_IDR; // No side effects, same AP and bank 772 | uint32_t dummyData; 773 | 774 | bool result = dpSelect(addr) && // Select AP and register bank 775 | dpRead(addr, true, dummyData) && // Initiate read, returns dummy data 776 | dpRead(RDBUFF, false, data); // Explicitly request read results 777 | 778 | if (result) { 779 | log(LOG_TRACE_AP, "AP Read [%x] %08x", addr, data); 780 | } 781 | 782 | return result; 783 | } 784 | 785 | bool ARMDebug::dpReadPoll(unsigned addr, uint32_t &data, uint32_t mask, uint32_t expected, unsigned retries) 786 | { 787 | expected &= mask; 788 | do { 789 | if (!dpRead(addr, false, data)) 790 | return false; 791 | if ((data & mask) == expected) 792 | return true; 793 | yield(); 794 | } while (retries--); 795 | 796 | log(LOG_ERROR, "ARMDebug: Timed out while polling DP ([%08x] & %08x == %08x). Current value: %08x", 797 | addr, mask, expected, data); 798 | return false; 799 | } 800 | 801 | bool ARMDebug::apReadPoll(unsigned addr, uint32_t &data, uint32_t mask, uint32_t expected, unsigned retries) 802 | { 803 | expected &= mask; 804 | do { 805 | if (!apRead(addr, data)) 806 | return false; 807 | if ((data & mask) == expected) 808 | return true; 809 | yield(); 810 | } while (retries--); 811 | 812 | log(LOG_ERROR, "ARMDebug: Timed out while polling AP ([%08x] & %08x == %08x). Current value: %08x", 813 | addr, mask, expected, data); 814 | return false; 815 | } 816 | 817 | bool ARMDebug::memPoll(unsigned addr, uint32_t &data, uint32_t mask, uint32_t expected, unsigned retries) 818 | { 819 | expected &= mask; 820 | do { 821 | if (!memLoad(addr, data)) 822 | return false; 823 | if ((data & mask) == expected) 824 | return true; 825 | yield(); 826 | } while (retries--); 827 | 828 | log(LOG_ERROR, "ARMDebug: Timed out while polling MEM ([%08x] & %08x == %08x). Current value: %08x", 829 | addr, mask, expected, data); 830 | return false; 831 | } 832 | 833 | bool ARMDebug::memPollByte(unsigned addr, uint8_t &data, uint8_t mask, uint8_t expected, unsigned retries) 834 | { 835 | expected &= mask; 836 | do { 837 | if (!memLoadByte(addr, data)) 838 | return false; 839 | if ((data & mask) == expected) 840 | return true; 841 | yield(); 842 | } while (retries--); 843 | 844 | log(LOG_ERROR, "ARMDebug: Timed out while polling MEM ([%08x] & %02x == %02x). Current value: %02x", 845 | addr, mask, expected, data); 846 | return false; 847 | } 848 | 849 | bool ARMDebug::dpSelect(unsigned addr) 850 | { 851 | /* 852 | * Select a new access port and/or a bank. Uses only the high byte and 853 | * second nybble of 'addr'. This is cached, so redundant dpSelect()s have no effect. 854 | */ 855 | 856 | uint32_t select = addr & kSelectMask; 857 | 858 | if (select != cache.select) { 859 | if (!dpWrite(SELECT, false, select)) 860 | return false; 861 | cache.select = select; 862 | } 863 | return true; 864 | } 865 | 866 | bool ARMDebug::dpWrite(unsigned addr, bool APnDP, uint32_t data) 867 | { 868 | unsigned retries = 10; 869 | unsigned ack; 870 | log(LOG_TRACE_DP, "DP Write [%x:%x] %08x", addr, APnDP, data); 871 | 872 | do { 873 | wireWrite(packHeader(addr, APnDP, false), 8); 874 | wireReadTurnaround(); 875 | ack = wireRead(3); 876 | wireWriteTurnaround(); 877 | 878 | switch (ack) { 879 | case 1: // Success 880 | wireWrite(data, 32); 881 | wireWrite(evenParity(data), 1); 882 | wireWriteIdle(); 883 | return true; 884 | 885 | case 2: // WAIT 886 | wireWriteIdle(); 887 | log(LOG_TRACE_DP, "DP WAIT response, %d retries left", retries); 888 | retries--; 889 | break; 890 | 891 | case 4: // FAULT 892 | wireWriteIdle(); 893 | log(LOG_ERROR, "FAULT response during write (addr=%x APnDP=%d data=%08x)", 894 | addr, APnDP, data); 895 | if (!handleFault()) 896 | log(LOG_ERROR, "Error during fault recovery!"); 897 | return false; 898 | 899 | default: 900 | wireWriteIdle(); 901 | log(LOG_ERROR, "PROTOCOL ERROR response during write (ack=%x addr=%x APnDP=%d data=%08x)", 902 | ack, addr, APnDP, data); 903 | return false; 904 | } 905 | } while (retries--); 906 | 907 | log(LOG_ERROR, "WAIT timeout during write (addr=%x APnDP=%d data=%08x)", 908 | addr, APnDP, data); 909 | return false; 910 | } 911 | 912 | bool ARMDebug::dpRead(unsigned addr, bool APnDP, uint32_t &data) 913 | { 914 | unsigned retries = 10; 915 | unsigned ack; 916 | 917 | do { 918 | wireWrite(packHeader(addr, APnDP, true), 8); 919 | wireReadTurnaround(); 920 | ack = wireRead(3); 921 | 922 | switch (ack) { 923 | case 1: // Success 924 | data = wireRead(32); 925 | if (wireRead(1) != evenParity(data)) { 926 | wireWriteTurnaround(); 927 | wireWriteIdle(); 928 | log(LOG_ERROR, "PARITY ERROR during read (addr=%x APnDP=%d data=%08x)", 929 | addr, APnDP, data); 930 | return false; 931 | } 932 | wireWriteTurnaround(); 933 | wireWriteIdle(); 934 | log(LOG_TRACE_DP, "DP Read [%x:%x] %08x", addr, APnDP, data); 935 | return true; 936 | 937 | case 2: // WAIT 938 | wireWriteTurnaround(); 939 | wireWriteIdle(); 940 | log(LOG_TRACE_DP, "DP WAIT response, %d retries left", retries); 941 | retries--; 942 | break; 943 | 944 | case 4: // FAULT 945 | wireWriteTurnaround(); 946 | wireWriteIdle(); 947 | log(LOG_ERROR, "FAULT response during read (addr=%x APnDP=%d)", addr, APnDP); 948 | if (!handleFault()) 949 | log(LOG_ERROR, "Error during fault recovery!"); 950 | return false; 951 | 952 | default: 953 | wireWriteTurnaround(); 954 | wireWriteIdle(); 955 | log(LOG_ERROR, "PROTOCOL ERROR response during read (ack=%x addr=%x APnDP=%d)", ack, addr, APnDP); 956 | return false; 957 | } 958 | } while (retries--); 959 | 960 | log(LOG_ERROR, "WAIT timeout during read (addr=%x APnDP=%d)", addr, APnDP); 961 | return false; 962 | } 963 | 964 | bool ARMDebug::handleFault() 965 | { 966 | // Read CTRLSTAT to see what the fault was, and set appropriate ABORT bits 967 | 968 | uint32_t ctrlstat; 969 | uint32_t abortbits = 0; 970 | bool dumpRegs = false; 971 | 972 | if (!dpRead(CTRLSTAT, false, ctrlstat)) 973 | return false; 974 | 975 | if (ctrlstat & (1 << 7)) { 976 | log(LOG_ERROR, "FAULT: Detected WDATAERR"); 977 | abortbits |= 1 << 3; 978 | } 979 | if (ctrlstat & (1 << 4)) { 980 | log(LOG_ERROR, "FAULT: Detected STICKCMP"); 981 | abortbits |= 1 << 1; 982 | } 983 | if (ctrlstat & (1 << 1)) { 984 | log(LOG_ERROR, "FAULT: Detected STICKORUN"); 985 | abortbits |= 1 << 4; 986 | } 987 | if (ctrlstat & (1 << 5)) { 988 | log(LOG_ERROR, "FAULT: Detected STICKYERR"); 989 | abortbits |= 1 << 2; 990 | dumpRegs = true; 991 | } 992 | 993 | if (abortbits && !dpWrite(ABORT, false, abortbits)) 994 | return false; 995 | 996 | if (dumpRegs && !dumpMemPortRegisters()) { 997 | log(LOG_ERROR, "Failed to dump memory port registers for diagnostics."); 998 | return false; 999 | } 1000 | 1001 | return true; 1002 | } 1003 | 1004 | bool ARMDebug::dumpMemPortRegisters() 1005 | { 1006 | uint32_t reg; 1007 | 1008 | if (!apRead(MEM_IDR, reg)) return false; 1009 | log(LOG_ERROR, " IDR = %08x", reg); 1010 | 1011 | if (!apRead(MEM_CSW, reg)) return false; 1012 | log(LOG_ERROR, " CSW = %08x", reg); 1013 | 1014 | if (!apRead(MEM_TAR, reg)) return false; 1015 | log(LOG_ERROR, " TAR = %08x", reg); 1016 | 1017 | return true; 1018 | } 1019 | 1020 | uint8_t ARMDebug::packHeader(unsigned addr, bool APnDP, bool RnW) 1021 | { 1022 | bool a2 = (addr >> 2) & 1; 1023 | bool a3 = (addr >> 3) & 1; 1024 | bool parity = APnDP ^ RnW ^ a2 ^ a3; 1025 | return (1 << 0) | // Start 1026 | (APnDP << 1) | 1027 | (RnW << 2) | 1028 | ((addr & 0xC) << 1) | 1029 | (parity << 5) | 1030 | (1 << 7) ; // Park 1031 | } 1032 | 1033 | bool ARMDebug::evenParity(uint32_t word) 1034 | { 1035 | word = (word & 0xFFFF) ^ (word >> 16); 1036 | word = (word & 0xFF) ^ (word >> 8); 1037 | word = (word & 0xF) ^ (word >> 4); 1038 | word = (word & 0x3) ^ (word >> 2); 1039 | word = (word & 0x1) ^ (word >> 1); 1040 | return word; 1041 | } 1042 | 1043 | void ARMDebug::wireWrite(uint32_t data, unsigned nBits) 1044 | { 1045 | log(LOG_TRACE_SWD, "SWD Write %08x (%d)", data, nBits); 1046 | 1047 | while (nBits--) { 1048 | digitalWrite(dataPin, data & 1); 1049 | digitalWrite(clockPin, LOW); 1050 | data >>= 1; 1051 | digitalWrite(clockPin, HIGH); 1052 | } 1053 | } 1054 | 1055 | void ARMDebug::wireWriteIdle() 1056 | { 1057 | // Minimum 8 clock cycles. 1058 | wireWrite(0, 32); 1059 | } 1060 | 1061 | uint32_t ARMDebug::wireRead(unsigned nBits) 1062 | { 1063 | uint32_t result = 0; 1064 | uint32_t mask = 1; 1065 | unsigned count = nBits; 1066 | 1067 | while (count--) { 1068 | if (digitalRead(dataPin)) { 1069 | result |= mask; 1070 | } 1071 | digitalWrite(clockPin, LOW); 1072 | mask <<= 1; 1073 | digitalWrite(clockPin, HIGH); 1074 | } 1075 | 1076 | log(LOG_TRACE_SWD, "SWD Read %08x (%d)", result, nBits); 1077 | return result; 1078 | } 1079 | 1080 | void ARMDebug::wireWriteTurnaround() 1081 | { 1082 | log(LOG_TRACE_SWD, "SWD Write trn"); 1083 | 1084 | digitalWrite(dataPin, HIGH); 1085 | pinMode(dataPin, INPUT_PULLUP); 1086 | digitalWrite(clockPin, LOW); 1087 | digitalWrite(clockPin, HIGH); 1088 | pinMode(dataPin, OUTPUT); 1089 | } 1090 | 1091 | void ARMDebug::wireReadTurnaround() 1092 | { 1093 | log(LOG_TRACE_SWD, "SWD Read trn"); 1094 | 1095 | digitalWrite(dataPin, HIGH); 1096 | pinMode(dataPin, INPUT_PULLUP); 1097 | digitalWrite(clockPin, LOW); 1098 | digitalWrite(clockPin, HIGH); 1099 | } 1100 | 1101 | void ARMDebug::log(int level, const char *fmt, ...) 1102 | { 1103 | if (level <= logLevel && Serial) { 1104 | va_list ap; 1105 | char buffer[256]; 1106 | 1107 | va_start(ap, fmt); 1108 | int ret = vsnprintf(buffer, sizeof buffer, fmt, ap); 1109 | va_end(ap); 1110 | 1111 | Serial.println(buffer); 1112 | } 1113 | } 1114 | 1115 | void ARMDebug::binDump(uint32_t addr, uint8_t* &buffer) 1116 | { 1117 | // Hex dump target memory to buffer 1118 | 1119 | va_list ap; 1120 | LogLevel oldLogLevel; 1121 | setLogLevel(LOG_NONE, oldLogLevel); //force log level none 1122 | 1123 | uint8_t byte; 1124 | memLoadByte(addr, byte); 1125 | 1126 | *buffer = byte; 1127 | 1128 | setLogLevel(oldLogLevel); //restore previous log level 1129 | } 1130 | 1131 | void ARMDebug::hexDump(uint32_t addr, unsigned count, StreamString &data) 1132 | { 1133 | // Hex dump target memory to StreamString 1134 | 1135 | va_list ap; 1136 | char buffer[32]; 1137 | LogLevel oldLogLevel; 1138 | setLogLevel(LOG_NONE, oldLogLevel); //force log level none 1139 | 1140 | while (count) { 1141 | snprintf(buffer, sizeof buffer, "%08x:", addr); 1142 | data.print(buffer); 1143 | 1144 | for (unsigned x = 0; count && x < 4; x++) { 1145 | uint32_t word; 1146 | if (memLoad(addr, word)) { 1147 | 1148 | /* 1149 | word = ((word << 8) & 0xFF00FF00) | ((word >> 8) & 0xFF00FF); 1150 | word = (word << 16) | ((word >> 16) & 0xFFFF); 1151 | snprintf(buffer, sizeof buffer, " %08x", word); 1152 | */ 1153 | 1154 | uint8_t bytes[3]; 1155 | bytes[0] = (word >> 0) & 0xFF; 1156 | bytes[1] = (word >> 8) & 0xFF; 1157 | bytes[2] = (word >> 16) & 0xFF; 1158 | bytes[3] = (word >> 24) & 0xFF; 1159 | //snprintf(buffer, sizeof buffer, " | %02x %02x %02x %02x", bytes[3], bytes[2], bytes[1], bytes[0]); // big-endian 1160 | snprintf(buffer, sizeof buffer, " | %02x %02x %02x %02x", bytes[0], bytes[1], bytes[2], bytes[3]); // little-endian (reversed) 1161 | 1162 | data.print(buffer); 1163 | } else { 1164 | data.print(" (error )"); 1165 | } 1166 | 1167 | count--; 1168 | addr += 4; //int32 word = 4 individual bytes 1169 | } 1170 | 1171 | data.println(); 1172 | } 1173 | setLogLevel(oldLogLevel); //restore previous log level 1174 | } 1175 | 1176 | void ARMDebug::setLogLevel(LogLevel newLevel) 1177 | { 1178 | logLevel = newLevel; 1179 | } 1180 | 1181 | void ARMDebug::setLogLevel(LogLevel newLevel, LogLevel &prevLevel) 1182 | { 1183 | prevLevel = logLevel; 1184 | logLevel = newLevel; 1185 | } 1186 | -------------------------------------------------------------------------------- /src/arm_debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Simple ARM debug interface for Arduino, using the SWD (Serial Wire Debug) port. 3 | * 4 | * Copyright (c) 2013 Micah Elizabeth Scott 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | * this software and associated documentation files (the "Software"), to deal in 8 | * the Software without restriction, including without limitation the rights to 9 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | * the Software, and to permit persons to whom the Software is furnished to do so, 11 | * subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | #pragma once 25 | #include 26 | #include 27 | 28 | class ARMDebug 29 | { 30 | public: 31 | enum LogLevel { 32 | LOG_NONE = 0, 33 | LOG_ERROR, 34 | LOG_NORMAL, 35 | LOG_TRACE_MEM, 36 | LOG_TRACE_AP, 37 | LOG_TRACE_DP, 38 | LOG_TRACE_SWD, 39 | LOG_MAX 40 | }; 41 | 42 | ARMDebug(unsigned clockPin, unsigned dataPin, LogLevel logLevel = LOG_NORMAL); 43 | 44 | //////////////// Higher level API 45 | 46 | /** 47 | * Reinitialize the debug interface, and identify the connected chip. 48 | * This resets the target chip, putting it in SWD mode and logging its 49 | * identity. 50 | * 51 | * Returns true on success, logs and returns false on error. 52 | */ 53 | bool begin(); 54 | 55 | // Hard reset 56 | bool reset(); 57 | 58 | // Turn on debugging and enter halt state 59 | bool debugHalt(); 60 | 61 | // Enable halt-on-reset 62 | bool debugHaltOnReset(uint8_t enable); 63 | 64 | // Reset the core 65 | bool debugReset(); 66 | 67 | // Step command 68 | bool debugStep(); 69 | 70 | // CPU continue its execution 71 | bool debugRun(); 72 | 73 | // Progaramming SRAM with flashloader 74 | void flashloaderSRAM(); 75 | bool flashFinalize(uint32_t addr); 76 | void flashloaderRUN(uint32_t addr, unsigned count); 77 | void writeBufferSRAM(uint32_t addr, const uint8_t *data, unsigned count); 78 | 79 | // Locking the Flash memory 80 | bool lockFlash(); 81 | 82 | // Unlocking the Flash memory 83 | bool unlockFlash(); 84 | 85 | // Wait for FPEC_FLASH_CR to change 86 | int flashWait(); 87 | 88 | // Flash Mass Erase 89 | bool flashEraseAll(); 90 | 91 | // Erase Flash by Sector (Page) 92 | bool flashErase(uint32_t addr); 93 | 94 | // Writes to flash (and erase operations) 95 | bool flashWrite(uint32_t addr, uint32_t data); 96 | 97 | // CPU register operations, when halted (via DCRSR) 98 | bool regWrite(unsigned num, uint32_t data); 99 | bool regRead(unsigned num, uint32_t &data); 100 | 101 | // Memory operations (AHB bus) 102 | bool memStore(uint32_t addr, uint32_t data); 103 | bool memStore(uint32_t addr, const uint32_t *data, unsigned count); 104 | bool memLoad(uint32_t addr, uint32_t &data); 105 | bool memLoad(uint32_t addr, uint32_t *data, unsigned count); 106 | 107 | // Write with verify 108 | bool memStoreAndVerify(uint32_t addr, uint32_t data); 109 | bool memStoreAndVerify(uint32_t addr, const uint32_t *data, unsigned count); 110 | 111 | // Byte load/store operations (AHB bus) 112 | bool memStoreByte(uint32_t addr, uint8_t data); 113 | bool memLoadByte(uint32_t addr, uint8_t &data); 114 | 115 | // Halfword (16-bit) load/store operations (AHB bus) 116 | bool memStoreHalf(uint32_t addr, uint16_t data); 117 | bool memLoadHalf(uint32_t addr, uint16_t &data); 118 | 119 | // Poll for an expected value 120 | bool memPoll(unsigned addr, uint32_t &data, uint32_t mask, uint32_t expected, unsigned retries = DEFAULT_RETRIES); 121 | bool memPollByte(unsigned addr, uint8_t &data, uint8_t mask, uint8_t expected, unsigned retries = DEFAULT_RETRIES); 122 | 123 | // Write to the log, printf-style 124 | void log(int level, const char *fmt, ...); 125 | 126 | // Hex dump target memory to buffer 127 | void binDump(uint32_t addr, uint8_t* &buffer); 128 | 129 | // Hex dump target memory to StreamString 130 | void hexDump(uint32_t addr, unsigned count, StreamString &data); 131 | 132 | // Change log levels, optionally returning the old level so it can be restored. 133 | void setLogLevel(LogLevel newLevel); 134 | void setLogLevel(LogLevel newLevel, LogLevel &prevLevel); 135 | 136 | //////////////// Lower level API 137 | 138 | // Low-level wire interface (LSB-first) 139 | void wireWrite(uint32_t data, unsigned nBits); 140 | uint32_t wireRead(unsigned nBits); 141 | void wireWriteTurnaround(); 142 | void wireReadTurnaround(); 143 | void wireWriteIdle(); 144 | 145 | // Error diagnostics and recovert 146 | bool handleFault(); 147 | bool dumpMemPortRegisters(); 148 | 149 | // Packet assembly tools 150 | uint8_t packHeader(unsigned addr, bool APnDP, bool RnW); 151 | bool evenParity(uint32_t word); 152 | 153 | // Debug core register handshaking 154 | bool regTransactionHandshake(); 155 | 156 | // Debug port layer 157 | bool dpWrite(unsigned addr, bool APnDP, uint32_t data); 158 | bool dpRead(unsigned addr, bool APnDP, uint32_t &data); 159 | bool dpSelect(unsigned addr); 160 | 161 | // Access port layer 162 | bool apWrite(unsigned addr, uint32_t data); 163 | bool apRead(unsigned addr, uint32_t &data); 164 | 165 | // Internal MEM-AP functions 166 | bool memWait(); 167 | bool memWriteCSW(uint32_t data); 168 | 169 | // Poll for an expected value 170 | bool dpReadPoll(unsigned addr, uint32_t &data, uint32_t mask, uint32_t expected, unsigned retries = DEFAULT_RETRIES); 171 | bool apReadPoll(unsigned addr, uint32_t &data, uint32_t mask, uint32_t expected, unsigned retries = DEFAULT_RETRIES); 172 | 173 | // Individual initialization steps (already included in begin) 174 | bool getIDCODE(uint32_t &idcode); 175 | bool debugPortPowerup(); 176 | bool debugPortReset(); 177 | bool initMemPort(); 178 | 179 | // Debug port registers 180 | enum DebugPortReg { 181 | ABORT = 0x0, 182 | IDCODE = 0x0, 183 | CTRLSTAT = 0x4, 184 | SELECT = 0x8, 185 | RDBUFF = 0xC 186 | }; 187 | 188 | // DCB_DHCSR bit and field definitions 189 | enum DCB_DHCSRBit { 190 | DBGKEY = (0xA05F << 16), 191 | C_DEBUGEN = (1 << 0), 192 | C_HALT = (1 << 1), 193 | C_STEP = (1 << 2), 194 | C_MASKINTS = (1 << 3), 195 | S_REGRDY = (1 << 16), 196 | S_HALT = (1 << 17), 197 | S_SLEEP = (1 << 18), 198 | S_LOCKUP = (1 << 19), 199 | S_RETIRE_ST = (1 << 24), 200 | S_RESET_ST = (1 << 25) 201 | }; 202 | 203 | // CTRL/STAT bits 204 | enum CtrlStatBit { 205 | CSYSPWRUPACK = 1 << 31, 206 | CSYSPWRUPREQ = 1 << 30, 207 | CDBGPWRUPACK = 1 << 29, 208 | CDBGPWRUPREQ = 1 << 28, 209 | CDBGRSTACK = 1 << 27, 210 | CDBGRSTREQ = 1 << 26 211 | }; 212 | 213 | // Memory Access Port registers 214 | enum MemPortReg { 215 | MEM_CSW = 0x00, //Control and Status Word Register 216 | MEM_TAR = 0x04, //Transfer Address Register 217 | MEM_DRW = 0x0C, //Data Read/Write Register 218 | MEM_IDR = 0xFC, //Identification Register 219 | }; 220 | 221 | // MEM-AP CSW bits 222 | enum MemCSWBit { 223 | CSW_8BIT = 0, 224 | CSW_16BIT = 1, 225 | CSW_32BIT = 2, 226 | CSW_ADDRINC_OFF = 0, 227 | CSW_ADDRINC_SINGLE = 1 << 4, 228 | CSW_ADDRINC_PACKED = 2 << 4, 229 | CSW_DEVICE_EN = 1 << 6, 230 | CSW_TRIN_PROG = 1 << 7, 231 | CSW_SPIDEN = 1 << 23, 232 | CSW_HPROT = 1 << 25, 233 | CSW_MASTER_DEBUG = 1 << 29, 234 | CSW_SPROT = 1 << 30, 235 | CSW_DBGSWENABLE = 1 << 31 236 | }; 237 | 238 | // FPEC Flash CR bits 239 | enum FlashCRBit { 240 | FLASH_CR_PG = (1 << 0), //Programming 241 | FLASH_CR_PER = (1 << 1), //Page erase 242 | FLASH_CR_MER = (1 << 2), //Mass erase 243 | FLASH_CR_STRT = (1 << 6), //Start 244 | FLASH_CR_OPTWRE = (1 << 9), //Option bytes write enable 245 | FLASH_CR_PSIZE_8 = (0 << 8), 246 | FLASH_CR_PSIZE_16 = (1 << 8), 247 | FLASH_CR_PSIZE_32 = (2 << 8), 248 | FLASH_CR_PSIZE_64 = (3 << 8), 249 | FLASH_CR_LOCK = (1 << 31) //Lock 0x0080 250 | }; 251 | 252 | inline uint32_t FLASH_CR_SNB(uint8_t sector) { 253 | return sector << 3; 254 | } 255 | 256 | static const unsigned CSW_DEFAULTS = CSW_DBGSWENABLE | CSW_MASTER_DEBUG | CSW_HPROT; 257 | static const unsigned DEFAULT_RETRIES = 50; 258 | 259 | private: 260 | uint8_t clockPin, dataPin, fastPins; 261 | LogLevel logLevel; 262 | 263 | // Cached versions of ARM debug registers 264 | struct { 265 | uint32_t select; 266 | uint32_t csw; 267 | } cache; 268 | 269 | // Relevant bits in cache.select 270 | const uint32_t kSelectMask = 0xFF0000F0; 271 | }; 272 | -------------------------------------------------------------------------------- /src/arm_reg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Register definitions for remote debugging on ARMv6-M chips. 3 | */ 4 | 5 | #pragma once 6 | 7 | #define REG_FLASH_SIZE 0x1FFFF7E0 //STM32 F1 8 | 9 | // Cortex M3 Memory Access Port 10 | #define MEMAP_BANK_0 0x00000000 // BANK 0 => CSW, TAR, Reserved, DRW 11 | #define MEMAP_BANK_1 0x00000001 // BANK 1 => BD0, BD1, BD2, BD3 12 | #define MEMAP_SRAM_START 0x20000000 // RAM start 13 | 14 | // Flash program and erase controller (FPEC), PM0075 manual STM32F10xxx Flash memory 15 | #define REG_FPEC_FLASH_BANK2_START 0x08080000 //VL/F1_XL F0 flash with dual bank 16 | #define REG_FPEC_FLASH_BANK2_OFS 0x40 //If SRAM > 1024 and target address is over 0x08080000, All FPEC Registers offset +0x40 e.g. 0x40022040 17 | #define REG_FPEC_FLASH_ACR 0x40022000 //Flash access control register 18 | #define REG_FPEC_FLASH_KEYR 0x40022004 //FPEC key register 19 | #define REG_FPEC_FLASH_OPTKEYR 0x40022008 //Option byte key register 20 | #define REG_FPEC_FLASH_SR 0x4002200C //Flash status register 21 | #define REG_FPEC_FLASH_CR 0x40022010 //Flash control register 22 | #define REG_FPEC_FLASH_AR 0x40022014 //Flash address register 23 | #define REG_FPEC_FLASH_OBR 0x4002201C //Option byte register 24 | #define REG_FPEC_FLASH_WRPR 0x40022020 //Write protection register 25 | #define REG_FPEC_KEY_RDPRT 0x00A5 26 | #define REG_FPEC_KEY_KEY1 0x45670123 27 | #define REG_FPEC_KEY_KEY2 0xCDEF89AB 28 | 29 | // System Control Space (SCS), ARMv7 ref manual, B3.2, page 708 30 | #define REG_SCB_CPUID 0xE000ED00 // CPUID Base Register 31 | #define REG_SCB_ICSR 0xE000ED04 // Interrupt Control and State 32 | #define REG_SCB_ICSR_PENDSTSET 0x04000000 33 | #define REG_SCB_VTOR 0xE000ED08 // Vector Table Offset 34 | #define REG_SCB_AIRCR 0xE000ED0C // Application Interrupt and Reset Control 35 | #define REG_SCB_SCR 0xE000ED10 // System Control Register 36 | #define REG_SCB_CCR 0xE000ED14 // Configuration and Control 37 | #define REG_SCB_SHPR1 0xE000ED18 // System Handler Priority Register 1 38 | #define REG_SCB_SHPR2 0xE000ED1C // System Handler Priority Register 2 39 | #define REG_SCB_SHPR3 0xE000ED20 // System Handler Priority Register 3 40 | #define REG_SCB_SHCSR 0xE000ED24 // System Handler Control and State 41 | #define REG_SCB_CFSR 0xE000ED28 // Configurable Fault Status Register 42 | #define REG_SCB_HFSR 0xE000ED2C // HardFault Status 43 | #define REG_SCB_DFSR 0xE000ED30 // Debug Fault Status 44 | #define REG_SCB_MMFAR 0xE000ED34 // MemManage Fault Address 45 | #define REG_SCB_DHCSR 0xE000EDF0 // Debug Halting Control and Status Register 46 | #define REG_SCB_DCRSR 0xE000EDF4 // Debug Core Register Selector Register 47 | #define REG_SCB_DCRDR 0xE000EDF8 // Debug Core Register Data Register 48 | #define REG_SCB_DEMCR 0xE000EDFC // Debug Exception and Monitor Control Register 49 | 50 | #define REG_SYST_CSR 0xE000E010 // SysTick Control and Status 51 | #define REG_SYST_CSR_COUNTFLAG 0x00010000 52 | #define REG_SYST_CSR_CLKSOURCE 0x00000004 53 | #define REG_SYST_CSR_TICKINT 0x00000002 54 | #define REG_SYST_CSR_ENABLE 0x00000001 55 | #define REG_SYST_RVR 0xE000E014 // SysTick Reload Value Register 56 | #define REG_SYST_CVR 0xE000E018 // SysTick Current Value Register 57 | #define REG_SYST_CALIB 0xE000E01C // SysTick Calibration Value 58 | 59 | #define REG_ARM_DEMCR 0xE000EDFC // Debug Exception and Monitor Control 60 | #define REG_ARM_DEMCR_TRCENA (1 << 24) // Enable debugging & monitoring blocks 61 | #define REG_ARM_DWT_CTRL 0xE0001000 // DWT control register 62 | #define REG_ARM_DWT_CTRL_CYCCNTENA (1 << 0) // Enable cycle count 63 | #define REG_ARM_DWT_CYCCNT 0xE0001004 // Cycle count register 64 | -------------------------------------------------------------------------------- /src/flashloader/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, stlink-org 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /src/flashloader/Makefile: -------------------------------------------------------------------------------- 1 | # Note that according to the original GPLed code, compiling is noted to be 2 | # as simple as gcc -c, this fails with my tests where this will lead to a wrong 3 | # address read by the program. 4 | # This makefile will save your time from dealing with compile errors 5 | # Adjust CC if needed 6 | 7 | #CC = /usr/local/share/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gcc 8 | #OBJCOPY = /usr/local/share/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-objcopy 9 | CC = arm-none-eabi-gcc 10 | OBJCOPY = arm-none-eabi-objcopy 11 | 12 | CFLAGS = -mcpu=Cortex-M3 -Tlinker.ld -ffreestanding -nostdlib 13 | 14 | all: stm32x.o stm32f0.o 15 | 16 | %.bin: %.o 17 | $(OBJCOPY) -O binary $< $@ 18 | rm $< 19 | 20 | stm32x.o: stm32x.s 21 | $(CC) stm32x.s $(CFLAGS) -o stm32x.o 22 | make stm32x.bin 23 | 24 | stm32f0.o: stm32f0.s 25 | $(CC) stm32f0.s $(CFLAGS) -o stm32f0.o 26 | make stm32f0.bin 27 | 28 | clean: 29 | rm *.bin 30 | -------------------------------------------------------------------------------- /src/flashloader/linker.ld: -------------------------------------------------------------------------------- 1 | /*. Entry Point *./ 2 | ENTRY( copy ) 3 | 4 | 5 | /*. Specify the memory areas .*/ 6 | MEMORY 7 | { 8 | RAM ( xrw) : ORIGIN = 0x20000000 , LENGTH = 64K 9 | } -------------------------------------------------------------------------------- /src/flashloader/stm32f0.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ST-Link (Cortex-M3) stlink/flashloaders/ 3 | * r0 - source 4 | * r1 - target 5 | * r2 - count(16 bits half words) 6 | * r3 - flash register offset 7 | */ 8 | 9 | //flashloader/stm32f0.s 10 | static const uint8_t flashloader_raw[] = { 11 | 0x00, 0xBF, 0x00, 0xBF, 12 | 0x0E, 0x4F, 0x1F, 0x44, 13 | 0x0E, 0x4E, 0x3E, 0x44, 14 | 0x0E, 0x4D, 0x3D, 0x44, 15 | 0x4F, 0xF0, 0x01, 0x04, 16 | 0x34, 0x60, 0x04, 0x88, 17 | 0x0C, 0x80, 0x02, 0x30, 18 | 0x02, 0x31, 0x4F, 0xF0, 19 | 0x01, 0x07, 0x2C, 0x68, 20 | 0x3C, 0x42, 0xFC, 0xD1, 21 | 0x4F, 0xF0, 0x14, 0x07, 22 | 0x3C, 0x42, 0x01, 0xD1, 23 | 0x02, 0x3A, 0xF0, 0xDC, 24 | 0x4F, 0xF0, 0x01, 0x07, 25 | 0x34, 0x68, 0xBC, 0x43, 26 | 0x34, 0x60, 0x00, 0xBE, 27 | 0x00, 0x20, 0x02, 0x40, 28 | 0x10, 0x00, 0x00, 0x00, 29 | 0x0C, 0x00, 0x00, 0x00 30 | }; 31 | -------------------------------------------------------------------------------- /src/flashloader/stm32f0.s: -------------------------------------------------------------------------------- 1 | .syntax unified 2 | .text 3 | 4 | /* 5 | * Arguments: 6 | * r0 - source memory ptr 7 | * r1 - target memory ptr 8 | * r2 - count of bytes 9 | * r3 - flash register offset 10 | */ 11 | 12 | .global copy 13 | copy: 14 | /* 15 | * These two NOPs here are a safety precaution, added by Pekka Nikander 16 | * while debugging the STM32F05x support. They may not be needed, but 17 | * there were strange problems with simpler programs, like a program 18 | * that had just a breakpoint or a program that first moved zero to register r2 19 | * and then had a breakpoint. So, it appears safest to have these two nops. 20 | * 21 | * Feel free to remove them, if you dare, but then please do test the result 22 | * rigorously. Also, if you remove these, it may be a good idea first to 23 | * #if 0 them out, with a comment when these were taken out, and to remove 24 | * these only a few months later... But YMMV. 25 | */ 26 | nop 27 | nop 28 | 29 | # load flash control register address 30 | # add r3 to flash_base for support dual bank (see flash_loader.c) 31 | ldr r7, flash_base 32 | add r7, r7, r3 33 | ldr r6, flash_off_cr 34 | add r6, r6, r7 35 | ldr r5, flash_off_sr 36 | add r5, r5, r7 37 | 38 | # FLASH_CR = 0x01 (set PG) 39 | ldr r4, =0x1 40 | str r4, [r6] 41 | 42 | loop: 43 | # copy 2 bytes 44 | ldrh r4, [r0] 45 | strh r4, [r1] 46 | 47 | # increment address 48 | adds r0, r0, #0x2 49 | adds r1, r1, #0x2 50 | 51 | # BUSY flag 52 | ldr r7, =0x01 53 | wait: 54 | # get FLASH_SR 55 | ldr r4, [r5] 56 | 57 | # wait until BUSY flag is reset 58 | tst r4, r7 59 | bne wait 60 | 61 | # test PGERR or WRPRTERR flag is reset 62 | ldr r7, =0x14 63 | tst r4, r7 64 | bne exit 65 | 66 | # loop if count > 0 67 | subs r2, r2, #0x2 68 | bgt loop 69 | 70 | exit: 71 | # FLASH_CR &= ~1 72 | ldr r7, =0x1 73 | ldr r4, [r6] 74 | bics r4, r4, r7 75 | str r4, [r6] 76 | 77 | bkpt 78 | 79 | .align 2 80 | flash_base: 81 | .word 0x40022000 82 | flash_off_cr: 83 | .word 0x10 84 | flash_off_sr: 85 | .word 0x0c 86 | -------------------------------------------------------------------------------- /src/flashloader/stm32x.h: -------------------------------------------------------------------------------- 1 | /* 2 | * From OpenOCD, contrib/loaders/flash/stm32.s 3 | * i = STM32_FLASH_BASE; 0x08+r3 0x0800,0000 4 | * r0 = fl->buf_addr; // source address 5 | * r1 = target; // target address 6 | * r2 = count; // count (16 bits half words) 7 | * r3 = 0; // result 8 | * r15 = fl->loader_addr; // pc register 9 | */ 10 | 11 | //flashloader/stm32x.s 12 | static const uint8_t flashloader_raw[] = { 13 | /* #define STM32_FLASH_CR_OFFSET 0x10 */ 14 | /* #define STM32_FLASH_SR_OFFSET 0x0C */ 15 | /* write: */ 16 | 0xdf, 0xf8, 0x20, 0x40, /* ldr r4, STM32_FLASH_BASE */ 17 | /* write_half_word: */ 18 | 0x01, 0x23, /* movs r3, #0x01 */ 19 | 0x23, 0x61, /* str r3, [r4, #STM32_FLASH_CR_OFFSET] */ 20 | 0x30, 0xf8, 0x02, 0x3b, /* ldrh r3, [r0], #0x02 */ 21 | 0x21, 0xf8, 0x02, 0x3b, /* strh r3, [r1], #0x02 */ 22 | /* busy: */ 23 | 0xe3, 0x68, /* ldr r3, [r4, #STM32_FLASH_SR_OFFSET] */ 24 | 0x13, 0xf0, 0x01, 0x0f, /* tst r3, #0x01 */ 25 | 0xfb, 0xd0, /* beq busy */ 26 | 0x13, 0xf0, 0x14, 0x0f, /* tst r3, #0x14 */ 27 | 0x01, 0xd1, /* bne exit */ 28 | 0x01, 0x3a, /* subs r2, r2, #0x01 */ 29 | 0xf0, 0xd1, /* bne write_half_word */ 30 | /* exit: */ 31 | 0x00, 0xbe, /* bkpt #0x00 */ 32 | 0x00, 0x20, 0x02, 0x40, /* STM32_FLASH_BASE: .word 0x40022000 */ 33 | }; 34 | -------------------------------------------------------------------------------- /src/flashloader/stm32x.s: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (C) 2010 by Spencer Oliver * 3 | * spen@spen-soft.co.uk * 4 | * * 5 | * This program is free software; you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation; either version 2 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * This program is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with this program; if not, write to the * 17 | * Free Software Foundation, Inc., * 18 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * 19 | ***************************************************************************/ 20 | 21 | .text 22 | .syntax unified 23 | .thumb 24 | .thumb_func 25 | .global write 26 | 27 | /* 28 | r0 - source address 29 | r1 - target address 30 | r2 - count (halfword-16bit) 31 | r3 - result 32 | r4 - temp 33 | */ 34 | 35 | #define STM32_FLASH_CR_OFFSET 0x10 /* offset of CR register in FLASH struct */ 36 | #define STM32_FLASH_SR_OFFSET 0x0c /* offset of CR register in FLASH struct */ 37 | 38 | write: 39 | ldr r4, STM32_FLASH_BASE 40 | write_half_word: 41 | movs r3, #0x01 42 | str r3, [r4, #0x10] /* PG (bit0) == 1 => flash programming enabled */ 43 | ldrh r3, [r0], #0x02 /* read one half-word from src, increment ptr */ 44 | strh r3, [r1], #0x02 /* write one half-word from src, increment ptr */ 45 | busy: 46 | ldr r3, [r4, #0x0c] 47 | tst r3, #0x01 /* BSY (bit0) == 1 => operation in progress */ 48 | beq busy /* wait more... */ 49 | tst r3, #0x14 /* PGERR (bit2) == 1 or WRPRTERR (bit4) == 1 => error */ 50 | bne exit /* fail... */ 51 | subs r2, r2, #0x01 /* decrement counter */ 52 | bne write_half_word /* write next half-word if anything left */ 53 | exit: 54 | bkpt #0x00 55 | 56 | STM32_FLASH_BASE: .word 0x40022000 /* base address of FLASH struct */ 57 | -------------------------------------------------------------------------------- /svg/activity.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/check-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/cpu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/database.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/file-text.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/grid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/help-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/plus-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/repeat.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/rotate-ccw.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/save.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/slider.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/square.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/target.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/terminal.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/trash-2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/trash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/upload-cloud.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /svg/wifi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -lt 1 ] || [ $# -gt 2 ]; then 4 | echo "This tool will send either one, or all data files to the web interface" 5 | echo "" 6 | echo "Syntax: $0 []" 7 | exit 255 8 | fi 9 | 10 | IP="$1" 11 | echo "Uploading to $IP" 12 | 13 | if [ $# -gt 1 ]; then 14 | files="$2" 15 | else 16 | files="./data/*" 17 | fi 18 | 19 | for file in $files; do 20 | echo "Sending: $file" 21 | # curl -v --trace-ascii - -F 'data=@"'"$file"'";filename="'"$(basename "$file")"'"' http://"$IP"/edit 22 | curl -F 'data=@"'"$file"'";filename="'"$(basename "$file")"'"' http://"$IP"/edit 23 | done 24 | --------------------------------------------------------------------------------