From f34586ecae9277ea72b2297713a08bb76c4bf6c0 Mon Sep 17 00:00:00 2001 From: Jelmer Date: Mon, 26 Dec 2022 22:33:49 +0100 Subject: [PATCH] fresh --- .gitattributes | 2 + .gitignore | 25 + README.md | 76 ++ ap_fw/.gitignore | 25 + ap_fw/LICENSE.txt | 7 + ap_fw/Makefile | 55 ++ ap_fw/adc.h | 16 + ap_fw/asmUtil.h | 114 +++ ap_fw/board/boardCommon.h | 20 + ap_fw/board/boardZBS29common.c | 165 ++++ ap_fw/board/zbs29v033/board.c | 1 + ap_fw/board/zbs29v033/board.h | 53 ++ ap_fw/board/zbs29v033/make.mk | 7 + ap_fw/board/zbs29v033/screen.c | 379 ++++++++ ap_fw/board/zbs29v033/screen.h | 48 + ap_fw/builder.php | 47 + ap_fw/comms.c | 51 + ap_fw/comms.h | 30 + ap_fw/cpu/8051/asmUtil.c | 1502 ++++++++++++++++++++++++++++++ ap_fw/cpu/8051/cpu.c | 1 + ap_fw/cpu/8051/cpu.h | 18 + ap_fw/cpu/8051/make.mk | 8 + ap_fw/cpu/8051/peep.def | 115 +++ ap_fw/cpu/8051/printf.c | 795 ++++++++++++++++ ap_fw/cpu/8051/random.c | 261 ++++++ ap_fw/eeprom.c | 322 +++++++ ap_fw/eeprom.h | 54 ++ ap_fw/main.c | 891 ++++++++++++++++++ ap_fw/printf.h | 27 + ap_fw/proto.h | 182 ++++ ap_fw/sleep.h | 10 + ap_fw/soc/radioCommon.h | 67 ++ ap_fw/soc/zbs243/flash.c | 182 ++++ ap_fw/soc/zbs243/flash.h | 23 + ap_fw/soc/zbs243/i2c.c | 128 +++ ap_fw/soc/zbs243/i2c.h | 34 + ap_fw/soc/zbs243/make.mk | 8 + ap_fw/soc/zbs243/radio.c | 336 +++++++ ap_fw/soc/zbs243/radio.h | 22 + ap_fw/soc/zbs243/sleep.c | 66 ++ ap_fw/soc/zbs243/soc.c | 12 + ap_fw/soc/zbs243/soc.h | 29 + ap_fw/soc/zbs243/spi.c | 32 + ap_fw/soc/zbs243/spi.h | 16 + ap_fw/soc/zbs243/temperature.c | 113 +++ ap_fw/soc/zbs243/timer.c | 65 ++ ap_fw/soc/zbs243/timer.h | 24 + ap_fw/soc/zbs243/uart.c | 97 ++ ap_fw/soc/zbs243/uart.h | 25 + ap_fw/soc/zbs243/wdt.c | 59 ++ ap_fw/soc/zbs243/zbs243.h | 289 ++++++ ap_fw/wdt.h | 23 + esp32_fw/.clang-format | 4 + esp32_fw/.gitignore | 5 + esp32_fw/.vscode/extensions.json | 10 + esp32_fw/.vscode/settings.json | 3 + esp32_fw/data/index.html | 65 ++ esp32_fw/data/jquery.js | 6 + esp32_fw/data/main.css | 203 ++++ esp32_fw/data/main.js | 23 + esp32_fw/data/test154.bmp | Bin 0 -> 3102 bytes esp32_fw/data/test29.bmp | Bin 0 -> 4798 bytes esp32_fw/data/test42.bmp | Bin 0 -> 15662 bytes esp32_fw/include/commstructs.h | 65 ++ esp32_fw/include/flasher.h | 4 + esp32_fw/include/http.h | 4 + esp32_fw/include/newproto.h | 10 + esp32_fw/include/pendingdata.h | 23 + esp32_fw/include/serial.h | 6 + esp32_fw/include/settings.h | 45 + esp32_fw/include/web.h | 13 + esp32_fw/include/zbs_interface.h | 57 ++ esp32_fw/lib/README | 46 + esp32_fw/platformio.ini | 27 + esp32_fw/src/SPIFFSEditor.cpp | 561 +++++++++++ esp32_fw/src/flasher.cpp | 224 +++++ esp32_fw/src/http.cpp | 41 + esp32_fw/src/main.cpp | 54 ++ esp32_fw/src/newproto.cpp | 154 +++ esp32_fw/src/pendingdata.cpp | 53 ++ esp32_fw/src/serial.cpp | 352 +++++++ esp32_fw/src/web.cpp | 406 ++++++++ esp32_fw/src/zbs_interface.cpp | 214 +++++ esp32_fw/test/README | 11 + tag_fw/.gitignore | 25 + tag_fw/LICENSE.txt | 7 + tag_fw/Makefile | 77 ++ tag_fw/adc.h | 16 + tag_fw/asmUtil.h | 114 +++ tag_fw/barcode.c | 101 ++ tag_fw/barcode.h | 29 + tag_fw/board/boardCommon.h | 58 ++ tag_fw/board/boardZBS29common.c | 166 ++++ tag_fw/board/zbs154v033/board.c | 1 + tag_fw/board/zbs154v033/board.h | 53 ++ tag_fw/board/zbs154v033/make.mk | 7 + tag_fw/board/zbs154v033/screen.c | 475 ++++++++++ tag_fw/board/zbs154v033/screen.h | 50 + tag_fw/board/zbs29v033/board.c | 1 + tag_fw/board/zbs29v033/board.h | 53 ++ tag_fw/board/zbs29v033/make.mk | 7 + tag_fw/board/zbs29v033/screen.c | 397 ++++++++ tag_fw/board/zbs29v033/screen.h | 48 + tag_fw/board/zbs42v033/board.c | 1 + tag_fw/board/zbs42v033/board.h | 53 ++ tag_fw/board/zbs42v033/make.mk | 7 + tag_fw/board/zbs42v033/screen.c | 416 +++++++++ tag_fw/board/zbs42v033/screen.h | 50 + tag_fw/builder.php | 47 + tag_fw/chars.c | 182 ++++ tag_fw/chars.h | 32 + tag_fw/comms.c | 95 ++ tag_fw/comms.h | 28 + tag_fw/cpu/8051/asmUtil.c | 1502 ++++++++++++++++++++++++++++++ tag_fw/cpu/8051/cpu.c | 1 + tag_fw/cpu/8051/cpu.h | 18 + tag_fw/cpu/8051/make.mk | 9 + tag_fw/cpu/8051/peep.def | 115 +++ tag_fw/cpu/8051/printf.c | 795 ++++++++++++++++ tag_fw/cpu/8051/random.c | 261 ++++++ tag_fw/drawing.c | 426 +++++++++ tag_fw/drawing.h | 34 + tag_fw/eeprom.c | 322 +++++++ tag_fw/eeprom.h | 54 ++ tag_fw/main.c | 37 + tag_fw/main.map.old | 715 ++++++++++++++ tag_fw/makeit.exe | Bin 0 -> 219662 bytes tag_fw/printf.h | 27 + tag_fw/proto.h | 182 ++++ tag_fw/sleep.h | 10 + tag_fw/soc/radioCommon.h | 67 ++ tag_fw/soc/zbs243/flash.c | 182 ++++ tag_fw/soc/zbs243/flash.h | 23 + tag_fw/soc/zbs243/i2c.c | 126 +++ tag_fw/soc/zbs243/i2c.h | 34 + tag_fw/soc/zbs243/make.mk | 8 + tag_fw/soc/zbs243/radio.c | 336 +++++++ tag_fw/soc/zbs243/radio.h | 22 + tag_fw/soc/zbs243/sleep.c | 66 ++ tag_fw/soc/zbs243/soc.c | 12 + tag_fw/soc/zbs243/soc.h | 30 + tag_fw/soc/zbs243/spi.c | 32 + tag_fw/soc/zbs243/spi.h | 16 + tag_fw/soc/zbs243/temperature.c | 113 +++ tag_fw/soc/zbs243/timer.c | 65 ++ tag_fw/soc/zbs243/timer.h | 24 + tag_fw/soc/zbs243/uart.c | 24 + tag_fw/soc/zbs243/uart.h | 18 + tag_fw/soc/zbs243/wdt.c | 59 ++ tag_fw/soc/zbs243/zbs243.h | 289 ++++++ tag_fw/syncedproto.c | 1054 +++++++++++++++++++++ tag_fw/syncedproto.h | 10 + tag_fw/wdt.h | 23 + 153 files changed, 19383 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.md create mode 100644 ap_fw/.gitignore create mode 100644 ap_fw/LICENSE.txt create mode 100644 ap_fw/Makefile create mode 100644 ap_fw/adc.h create mode 100644 ap_fw/asmUtil.h create mode 100644 ap_fw/board/boardCommon.h create mode 100644 ap_fw/board/boardZBS29common.c create mode 100644 ap_fw/board/zbs29v033/board.c create mode 100644 ap_fw/board/zbs29v033/board.h create mode 100644 ap_fw/board/zbs29v033/make.mk create mode 100644 ap_fw/board/zbs29v033/screen.c create mode 100644 ap_fw/board/zbs29v033/screen.h create mode 100644 ap_fw/builder.php create mode 100644 ap_fw/comms.c create mode 100644 ap_fw/comms.h create mode 100644 ap_fw/cpu/8051/asmUtil.c create mode 100644 ap_fw/cpu/8051/cpu.c create mode 100644 ap_fw/cpu/8051/cpu.h create mode 100644 ap_fw/cpu/8051/make.mk create mode 100644 ap_fw/cpu/8051/peep.def create mode 100644 ap_fw/cpu/8051/printf.c create mode 100644 ap_fw/cpu/8051/random.c create mode 100644 ap_fw/eeprom.c create mode 100644 ap_fw/eeprom.h create mode 100644 ap_fw/main.c create mode 100644 ap_fw/printf.h create mode 100644 ap_fw/proto.h create mode 100644 ap_fw/sleep.h create mode 100644 ap_fw/soc/radioCommon.h create mode 100644 ap_fw/soc/zbs243/flash.c create mode 100644 ap_fw/soc/zbs243/flash.h create mode 100644 ap_fw/soc/zbs243/i2c.c create mode 100644 ap_fw/soc/zbs243/i2c.h create mode 100644 ap_fw/soc/zbs243/make.mk create mode 100644 ap_fw/soc/zbs243/radio.c create mode 100644 ap_fw/soc/zbs243/radio.h create mode 100644 ap_fw/soc/zbs243/sleep.c create mode 100644 ap_fw/soc/zbs243/soc.c create mode 100644 ap_fw/soc/zbs243/soc.h create mode 100644 ap_fw/soc/zbs243/spi.c create mode 100644 ap_fw/soc/zbs243/spi.h create mode 100644 ap_fw/soc/zbs243/temperature.c create mode 100644 ap_fw/soc/zbs243/timer.c create mode 100644 ap_fw/soc/zbs243/timer.h create mode 100644 ap_fw/soc/zbs243/uart.c create mode 100644 ap_fw/soc/zbs243/uart.h create mode 100644 ap_fw/soc/zbs243/wdt.c create mode 100644 ap_fw/soc/zbs243/zbs243.h create mode 100644 ap_fw/wdt.h create mode 100644 esp32_fw/.clang-format create mode 100644 esp32_fw/.gitignore create mode 100644 esp32_fw/.vscode/extensions.json create mode 100644 esp32_fw/.vscode/settings.json create mode 100644 esp32_fw/data/index.html create mode 100644 esp32_fw/data/jquery.js create mode 100644 esp32_fw/data/main.css create mode 100644 esp32_fw/data/main.js create mode 100644 esp32_fw/data/test154.bmp create mode 100644 esp32_fw/data/test29.bmp create mode 100644 esp32_fw/data/test42.bmp create mode 100644 esp32_fw/include/commstructs.h create mode 100644 esp32_fw/include/flasher.h create mode 100644 esp32_fw/include/http.h create mode 100644 esp32_fw/include/newproto.h create mode 100644 esp32_fw/include/pendingdata.h create mode 100644 esp32_fw/include/serial.h create mode 100644 esp32_fw/include/settings.h create mode 100644 esp32_fw/include/web.h create mode 100644 esp32_fw/include/zbs_interface.h create mode 100644 esp32_fw/lib/README create mode 100644 esp32_fw/platformio.ini create mode 100644 esp32_fw/src/SPIFFSEditor.cpp create mode 100644 esp32_fw/src/flasher.cpp create mode 100644 esp32_fw/src/http.cpp create mode 100644 esp32_fw/src/main.cpp create mode 100644 esp32_fw/src/newproto.cpp create mode 100644 esp32_fw/src/pendingdata.cpp create mode 100644 esp32_fw/src/serial.cpp create mode 100644 esp32_fw/src/web.cpp create mode 100644 esp32_fw/src/zbs_interface.cpp create mode 100644 esp32_fw/test/README create mode 100644 tag_fw/.gitignore create mode 100644 tag_fw/LICENSE.txt create mode 100644 tag_fw/Makefile create mode 100644 tag_fw/adc.h create mode 100644 tag_fw/asmUtil.h create mode 100644 tag_fw/barcode.c create mode 100644 tag_fw/barcode.h create mode 100644 tag_fw/board/boardCommon.h create mode 100644 tag_fw/board/boardZBS29common.c create mode 100644 tag_fw/board/zbs154v033/board.c create mode 100644 tag_fw/board/zbs154v033/board.h create mode 100644 tag_fw/board/zbs154v033/make.mk create mode 100644 tag_fw/board/zbs154v033/screen.c create mode 100644 tag_fw/board/zbs154v033/screen.h create mode 100644 tag_fw/board/zbs29v033/board.c create mode 100644 tag_fw/board/zbs29v033/board.h create mode 100644 tag_fw/board/zbs29v033/make.mk create mode 100644 tag_fw/board/zbs29v033/screen.c create mode 100644 tag_fw/board/zbs29v033/screen.h create mode 100644 tag_fw/board/zbs42v033/board.c create mode 100644 tag_fw/board/zbs42v033/board.h create mode 100644 tag_fw/board/zbs42v033/make.mk create mode 100644 tag_fw/board/zbs42v033/screen.c create mode 100644 tag_fw/board/zbs42v033/screen.h create mode 100644 tag_fw/builder.php create mode 100644 tag_fw/chars.c create mode 100644 tag_fw/chars.h create mode 100644 tag_fw/comms.c create mode 100644 tag_fw/comms.h create mode 100644 tag_fw/cpu/8051/asmUtil.c create mode 100644 tag_fw/cpu/8051/cpu.c create mode 100644 tag_fw/cpu/8051/cpu.h create mode 100644 tag_fw/cpu/8051/make.mk create mode 100644 tag_fw/cpu/8051/peep.def create mode 100644 tag_fw/cpu/8051/printf.c create mode 100644 tag_fw/cpu/8051/random.c create mode 100644 tag_fw/drawing.c create mode 100644 tag_fw/drawing.h create mode 100644 tag_fw/eeprom.c create mode 100644 tag_fw/eeprom.h create mode 100644 tag_fw/main.c create mode 100644 tag_fw/main.map.old create mode 100644 tag_fw/makeit.exe create mode 100644 tag_fw/printf.h create mode 100644 tag_fw/proto.h create mode 100644 tag_fw/sleep.h create mode 100644 tag_fw/soc/radioCommon.h create mode 100644 tag_fw/soc/zbs243/flash.c create mode 100644 tag_fw/soc/zbs243/flash.h create mode 100644 tag_fw/soc/zbs243/i2c.c create mode 100644 tag_fw/soc/zbs243/i2c.h create mode 100644 tag_fw/soc/zbs243/make.mk create mode 100644 tag_fw/soc/zbs243/radio.c create mode 100644 tag_fw/soc/zbs243/radio.h create mode 100644 tag_fw/soc/zbs243/sleep.c create mode 100644 tag_fw/soc/zbs243/soc.c create mode 100644 tag_fw/soc/zbs243/soc.h create mode 100644 tag_fw/soc/zbs243/spi.c create mode 100644 tag_fw/soc/zbs243/spi.h create mode 100644 tag_fw/soc/zbs243/temperature.c create mode 100644 tag_fw/soc/zbs243/timer.c create mode 100644 tag_fw/soc/zbs243/timer.h create mode 100644 tag_fw/soc/zbs243/uart.c create mode 100644 tag_fw/soc/zbs243/uart.h create mode 100644 tag_fw/soc/zbs243/wdt.c create mode 100644 tag_fw/soc/zbs243/zbs243.h create mode 100644 tag_fw/syncedproto.c create mode 100644 tag_fw/syncedproto.h create mode 100644 tag_fw/wdt.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..dfe077042 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..9b5ed19d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +*.asm +*.lst +*.rst +*.sym +*.ihx +*.map +*.mem +*.adb +*.cdb +*.omf +*.bak +*.rel +*.asm +*.lst +*.rst +*.sym +*.map +*.mem +*.ihx +*.adb +*.cdb +*.omf +*.bin +*.lk +*.o diff --git a/README.md b/README.md new file mode 100644 index 000000000..76b308def --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# solum-esl-alternative-proto + +## ⚠️⚠️⚠️THIS IS NOT PRODUCTION READY!⚠️⚠️⚠️ +This is not a final, polished codebase. Not by a long shot. It's a complicated system, and it is not unlikely to be a dead end. There are drawbacks to a slotted protocol, it's hard to debug, and it might not work all that well in unfavourable RF conditions. You'll need some knowledge on the use of these tags. A very good place to start is here: https://github.com/atc1441/ZBS_Flasher. You'll need to fix issues yourself, troubleshoot stuff. Once again: this is not for everyone. + +This is an alternative protocol for the ZBS243-based Electronic Shelf Labels - ESL / price tags by Solum / Samsung. + +### Compatibility +It is currently compatible with the following tags: +* 4.2" +* 2.9" +* 1.54" + +### Aims +- Low power (currently around 8µA with 30 second latency) +- Low latency (tags can check for new data every 30 seconds) +- High transfer speeds - It can do about 5kbyte/s in favorable RF conditions. This allows for lower power +- RF-spectrum friendly - We don't need to acknowledge EVERY packet, and we don't need to transfer data we already have + +The entire setup requires a few tags, and an ESP32. A (preferably, but not necessarily) broken tag is used as an 802.15.4 radio for the ESP32. You'll need a ZBS_Flasher in order to flash both the AP with its firmware, and the tags. Using the 'mac' option on ZBS_Flasher makes sure a tag flashed with a custom firmware has a valid mac address; it used the stock mac address assigned to the tag if it hasn't been flashed before. If you want to set it yourself, you can edit the mac address in the infopage. The AP expects a tag with a mac that starts with 00:00, followed by 6 bytes. The MAC-address also needs to be set on the AP-tag. + +Once flashed, you can hook the AP tag up to the ESP32 by connecting the tags serial lines to some free pins. Make sure you set the pins in settings.h, so that the ESP32 can communicate with it. This can be validated by checking the ESP32 debug output; you should see 'sync burst' displayed every 30 seconds + +You can access the ESP32 with any web browser after connecting it to your WiFi Network. The file browser is located at /edit. For sending data to tags, you'll need to upload the information in 'data' to the ESP32's filesystem. After uploading, you can access the status screen at /index.html. If everything is working, you should be able to see tags synchronising to the network. After uploading a suitable .bmp file to the filesystem, this file can be sent to the tag by entering it's 6-byte mac address and filename. + +## The synchronized/slotted protocol explained, kinda. This is simplified, the reality is even more convoluted + +- Every 30 seconds, the AP sends a 250ms long 'burst' of packets, each marked with a sequential 802.15.4 sequence number. The tag, when synchronized to the network, listens for a short while every 30 seconds, to receive exactly one packet. Based on the sequence number received by the tag, the tag can calculate clock drift and window-offset to the 30 seconds interval, in order to remain synchronized. +- At bootup, the tag will try to find an access point, and get timing information. It will start a calibration cycle in order to characterize the difference between the AP's clock and the internal (RC) oscillator. This allows for a fairly precise reception of the sync burst. After initial calibration, the tag will slowly adjust its calibration value to compensate for drift in the RC oscillator frequency. +- When synced, the tag only listens for a -very- short period of time, which allows for both low power use and low latency. +- The ESP32 can send a 'pendingData' message to the AP, indicating it wants a specific tag to check-in to the AP. +- AP adds this pendingData struc to a queue, and adds these tags to the sync-burst +- The sync-burst packets contain up to (currently) 2 mac addresses of tags that the AP wants to talk to, and an 'offset'. Tag receives a MAC address, see if it matches its own, and when it does, it sleeps for [offset] ms. This allows for minimal power consumption and maximum throughput, as the AP determines which tags are allowed to talk. +- When addressed by AP in the sync-burst, the tag wakes up after the offset period, and requests information from the AP. The AP responds with some metadata, such as size, MD5 checksum, and data type. +- The tag checks if this information is already downloaded to EEPROM, or is already displayed. If this is the case, the transfer is immediately cancelled by issuing a 'transfer complete' packet to the AP. +- The tag then proceeds to request data in 'blocks' of 4096 bytes. The AP responds with an ACK on the request, and specifies how long it will spend to gather the data. The tag sleeps until the AP will send the data +- The AP requests its block-buffer to be filled by the ESP32, specifying MD5 and blockID +- Datablock is sent by the AP, which will take about 42 packets for a full block. The tag will keep track of which blocks it has seen +- After a block has been received with missing parts, the tag will request the same block, with a bitmask of blocks that are missing +- The AP responds with the parts as requested by the tag. If there aren't too many blocks requested, the AP will fill the block with duplicate parts, to increase the chance of a successful transmission +- After a full block has been received, the tag will do a simple checksum over all received data. If the checksum matches, the 4K of data is stored in EEPROM storage for later use +- The tag will now either request the next block, or do a full re-request if the checksum failed +- If all blocks are received, the tag will send a 'transfer complete'. +- The AP will report the completed transfer to the ESP32, and removes the pendingData for this transfer from the queue + +## Todo: +- Code cleanup... Splitting into different files, for instance. It's a mess. +### Tags: +- Implement NFC for URL's +- Implement battery reading +- Implement RSSI/LQI to be sent to the AP +### AP: +- More reliable serial comms (sometimes bytes are dropped) +- Include source mac with blockrequest struct +### ESP32: +- Do more with status info as sent by the tags + +## Known issues: +- The RC oscillator has some jitter, especially on longer sleep times. I find it difficult to have the tag sleep for much longer than 30s without losing synchronization to the network +- For some reason, the screen needs to be reset and put to sleep -EVERY TIME- the tag wakes up. This is a relatively slow process; it would really help if we could find out what causes this. Some glitch on the reset line of the EPD would be my guess... +- The ZBS CPU should be able to sleep during the EPD-draw command; however, this currently (for some reason) increases the sleep-current-draw +- Some tags work better as AP's than others. Your range may suck. The boards on these tags are tiny and fragile. For instance, a dab of hot-glue on a board is enough to warp it pretty severely, and will damage the components that are soldered on there. Reportedly, segmented-display solum tags work well. + +## Hints and excuses: +- I'm sorry if reading this spaghetti code makes you lose your mind. 'Of all the things I've lost, I miss my mind the most' I know it is pretty unreadable. I could blame SDCC for a lot of things, but it's mostly me. +- There is no warranty whatsoever. Nothing. Not implied or otherwise suggested. This code isn't fit for anything. Please don't use this code to do nasty things. +- Do a ```make clean``` between building for different boards. It really helps! +- This repo builds on SDCC 4.2.0 for Linux. Different SDCC versions can behave VERY differently. +- I am happy and honored to accept pull requests! But please, no code/indent style changes :) + +## Credits +### Large parts of this repo are based on code written by, and wouldn't be possible without the hard work of: +- dmitry.gr +- atc1441 + +Hats off to these legends! diff --git a/ap_fw/.gitignore b/ap_fw/.gitignore new file mode 100644 index 000000000..9b5ed19d9 --- /dev/null +++ b/ap_fw/.gitignore @@ -0,0 +1,25 @@ +*.asm +*.lst +*.rst +*.sym +*.ihx +*.map +*.mem +*.adb +*.cdb +*.omf +*.bak +*.rel +*.asm +*.lst +*.rst +*.sym +*.map +*.mem +*.ihx +*.adb +*.cdb +*.omf +*.bin +*.lk +*.o diff --git a/ap_fw/LICENSE.txt b/ap_fw/LICENSE.txt new file mode 100644 index 000000000..83a002729 --- /dev/null +++ b/ap_fw/LICENSE.txt @@ -0,0 +1,7 @@ +This E-Paper firmware mod is based on Dmitry Grinbergs firmware which is available here: +http://dmitry.gr/?r=05.Projects&proj=29.%20eInk%20Price%20Tags + +Many thanks to Dmitry for sharing such a wonderful work ! + +The license is simple: +This code/data/waveforms are free for use in hobby and other non-commercial products. For commercial use, Dmitry: licensing@dmitry.gr diff --git a/ap_fw/Makefile b/ap_fw/Makefile new file mode 100644 index 000000000..4a251fc4d --- /dev/null +++ b/ap_fw/Makefile @@ -0,0 +1,55 @@ + +BUILD ?= zbs29v033 + +#file containing main() must be first! +SOURCES += main.c eeprom.c +SOURCES += comms.c + + +all: #make sure it is the first target + +include board/$(BUILD)/make.mk +include soc/$(SOC)/make.mk +include cpu/$(CPU)/make.mk +FLAGS += -Iboard/$(BUILD) +FLAGS += -Isoc/$(SOC) +FLAGS += -Icpu/$(CPU) + +SOURCES += cpu/$(CPU)/cpu.c +SOURCES += board/$(BUILD)/board.c + +FLAGS += -I. + + +OBJS = $(patsubst %.c,%.$(OBJFILEEXT),$(SOURCES)) + +all: $(TARGETS) + + +%.$(OBJFILEEXT): %.c + $(CC) -c $^ $(FLAGS) -o $@ + +main.ihx: $(OBJS) + rm -f $@ + $(CC) $^ $(FLAGS) + +main.elf: $(OBJS) + $(CC) $(FLAGS) -o $@ $^ + +%.bin: %.ihx + objcopy $^ $@ -I ihex -O binary + +.PHONY: clean +clean: + rm -f $(patsubst %.c,%.asm,$(SOURCES)) + rm -f $(patsubst %.c,%.lst,$(SOURCES)) + rm -f $(patsubst %.c,%.rst,$(SOURCES)) + rm -f $(patsubst %.c,%.sym,$(SOURCES)) + rm -f $(patsubst %.c,%.map,$(SOURCES)) + rm -f $(patsubst %.c,%.mem,$(SOURCES)) + rm -f $(patsubst %.c,%.ihx,$(SOURCES)) + rm -f $(patsubst %.c,%.adb,$(SOURCES)) + rm -f $(patsubst %.c,%.adb,$(SOURCES)) + rm -f $(patsubst %.c,%.omf,$(SOURCES)) + rm -f $(patsubst %.c,%.bin,$(SOURCES)) + rm -f $(OBJS) diff --git a/ap_fw/adc.h b/ap_fw/adc.h new file mode 100644 index 000000000..c495bc075 --- /dev/null +++ b/ap_fw/adc.h @@ -0,0 +1,16 @@ +#ifndef _ADC_H_ +#define _ADC_H_ + +#include + +extern uint16_t __xdata mAdcSlope; +extern uint16_t __xdata mAdcIntercept; + + +uint16_t adcSampleBattery(void); //in mV +int8_t adcSampleTemperature(void); //in degrees C + + + + +#endif diff --git a/ap_fw/asmUtil.h b/ap_fw/asmUtil.h new file mode 100644 index 000000000..423c20b6b --- /dev/null +++ b/ap_fw/asmUtil.h @@ -0,0 +1,114 @@ +#ifndef _ASM_UTIL_H_ +#define _ASM_UTIL_H_ + + +#include +#include "cpu.h" + + + +//SDCC may have uint64_t support, but it is so shitty, we're better off not using it +//sdcc is brain dead when compiling multiplication, so we write our own asm code to make it better...le sigh... +//SDCC also has issues managing xdata memory ops, we write our own + +#pragma callee_saves u64_copy +#pragma callee_saves u64_isLt +#pragma callee_saves u64_isEq +#pragma callee_saves u64_sub +#pragma callee_saves u64_add +#pragma callee_saves u64_inc +#pragma callee_saves u64_dec + +#pragma callee_saves xMemSet +#pragma callee_saves xMemEqual +#pragma callee_saves xMemEqual4 +#pragma callee_saves xMemCopy +#pragma callee_saves xMemCopyShort +#pragma callee_saves xMemCopy8 +#pragma callee_saves xStrLen + +#pragma callee_saves rngGen + +#pragma callee_saves mathPrvMul8x8 +#pragma callee_saves mathPrvMul16x8 +#pragma callee_saves mathPrvMul16x16 +#pragma callee_saves mathPrvMul32x8 +#pragma callee_saves mathPrvDiv32x8 +#pragma callee_saves mathPrvDiv32x16 +#pragma callee_saves mathPrvMod32x16 +#pragma callee_saves mathPrvDiv16x8 +#pragma callee_saves mathPrvMod16x8 +#pragma callee_saves mathPrvCopyPostinc + +#pragma callee_saves mathPrvI16Asr1 + +#pragma callee_saves mathPrvU8bitswap + +#pragma callee_saves charsPrvDerefAndIncGenericPtr //return *(*generic_charPtr)++ + + +//it saddens me that i need these....but i do +#pragma callee_saves mathPrvU16from2xU8 +#pragma callee_saves mathPrvU32from4xU8 +#pragma callee_saves u32minusU16 +#pragma callee_saves u32plusU16 +#pragma callee_saves u32Nonzero +#pragma callee_saves i32Negative + + +__bit u32minusU16(uint32_t __xdata *u32, uint16_t u16) __reentrant; //sets carry +__bit u32plusU16(uint32_t __xdata *u32, uint16_t u16) __reentrant; //sets carry +uint8_t u32Nonzero(uint32_t __xdata *u32) __reentrant; +__bit i32Negative(uint32_t __xdata *u32) __reentrant; + + +void u64_copy(uint64_t __xdata *dst, const uint64_t __xdata *src) __reentrant; +void u64_copyFromCode(uint64_t __xdata *dst, const uint64_t __code *src) __reentrant; +__bit u64_isLt(const uint64_t __xdata *lhs, const uint64_t __xdata *rhs) __reentrant; +__bit u64_isEq(const uint64_t __xdata *lhs, const uint64_t __xdata *rhs) __reentrant; +void u64_sub(uint64_t __xdata *lhs, const uint64_t __xdata *rhs) __reentrant; +void u64_add(uint64_t __xdata *lhs, const uint64_t __xdata *rhs) __reentrant; +void u64_and(uint64_t __xdata *lhs, const uint64_t __xdata *rhs) __reentrant; +void u64_inc(uint64_t __xdata *dst) __reentrant; +void u64_dec(uint64_t __xdata *dst) __reentrant; + +#define U64FMT "%04x%04x%04x%04x" +#define U64CVT(v) ((uint16_t __xdata*)&v)[3], ((uint16_t __xdata*)&v)[2], ((uint16_t __xdata*)&v)[1], ((uint16_t __xdata*)&v)[0] + +int16_t mathPrvI16Asr1(int16_t val) __reentrant; + +uint16_t mathPrvMul8x8(uint8_t a, uint8_t b) __reentrant; +uint32_t mathPrvMul16x8(uint16_t a, uint8_t b) __reentrant; +uint32_t mathPrvMul16x16(uint16_t a, uint16_t b) __reentrant; +uint32_t mathPrvMul32x8(uint32_t a, uint8_t b) __reentrant; +uint32_t mathPrvDiv32x8(uint32_t num, uint8_t denom) __reentrant; +uint32_t mathPrvDiv32x16(uint32_t num, uint16_t denom) __reentrant; +uint16_t mathPrvMod32x16(uint32_t num, uint16_t denom) __reentrant; +uint16_t mathPrvDiv16x8(uint16_t num, uint8_t denom) __reentrant; +uint8_t mathPrvMod16x8(uint16_t num, uint8_t denom) __reentrant; + +uint8_t mathPrvU8bitswap(uint8_t val) __reentrant; + +uint16_t mathPrvU16from2xU8(uint8_t hi, uint8_t lo) __reentrant; +uint32_t mathPrvU32from4xU8(uint8_t hi, uint8_t midhi, uint8_t midlo, uint8_t lo) __reentrant; + +char charsPrvDerefAndIncGenericPtr(const char * __xdata* __xdata str); + +void xMemSet(void __xdata* mem, uint8_t val, uint16_t num) __reentrant; +__bit xMemEqual(const void __xdata* memA, const void __xdata* memB, uint8_t num) __reentrant; +__bit xMemEqual4(const void __xdata* memA, const void __xdata* memB) __reentrant; +void xMemCopy(void __xdata* dst, const void __xdata* src, uint16_t num) __reentrant; +void xMemCopyShort(void __xdata* dst, const void __xdata* src, uint8_t num) __reentrant; +uint16_t xStrLen(const char __xdata *str) __reentrant; + +#define xMemCopy8(_dst, _src) u64_copy((uint64_t __xdata*)(_dst), (const uint64_t __xdata*)(_src)) + + +void mathPrvCopyPostinc(uint32_t __xdata *dst, uint32_t __xdata *src) __reentrant; //*dst = (*src)++ + +//private +void mathPrvSwapDptrR1R0(void) __reentrant; + + + +#endif diff --git a/ap_fw/board/boardCommon.h b/ap_fw/board/boardCommon.h new file mode 100644 index 000000000..f5399cca5 --- /dev/null +++ b/ap_fw/board/boardCommon.h @@ -0,0 +1,20 @@ +#ifndef _BOARD_COMMON_H_ +#define _BOARD_COMMON_H_ + +#include + +#pragma callee_saves powerPortsDownForSleep +void powerPortsDownForSleep(void); + +#pragma callee_saves boardInit +void boardInit(void); + +//early - before most things +#pragma callee_saves boardInitStage2 +void boardInitStage2(void); + +//late, after eeprom +#pragma callee_saves boardInit +__bit boardGetOwnMac(uint8_t __xdata *mac); + +#endif diff --git a/ap_fw/board/boardZBS29common.c b/ap_fw/board/boardZBS29common.c new file mode 100644 index 000000000..bb90de2d2 --- /dev/null +++ b/ap_fw/board/boardZBS29common.c @@ -0,0 +1,165 @@ +#include +#include "printf.h" +#include "screen.h" +#include "board.h" +#include "flash.h" +#include "uart.h" +#include "spi.h" +#include "cpu.h" +#include "wdt.h" +#include "adc.h" + + +void powerPortsDownForSleep(void) +{ + P0FUNC = 0; + P1FUNC = 0; + P2FUNC = 0; + P0DIR = 0; + P0 = 0; + P0PULL = 0; + P1DIR = 0; + P1 = 2; + P1PULL = 0; + P2DIR = 2; + P2 =1; + P2PULL = 0; +} + +void boardInit(void) +{ + wdtOff(); + + //set up pins for spi(0.0,0.1,0.2), UART (0.6) + P0FUNC |= (1 << 0) | (1 << 1) | (1 << 2) | (1 << 6); + P0DIR = (P0DIR &~ ((1 << 0) | (1 << 1) | (1 << 6))) | (1 << 2); + + //pulls for spi in + P0PULL = (P0PULL &~ ((1 << 0) | (1 << 1) | (1 << 6))) | (1 << 2); + + //setup 1.1(eeprom_nCS), 1.2(eink_BS1), 1.7(eink_nCS) + P1FUNC &=~ ((1 << 1) | (1 << 2) | (1 << 7)); + P1DIR &= ~((1 << 1) | (1 << 2) | (1 << 7)); + + //setup 2.0(eink_nRST), 2.1(eink_BUSY), 2.2(eink_D/nC) + P2FUNC &= ~((1 << 0) | (1 << 1) | (1 << 2)); + P2DIR = (P2DIR &~ ((1 << 0) | (1 << 2))) | (1 << 1); + + //raise chip select(s) + P1_1 = 1; + P1_7 = 1; + + //BS1 = low + P1_2 = 0; + + uartInit(); + spiInit(); +} + +void boardInitStage2(void) +{ + //nothing yet +} + +__bit boardGetOwnMac(uint8_t __xdata *mac) +{ + return flashRead(FLASH_INFOPAGE_ADDR + 0x10, mac, 8); +} + +#pragma callee_saves prvUpdateApplierGet +static uint32_t prvUpdateApplierGet(void) __naked +{ + __asm__( + " mov DPTR, #00098$ \n" + " mov A, #00099$ \n" + " clr C \n" + " subb A, DPL \n" + " mov B, A \n" + " ret \n" + + ///actual updater code + "00098$: \n" + + + //copied to last page of flash for updating, called with ints off and eeprom ready to read update + //flashes 63 flash pages, uses xram for buffer. uses combined erase+flash flash op + + " mov _CLKSPEED, #0x21 \n" + " mov _CFGPAGE, #0x04 \n" + " mov R0, #0 \n" + + "00001$: \n" + + //read a page of update + " mov DPTR, #0xe000 \n" + " mov R1, #0x04 \n" + " mov R2, #0x00 \n" + "000010$: \n" + " mov _SPITX, #0x00 \n" + " mov _SPICFG, #0xa0 \n" + "000011$: \n" + " mov A, _SPICFG \n" + " jb A.5, 000011$ \n" + " mov A, _SPIRX \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " djnz R2, 000010$ \n" + " djnz R1, 000010$ \n" + + //flash it + " clr A \n" + " orl _SETTINGS, #0x38 \n" + " mov _FWRTHREE, #0x03 \n" + " mov _FPGNO, R0 \n" + " mov _FWRDSTL, A \n" + " mov _FWRDSTH, A \n" + " mov _FWRLENL, #0xff \n" + " mov _FWRLENH, #0x03 \n" + " mov _FWRSRCL, A \n" + " mov _FWRSRCH, #0xe0 \n" + " orl _TRIGGER, #0x08 \n" + "00050$: \n" + " mov A, _TCON2 \n" + " jnb A.3, 00050$ \n" + " anl _TCON2, #~0x48 \n" + " anl _SETTINGS, #~0x10 \n" + + //go do next page + " inc R0 \n" + " cjne R0, #63, 00001$ \n" + + //done? reset + " mov _WDTCONF, #0x80 \n" + " mov _WDTENA, #0x01 \n" + " mov A, #0xff \n" + " mov _WDTRSTVALH, A \n" + " mov _WDTRSTVALM, A \n" + " mov _WDTRSTVALL, A \n" + "00090$: \n" + " sjmp 00090$ \n" + + "00099$: \n" + ); +} + +void selfUpdate(void) +{ + uint32_t updaterInfo = prvUpdateApplierGet(); + uint8_t __code *src = (uint8_t __code*)updaterInfo; + uint8_t i, len = updaterInfo >> 16; + uint8_t __xdata *dst = mScreenRow; + + for (i = len; i ; i--) + *dst++ = *src++; + + if (!flashWrite(0xfc00, mScreenRow, len, true)) + pr("failed to write updater\n"); + + IEN_EA = 0; //ints off + + __asm__( + " mov dptr, #0xfc00 \n" + " clr a \n" + " jmp @a+dptr \n" + ); +} \ No newline at end of file diff --git a/ap_fw/board/zbs29v033/board.c b/ap_fw/board/zbs29v033/board.c new file mode 100644 index 000000000..15cf44cc0 --- /dev/null +++ b/ap_fw/board/zbs29v033/board.c @@ -0,0 +1 @@ +#include "../boardZBS29common.c" diff --git a/ap_fw/board/zbs29v033/board.h b/ap_fw/board/zbs29v033/board.h new file mode 100644 index 000000000..8951cd17a --- /dev/null +++ b/ap_fw/board/zbs29v033/board.h @@ -0,0 +1,53 @@ +#ifndef _BOARD_H_ +#define _BOARD_H_ + +#include + +#include "spi.h" +#include "uart.h" + +//colors for ui messages +#define UI_MSG_MAGNIFY1 1 +#define UI_MSG_MAGNIFY2 1 +#define UI_MSG_MAGNIFY3 1 +#define UI_MSG_BACK_COLOR 4 +#define UI_MSG_FORE_COLOR_1 0 +#define UI_MSG_FORE_COLOR_2 5 +#define UI_MSG_FORE_COLOR_3 5 +#define UI_BARCODE_VERTICAL + +#define eepromByte spiByte +#define eepromPrvSelect() do { __asm__("nop\nnop\nnop\n"); P1_1 = 0; __asm__("nop\nnop\nnop\n"); } while(0) +#define eepromPrvDeselect() do { __asm__("nop\nnop\nnop\n"); P1_1 = 1; __asm__("nop\nnop\nnop\n"); } while(0) + +//debug uart (enable only when needed, on some boards it inhibits eeprom access) +#define dbgUartOn() +#define dbgUartOff() +#define dbgUartByte uartTx + +//eeprom map +#define EEPROM_SETTINGS_AREA_START (0x01000UL) +#define EEPROM_SETTINGS_AREA_LEN (0x03000UL) +#define EEPROM_UPDATA_AREA_START (0x04000UL) +#define EEPROM_UPDATE_AREA_LEN (0x10000UL) +#define EEPROM_IMG_START (0x14000UL) +#define EEPROM_IMG_EACH (0x04000UL) +//till end of eeprom really. do not put anything after - it will be erased at pairing time!!! +#define EEPROM_PROGRESS_BYTES (128) + +//radio cfg +#define RADIO_FIRST_CHANNEL (11) //2.4-GHz channels start at 11 +#define RADIO_NUM_CHANNELS (1) + +//hw types +#define HW_TYPE_NORMAL HW_TYPE_29_INCH_ZBS_026 +#define HW_TYPE_CYCLING HW_TYPE_29_INCH_ZBS_026_FRAME_MODE + + + + +#include "../boardCommon.h" + + + +#endif diff --git a/ap_fw/board/zbs29v033/make.mk b/ap_fw/board/zbs29v033/make.mk new file mode 100644 index 000000000..d1083181f --- /dev/null +++ b/ap_fw/board/zbs29v033/make.mk @@ -0,0 +1,7 @@ +FLAGS += --code-size 0xfc00 + +SOC = zbs243 + +BARCODE = datamatrix + +# 0xfc00 and not 0x10000 to leave some space for update header and updater in flash \ No newline at end of file diff --git a/ap_fw/board/zbs29v033/screen.c b/ap_fw/board/zbs29v033/screen.c new file mode 100644 index 000000000..5e3976ddc --- /dev/null +++ b/ap_fw/board/zbs29v033/screen.c @@ -0,0 +1,379 @@ +#include +#include "asmUtil.h" +#include "screen.h" +#include "printf.h" +#include "board.h" +#include "timer.h" +#include "sleep.h" +#include "adc.h" +#include "cpu.h" +#include "spi.h" + +uint8_t __xdata mScreenRow[320]; + +static __bit mInited = false, mPartial; +static uint8_t __xdata mPassNo; + +#define SCREEN_CMD_CLOCK_ON 0x80 +#define SCREEN_CMD_CLOCK_OFF 0x01 + +#define SCREEN_CMD_ANALOG_ON 0x40 +#define SCREEN_CMD_ANALOG_OFF 0x02 + +#define SCREEN_CMD_LATCH_TEMPERATURE_VAL 0x20 + +#define SCREEN_CMD_LOAD_LUT 0x10 +#define SCREEN_CMD_USE_MODE_2 0x08 // modified commands 0x10 and 0x04 + +#define SCREEN_CMD_REFRESH 0xC7 + +static const uint8_t __code mColorMap[][6] = + { + // colors are: B, DG, G, LG, W, R + // phase 0 (LUTS: B:W:R:G, purpose: BWR, prepare greys) + { + 1, 1, 1, 1, 0, 0, // lo plane (B) + }, + { + 0, 0, 0, 0, 0, 1, // hi plane (R) + }}; + +#define einkPrvSelect() \ + do \ + { \ + P1_7 = 0; \ + } while (0) + +#define einkPrvDeselect() \ + do \ + { \ + P1_7 = 1; \ + } while (0) +// urx pin +#define einkPrvMarkCommand() \ + do \ + { \ + P2_2 = 0; \ + } while (0) + +#define einkPrvMarkData() \ + do \ + { \ + P2_2 = 1; \ + } while (0) + +#pragma callee_saves einkPrvCmd +static void einkPrvCmd(uint8_t cmd) // sets chip select +{ + einkPrvSelect(); + einkPrvMarkCommand(); + spiByte(cmd); +} + +#pragma callee_saves einkPrvData +static void einkPrvData(uint8_t byte) +{ + einkPrvMarkData(); + spiByte(byte); +} + +#pragma callee_saves einkPrvCmdWithOneByte +static void einkPrvCmdWithOneByte(uint16_t vals) // passing in one u16 is better than two params cause SDCC sucks +{ + einkPrvCmd(vals >> 8); + einkPrvData(vals); + einkPrvDeselect(); +} + +#pragma callee_saves einkPrvWaitWithTimeout +static void einkPrvWaitWithTimeout(uint32_t timeout) +{ + uint32_t __xdata start = timerGet(); + + while (timerGet() - start < timeout) + { + + if (!P2_1) + return; + } + pr("screen timeout %lu ticks\n", timerGet() - start); + //while (1) + ; +} + +#pragma callee_saves einkPrvWaitWithTimeout +static void einkPrvWaitWithTimeoutSleep(uint32_t timeout) +{ + uint8_t tmp_P2FUNC = P2FUNC; + uint8_t tmp_P2DIR = P2DIR; + uint8_t tmp_P2PULL = P2PULL; + uint8_t tmp_P2LVLSEL = P2LVLSEL; + P2FUNC &= 0xfd; + P2DIR |= 2; + P2PULL |= 2; + P2LVLSEL |= 2; + + P2CHSTA &= 0xfd; + P2INTEN |= 2; + P2CHSTA &= 0xfd; + sleepForMsec(timeout); + P2CHSTA &= 0xfd; + P2INTEN &= 0xfd; + + P2FUNC = tmp_P2FUNC; + P2DIR = tmp_P2DIR; + P2PULL = tmp_P2PULL; + P2LVLSEL = tmp_P2LVLSEL; + /*if (!P2_1) + return; + + pr("screen timeout\n"); + while(1);*/ +} + +#pragma callee_saves einkPrvReadByte +static uint8_t einkPrvReadByte(void) +{ + uint8_t val = 0, i; + + P0DIR = (P0DIR & ~(1 << 0)) | (1 << 1); + P0 &= ~(1 << 0); + P0FUNC &= ~((1 << 0) | (1 << 1)); + + P2_2 = 1; + + for (i = 0; i < 8; i++) + { + P0_0 = 1; + __asm__("nop\nnop\nnop\nnop\nnop\n"); + val <<= 1; + if (P0_1) + val++; + P0_0 = 0; + __asm__("nop\nnop\nnop\nnop\nnop\n"); + } + + // set up pins for spi (0.0,0.1,0.2) + P0FUNC |= (1 << 0) | (1 << 1); + + return val; +} + +#pragma callee_saves einkPrvReadStatus +static uint8_t einkPrvReadStatus(void) +{ + uint8_t sta; + einkPrvCmd(0x2f); + + sta = einkPrvReadByte(); + einkPrvDeselect(); + + return sta; +} + +#pragma callee_saves screenPrvStartSubPhase +static void screenPrvStartSubPhase(__bit redSubphase) +{ + einkPrvCmd(0x4e); + einkPrvData(0); + einkPrvDeselect(); + + einkPrvCmd(0x4f); + einkPrvData(0x00); + einkPrvData(0x00); + einkPrvDeselect(); + + einkPrvCmd(redSubphase ? 0x26 : 0x24); + + einkPrvDeselect(); +} + +#pragma callee_saves screenInitIfNeeded +static void screenInitIfNeeded(__bit forPartial) +{ + if (mInited) + return; + + mInited = true; + mPartial = forPartial; + + timerDelay(TIMER_TICKS_PER_SECOND / 1000); + P2_0 = 0; + timerDelay(TIMER_TICKS_PER_SECOND / 1000); + P2_0 = 1; + timerDelay(TIMER_TICKS_PER_SECOND / 1000); + + einkPrvCmd(0x12); // software reset + einkPrvDeselect(); + timerDelay(TIMER_TICKS_PER_SECOND / 1000); + + einkPrvCmdWithOneByte(0x7454); + + einkPrvCmdWithOneByte(0x7e3b); + + einkPrvCmd(0x2b); + einkPrvData(0x04); + einkPrvData(0x63); + einkPrvDeselect(); + + einkPrvCmd(0x0c); // they send 8f 8f 8f 3f + einkPrvData(0x8f); + einkPrvData(0x8f); + einkPrvData(0x8f); + einkPrvData(0x3f); + einkPrvDeselect(); + + einkPrvCmd(0x01); + einkPrvData((SCREEN_HEIGHT - 1) & 0xff); + einkPrvData((SCREEN_HEIGHT - 1) >> 8); + einkPrvData(0x00); + einkPrvDeselect(); + + einkPrvCmdWithOneByte(0x1103); + + einkPrvCmd(0x44); + einkPrvData(0x00); + einkPrvData(SCREEN_WIDTH / 8 - 1); + einkPrvDeselect(); + + einkPrvCmd(0x45); + einkPrvData(0x00); + einkPrvData(0x00); + einkPrvData((SCREEN_HEIGHT - 1) & 0xff); + einkPrvData((SCREEN_HEIGHT - 1) >> 8); + einkPrvDeselect(); + + einkPrvCmdWithOneByte(0x3cc0); // border will be HiZ + + einkPrvCmdWithOneByte(0x1880); // internal temp sensor + + einkPrvCmdWithOneByte(0x2108); + // turn on clock & analog + einkPrvCmdWithOneByte(0x22B1); + einkPrvCmd(0x20); // do action + einkPrvDeselect(); + einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); +} + +#pragma callee_saves screenPrvDraw +static void screenPrvDraw(void) +{ + einkPrvCmdWithOneByte(0x2200 | SCREEN_CMD_REFRESH); + einkPrvCmd(0x20); // do actions + if (1) + { + einkPrvWaitWithTimeoutSleep(1000 * 60UL); + screenSleep(); + } + else + { + einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND * 60UL); + } +} + +__bit screenTxStart(__bit forPartial) +{ + screenInitIfNeeded(forPartial); + mPassNo = 0; + + screenPrvStartSubPhase(false); + + return true; +} + +void screenEndPass(void) +{ + switch (mPassNo) + { + case 0: + screenPrvStartSubPhase(true); + break; + default: + return; + } + mPassNo++; +} + +void screenTxEnd(void) +{ + screenPrvDraw(); + screenShutdown(); +} + +void screenShutdown(void) +{ + if (!mInited) + return; + + mInited = false; + einkPrvCmdWithOneByte(0x1003); // shut down +} + +void screenSleep(void) +{ + P2_0 = 0; + timerDelay(TIMER_TICKS_PER_SECOND / 250); + P2_0 = 1; + timerDelay(TIMER_TICKS_PER_SECOND / 250); + + einkPrvCmd(0x12); // software reset + einkPrvDeselect(); + einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); + + einkPrvCmdWithOneByte(0x1003); // shut down +} + +#pragma callee_saves screenByteTx +void screenByteTx(uint8_t byte) +{ + static uint8_t __xdata prev, step = 0; + + prev <<= 2; + prev |= (mColorMap[mPassNo][byte >> 4] << 1) | mColorMap[mPassNo][byte & 0x0f]; + if (++step == 4) + { + step = 0; + einkPrvSelect(); + einkPrvData(prev); + einkPrvDeselect(); + } +} + +// yes this is here... +uint16_t adcSampleBattery(void) +{ + __bit wasInited = mInited; + uint16_t voltage = 2600; + + if (!mInited) + screenInitIfNeeded(false); + + uint8_t val; + + einkPrvCmdWithOneByte(0x2200 | SCREEN_CMD_CLOCK_ON | SCREEN_CMD_ANALOG_ON); + einkPrvCmd(0x20); // do action + einkPrvDeselect(); + einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); + + for (val = 3; val < 8; val++) + { + + einkPrvCmdWithOneByte(0x1500 + val); + einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); + if (einkPrvReadStatus() & 0x10) + { // set if voltage is less than threshold ( == 1.9 + val / 10) + voltage = 1850 + mathPrvMul8x8(val, 100); + break; + } + } + + einkPrvCmdWithOneByte(0x22B1); + einkPrvCmd(0x20); // do action + einkPrvDeselect(); + einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); + + if (!wasInited) + screenShutdown(); + + return voltage; +} diff --git a/ap_fw/board/zbs29v033/screen.h b/ap_fw/board/zbs29v033/screen.h new file mode 100644 index 000000000..576238d57 --- /dev/null +++ b/ap_fw/board/zbs29v033/screen.h @@ -0,0 +1,48 @@ +#ifndef _SCREEN_H_ +#define _SCREEN_H_ + +#include +#include + + +//i hate globals, but for 8051 this makes life a lot easier, sorry :( +extern uint8_t __xdata mScreenVcom; +extern int8_t __xdata mCurTemperature; + + +#define SCREEN_WIDTH 128 +#define SCREEN_HEIGHT 296 + +#define SCREEN_NUM_GREYS 5 +#define SCREEN_FIRST_GREY_IDX 0 +#define SCREEN_EXTRA_COLOR_INDEX 5 //set to negative if nonexistent +#define SCREEN_TX_BPP 4 //in transit + +#define SCREEN_WIDTH_MM 29 +#define SCREEN_HEIGHT_MM 67 + +#define SCREEN_BYTE_FILL 0x44 //white + +#define SCREEN_TYPE TagScreenEink_BWR_6colors + +#define SCREEN_DATA_PASSES 2 + +void screenShutdown(void); + +void screenTest(void); + +__bit screenTxStart(__bit forPartial); + +void screenEndPass(void); //at end of each pass + +#pragma callee_saves screenByteTx +void screenByteTx(uint8_t byte); +void screenTxEnd(void); + +void screenSleep(void); + +extern uint8_t __xdata mScreenRow[]; //320 bytes used as temp by many on cc where memory is tight + +#endif + + diff --git a/ap_fw/builder.php b/ap_fw/builder.php new file mode 100644 index 000000000..d93607f6d --- /dev/null +++ b/ap_fw/builder.php @@ -0,0 +1,47 @@ +&1 | grep error | grep -v make", $errlist); + if(checkmem()!=$mem){ + $stackdisturbed = true; + echo "Stack size was $mem, is now ".checkmem()." !!!\n"; + bells(); + } else{ + if($stackdisturbed){ + bell(); + echo "stack back to $mem\n"; + $stackdisturbed = false; + } + } + if(count($errlist)){ + echo "-------------------------------\n"; + foreach($errlist as $err){ + echo " - $err\n"; + } + bells(); + } + sleep(5); +} + diff --git a/ap_fw/comms.c b/ap_fw/comms.c new file mode 100644 index 000000000..a48885d08 --- /dev/null +++ b/ap_fw/comms.c @@ -0,0 +1,51 @@ +#define __packed +#include "comms.h" +#include +#include "asmUtil.h" +#include "printf.h" +#include "proto.h" +#include "radio.h" + +static uint8_t __xdata mCommsBuf[127]; + +uint8_t commsGetLastPacketLQI(void) { + return mLastLqi; +} + +int8_t commsGetLastPacketRSSI(void) { + return mLastRSSI; +} + +int8_t commsRxUnencrypted(void __xdata *data) { + uint8_t __xdata *dstData = (uint8_t __xdata *)data; + uint8_t __xdata *__xdata rxedBuf; + int8_t ret = COMMS_RX_ERR_INVALID_PACKET; + + int8_t rxedLen = radioRxDequeuePktGet((void __xdata *__xdata) & rxedBuf, &mLastLqi, &mLastRSSI); + + if (rxedLen < 0) + return COMMS_RX_ERR_NO_PACKETS; + + xMemCopyShort(dstData, rxedBuf, rxedLen); + radioRxDequeuedPktRelease(); + return rxedLen; +} + +bool commsTxUnencrypted(const void __xdata *packetP, uint8_t len) { + const uint8_t __xdata *packet = (const uint8_t __xdata *)packetP; + + if (len > COMMS_MAX_PACKET_SZ) + return false; + memset(mCommsBuf, 0, COMMS_MAX_PACKET_SZ); + xMemCopyShort(mCommsBuf + 1, packet, len); + + mCommsBuf[0] = len + RADIO_PAD_LEN_BY; + + return radioTx(mCommsBuf);; + radioTx(mCommsBuf);; + +} + +bool commsTxNoCpy(const void __xdata *packetp) { + return radioTx(packetp); +} \ No newline at end of file diff --git a/ap_fw/comms.h b/ap_fw/comms.h new file mode 100644 index 000000000..f4e6becd1 --- /dev/null +++ b/ap_fw/comms.h @@ -0,0 +1,30 @@ +#ifndef _COMMS_H_ +#define _COMMS_H_ + +#include + +#define COMMS_MAX_RADIO_WAIT_MSEC 200 + +#define COMMS_IV_SIZE (4) //zeroes except these 4 counter bytes + +#define COMMS_RX_ERR_NO_PACKETS (-1) +#define COMMS_RX_ERR_INVALID_PACKET (-2) +#define COMMS_RX_ERR_MIC_FAIL (-3) + +#define COMMS_MAX_PACKET_SZ (127) + +int8_t commsRxUnencrypted(void __xdata *data); +bool commsTxUnencrypted(const void __xdata *packetP, uint8_t len); +bool commsTxNoCpy(const void __xdata *packetp); + +static uint8_t __xdata mLastLqi; +static int8_t __xdata mLastRSSI; + +#pragma callee_saves commsGetLastPacketLQI +uint8_t commsGetLastPacketLQI(void); + +#pragma callee_saves commsGetLastPacketRSSI +int8_t commsGetLastPacketRSSI(void); + + +#endif diff --git a/ap_fw/cpu/8051/asmUtil.c b/ap_fw/cpu/8051/asmUtil.c new file mode 100644 index 000000000..37e7502ee --- /dev/null +++ b/ap_fw/cpu/8051/asmUtil.c @@ -0,0 +1,1502 @@ +#include "asmUtil.h" +#include "cpu.h" + + +#pragma callee_saves mathPrvU8bitswap +uint16_t mathPrvU16from2xU8(uint8_t hi, uint8_t lo) __reentrant __naked +{ + __asm__( + " mov DPH, DPL \n" + " pop B \n" + " pop A \n" + " pop DPL \n" + " push DPL \n" + " push A \n" + " push B \n" + " ret \n" + ); + (void)hi; + (void)lo; +} + +//a is hi +uint32_t mathPrvU32from4xU8(uint8_t hi, uint8_t midhi, uint8_t midlo, uint8_t lo) __reentrant __naked +{ + __asm__( + " push _R0 \n" + " mov A, #-5 \n" //point to first pushed arg (last param - lo) + " add A, sp \n" + " mov R0, A \n" + " mov A, DPL \n" + " mov DPL, @R0 \n" + " inc R0 \n" + " mov DPH, @R0 \n" + " inc R0 \n" + " mov B, @R0 \n" + " pop _R0 \n" + " ret \n" + ); + (void)hi; + (void)midhi; + (void)midlo; + (void)lo; +} + + +#pragma callee_saves mathPrvU8bitswap +uint8_t mathPrvU8bitswap(uint8_t val) __reentrant __naked +{ + __asm__( + " mov B, #8 \n" + "00003$: \n" + " xch A, DPL \n" + " rrc A \n" + " xch A, DPL \n" + " rlc A \n" + " djnz B, 00003$ \n" + " xch A, DPL \n" + " ret \n" + ); + (void)val; +} + + +#pragma callee_saves mathPrvI16Asr1 +int16_t mathPrvI16Asr1(int16_t val) __reentrant __naked +{ + __asm__( + " mov A, DPH \n" + " mov C, A.7 \n" + " rrc A \n" + " mov DPH, A \n" + " mov A, DPL \n" + " rrc A \n" + " mov DPL, A \n" + " ret \n" + ); + (void)val; +} + +#pragma callee_saves mathPrvMul8x8 +uint16_t mathPrvMul8x8(uint8_t a, uint8_t b) __reentrant __naked +{ + //return expected in DPTR, first param is in DPL, second on stack + __asm__( + //grab param into B + " pop DPH \n\t" + " pop A \n\t" + " pop B \n\t" + " push B \n\t" + " push A \n\t" + " push DPH \n\t" + + //second param into A + " mov A, DPL \n\t" + + //do the deed + " mul AB \n\t" + + //return results + " mov DPL, A \n\t" + " mov DPH, B \n\t" + + " ret \n\t" + ); + (void)a; + (void)b; +} + +#pragma callee_saves mathPrvMul16x8 +uint32_t mathPrvMul16x8(uint16_t a, uint8_t b) __reentrant __naked +{ + //return expected in A:B:DPTR, first param is in DPTR, second on stack + __asm__( + //save r0 + " push _R0 \n\t" + + //get 2nd param into a & r0 + " mov A, SP \n\t" + " add A, #-0x03 \n\t" + " mov R0, A \n\t" + " mov A, @R0 \n\t" + " mov R0, A \n\t" + + //get first param.lo into B + " mov B, DPL \n\t" + + //mul + " mul AB \n\t" + + //lower result byte is ready! + " mov DPL, A \n\t" + + //save upper in r0, get 2nd param into A + " mov A, R0 \n\t" + " mov R0, B \n\t" + + //get first param.hi into B + " mov B, DPH \n\t" + + //mul + " mul AB \n\t" + + //add in the carry from before + " add A, R0 \n\t" + + //produce middle byte + " mov DPH, A \n\t" + + //calc high byte + " mov A, B \n\t" + " addc A, #0 \n\t" + " mov B, A \n\t" + + //set high high byte to 0 (guaranteed) + " clr A \n\t" + + //get out + " pop _R0 \n\t" + " ret \n\t" + ); + (void)a; + (void)b; +} + +#pragma callee_saves mathPrvMul32x8 +uint32_t mathPrvMul32x8(uint32_t a, uint8_t b) __reentrant __naked +{ + //return expected in A:B:DPTR, first param is in DPTR, second on stack + __asm__( + //save r0 + " push _R0 \n\t" + " push _R1 \n\t" + + //save A and B + " push A \n" + " push B \n" + + //get second param into R0 + " mov A, #-6 \n" + " add A, sp \n" + " mov R0, A \n" + " mov _R0, @R0 \n" + + //low + " mov A, DPL \n" + " mov B, R0 \n" + " mul AB \n" + " mov DPL, A \n" + " mov R1, B \n" + + //mid.lo + " mov A, DPH \n" + " mov B, R0 \n" + " mul AB \n" + " add A, R1 \n" + " mov DPH, A \n" + " mov A, B \n" + " addc A, #0 \n" + " mov R1, A \n" + + //mid.hi + " pop A \n" + " mov B, R0 \n" + " mul AB \n" + " add A, R1 \n" + " xch A, B \n" + " addc A, #0 \n" + " mov R1, A \n" + + //hi + " pop A \n" + " push B \n" + " mov B, R0 \n" + " mul AB \n" + " add A, R1 \n" + " pop B \n" + + //get out + " pop _R1 \n\t" + " pop _R0 \n\t" + " ret \n\t" + ); + (void)a; + (void)b; +} + +#pragma callee_saves mathPrvMul16x16 +uint32_t mathPrvMul16x16(uint16_t a, uint16_t b) __reentrant __naked +{ + //return expected in A:B:DPTR, first param is in DPTR, second on stack (low byte was pushed first) + __asm__( + //save r0,r1,r2 + " push _R0 \n\t" + " push _R1 \n\t" + " push _R2 \n\t" + + //get 2nd param into r1:r0 + " mov A, SP \n\t" + " add A, #-0x05 \n\t" + " mov R0, A \n\t" + " mov A, @R0 \n\t" + " mov R1, A \n\t" + " dec R0 \n\t" + " mov A, @R0 \n\t" + " mov R0, A \n\t" + + //mul low bytes, save low result byte + " mov B, DPL \n\t" + " mul AB \n\t" + " push A \n\t" + + //save high byte + " mov R2, B \n\t" + + //mul p2.lo * p1.hi. add in high from the low multiplication. we know this fits in 16 bits! + " mov A, DPH \n\t" + " mov B, R0 \n\t" + " mul AB \n\t" + " add A, R2 \n\t" + " mov R0, A \n\t" //save intermediate's lo in R0 + " mov A, B \n\t" + " addc A, #0 \n\t" + " mov R2, A \n\t" //save intermediate's hi in R2 + + //mul p2.hi * p1.lo, add in intermediate result + " mov A, R1 \n\t" + " mov B, DPL \n\t" + " mul AB \n\t" + " add A, R0 \n\t" //calc intermediate's lo + " push A \n\t" //push it + " mov A, B \n\t" + " addc A, R2 \n\t" + " mov R2, A \n\t" //calc intermediate's hi in R2 + " mov A, #0 \n\t" + " addc A, #0 \n\t" //calc high byte so far + " mov R0, A \n\t" //store in R0 + + //mul high bytes + " mov A, R1 \n\t" + " mov B, DPH \n\t" + " mul AB \n\t" + " add A, R2 \n\t" + " mov R2, A \n\t" //final value for intermediate's high + " mov A, B \n\t" + " addc A, R0 \n\t" //final high value is ready + + //produce the rest of the result bytes + " mov B, R2 \n\t" + " pop DPH \n\t" + " pop DPL \n\t" + + //return + " pop _R2 \n\t" + " pop _R1 \n\t" + " pop _R0 \n\t" + " ret \n\t" + ); + (void)a; + (void)b; +} + +//pushes R0..R2, gets second param into r1:r0 +#pragma callee_saves u64_start +static void u64_start(void) __reentrant __naked +{ + __asm__( + " pop A \n" //get ret addr + " pop B \n" //get ret addr + + " push _R0 \n" + " push _R1 \n" + " push _R2 \n" + + " push B \n" //re-push ret addr + " push A \n" + + " mov a, #0xf8 \n" // DPTR = pushed_arg + " add a, sp \n" + " mov R1, a \n" + " mov a, @R1 \n" + " mov R0, a \n" + " inc R1 \n" + " mov a, @R1 \n" + " mov R1, a \n" + + " ret \n" + ); +} + +//jump to this, do not call it +//does not clobber anything +#pragma callee_saves u64_end +static void u64_end(void) __reentrant __naked +{ + __asm__( + " pop _R2 \n" + " pop _R1 \n" + " pop _R0 \n" + " ret \n" + ); +} + +#pragma callee_saves mathPrvSwapDptrR1R0 +void mathPrvSwapDptrR1R0(void) __reentrant __naked +{ + __asm__( + " xch A, R1 \n\t" + " xch A, DPH \n\t" + " xch A, R1 \n\t" + " xch A, R0 \n\t" + " xch A, DPL \n\t" + " xch A, R0 \n\t" + " ret \n\t" + ); +} + +#pragma callee_saves u64_copy +void u64_copy(uint64_t __xdata *dst, const uint64_t __xdata *src) __reentrant __naked +{ + __asm__( + " lcall _u64_start \n" //get second pointer into R1:R0 + + " mov b, #8 \n" //repeat 8 times: + "00003$: \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " inc dptr \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx @dptr, a \n" + " inc dptr \n" + + " djnz b, 00003$ \n" + + " ljmp _u64_end \n" + ); + (void)dst; + (void)src; +} + +void u64_copyFromCode(uint64_t __xdata *dst, const uint64_t __code *src) __reentrant __naked +{ + __asm__( + " lcall _u64_start \n" //get second pointer into R1:R0 + + " mov b, #8 \n" //repeat 8 times: + "00003$: \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " clr a \n" + " movc a, @a+dptr \n" + " inc dptr \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx @dptr, a \n" + " inc dptr \n" + + " djnz b, 00003$ \n" + + " ljmp _u64_end \n" + ); + (void)dst; + (void)src; +} + +#pragma callee_saves u64_add +void u64_add(uint64_t __xdata *lhsP, const uint64_t __xdata *rhsP) __reentrant __naked +{ + __asm__( + " lcall _u64_start \n" //get second pointer into R1:R0 + " clr C \n" + + " mov b, #8 \n" //repeat 8 times: + "00003$: \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " inc dptr \n" + " mov _R2, a \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " addc a, R2 \n" + " movx @dptr, a \n" + " inc dptr \n" + + " djnz b, 00003$ \n" + + " ljmp _u64_end \n" + ); + (void)lhsP; + (void)rhsP; +} + +#pragma callee_saves u64_and +void u64_and(uint64_t __xdata *lhsP, const uint64_t __xdata *rhsP) __reentrant __naked +{ + __asm__( + " lcall _u64_start \n" //get second pointer into R1:R0 + + " mov b, #8 \n" //repeat 8 times: + "00003$: \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " inc dptr \n" + " mov _R2, a \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " anl a, R2 \n" + " movx @dptr, a \n" + " inc dptr \n" + + " djnz b, 00003$ \n" + + " ljmp _u64_end \n" + ); + (void)lhsP; + (void)rhsP; +} + +#pragma callee_saves u64_sub +void u64_sub(uint64_t __xdata *lhsP, const uint64_t __xdata *rhsP) __reentrant __naked +{ + __asm__( + " lcall _u64_start \n" //get second pointer into R1:R0 + " clr C \n" + + " mov b, #8 \n" //repeat 8 times: + "00003$: \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " inc dptr \n" + " mov _R2, a \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " subb a, R2 \n" + " movx @dptr, a \n" + " inc dptr \n" + + " djnz b, 00003$ \n" + + " ljmp _u64_end \n" + ); + (void)lhsP; + (void)rhsP; +} + +#pragma callee_saves u64_isLt +__bit u64_isLt(const uint64_t __xdata *lhsP, const uint64_t __xdata *rhsP) __reentrant __naked +{ + __asm__( + " lcall _u64_start \n" //get second pointer into R1:R0 + " clr C \n" + + " mov b, #8 \n" //repeat 8 times: + "00003$: \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " inc dptr \n" + " mov _R2, a \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " inc dptr \n" + " subb a, R2 \n" + + " djnz b, 00003$ \n" + + " ljmp _u64_end \n" + ); + (void)lhsP; + (void)rhsP; +} + +#pragma callee_saves u64_isEq +__bit u64_isEq(const uint64_t __xdata *lhsP, const uint64_t __xdata *rhsP) __reentrant __naked +{ + __asm__( + " lcall _u64_start \n" //get second pointer into R1:R0 + + " mov b, #8 \n" //repeat 8 times: + "00003$: \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " inc dptr \n" + " mov _R2, a \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " inc dptr \n" + " xrl a, R2 \n" + " jnz 00004$ \n" + " djnz b, 00003$ \n" + + " setb C \n" + " ljmp _u64_end \n" + + "00004$: \n" + " clr C \n" + " ljmp _u64_end \n" + ); + (void)lhsP; + (void)rhsP; +} + +//c is set, A is what to add to each limb +#pragma callee_saves u64_incdec +static void u64_incdec(uint64_t __xdata *dst) __reentrant __naked +{ + __asm__( + " xch A, R0 \n" + " push A \n" + " mov b, #8 \n" //repeat 8 times: + "00003$: \n" + " movx a, @dptr \n" + " addc a, R0 \n" + " movx @dptr, a \n" + " inc dptr \n" + " djnz b, 00003$ \n" + " pop _R0 \n" + " ret \n" + ); + (void)dst; +} + +#pragma callee_saves u64_inc +void u64_inc(uint64_t __xdata *dst) __reentrant __naked +{ + __asm__( + " setb C \n" + " clr A \n" + " ljmp _u64_incdec \n" + ); + (void)dst; +} + +#pragma callee_saves u64_dec +void u64_dec(uint64_t __xdata *dst) __reentrant __naked +{ + __asm__( + " clr C \n" + " mov A, #0xFF \n" + " ljmp _u64_incdec \n" + ); + (void)dst; +} + + +#pragma callee_saves xMemSet +void xMemSet(void __xdata* mem, uint8_t val, uint16_t num) __reentrant __naked +{ + __asm__( + " push _R0 \n" + " push _R1 \n" + + " mov A, #-6 \n" + " add A, sp \n" + " mov R0, A \n" + " mov A, @R0 \n" //num.lo + " mov B, A \n" + " inc R0 \n" + " mov _R1, @R0 \n" //num.hi + " orl A, R1 \n" //zero check + " jz 00002$ \n" //num is R1:B + " inc R0 \n" + " mov _R0, @R0 \n" //val + "00003$: \n" + " mov A, R0 \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " mov A, #0xff \n" + " add A, B \n" + " mov B, A \n" + " mov A, #0xff \n" + " addc A, R1 \n" + " mov _R1, A \n" + " orl A, B \n" + " jnz 00003$ \n" + "00002$: \n" + " pop _R1 \n" + " pop _R0 \n" + " ret \n" + ); + + (void)mem; + (void)val; + (void)num; +} + +#pragma callee_saves xMemEqual +__bit xMemEqual(const void __xdata* memA, const void __xdata* memB, uint8_t num) __reentrant __naked +{ + __asm__( + " push _R0 \n" + " push _R1 \n" + " push _R2 \n" + + " mov a, #-7 \n" + " add a, sp \n" + " mov R1, a \n" + " mov A, @R1 \n" + " setb C \n" //equal if len is zero + " jz 00004$ \n" + " mov B, A \n" + " inc R1 \n" + " mov _R0, @R1 \n" + " inc R1 \n" + " mov _R1, @R1 \n" + " clr C \n" + "00003$: \n" + " movx a, @dptr \n" + " mov _R2, a \n" + " inc dptr \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " inc dptr \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " xrl a, R2 \n" + " jnz 00004$ \n" + " djnz b, 00003$ \n" + " setb C \n" + "00004$: \n" + " pop _R2 \n" + " pop _R1 \n" + " pop _R0 \n" + " ret \n" + ); + + (void)memA; + (void)memB; + (void)num; +} + +#pragma callee_saves xMemCopy +void xMemCopy(void __xdata* dst, const void __xdata* src, uint16_t num) __reentrant __naked +{ + __asm__( + " push _R0 \n" + " push _R1 \n" + " push _R2 \n" + + " mov A, #-8 \n" + " add A, sp \n" + " mov R1, A \n" + " mov A, @R1 \n" + " mov B, A \n" + " inc R1 \n" + " mov _R2, @R1 \n" //R2:B is length + " orl A, R2 \n" + " jz 00004$ \n" //handle zero length + " inc R1 \n" + " mov _R0, @R1 \n" + " inc R1 \n" + " mov _R1, @R1 \n" //R1:R0 is src + "00003$: \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx A, @DPTR \n" + " inc DPTR \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " mov A, #0xff \n" + " add A, B \n" + " mov B, A \n" + " mov A, #0xff \n" + " addc A, R2 \n" + " mov _R2, A \n" + " orl A, B \n" + " jnz 00003$ \n" + "00004$: \n" + " pop _R2 \n" + " pop _R1 \n" + " pop _R0 \n" + " ret \n" + ); + + (void)dst; + (void)src; + (void)num; +} + +#pragma callee_saves xMemCopyShort +void xMemCopyShort(void __xdata* dst, const void __xdata* src, uint8_t num) __reentrant __naked +{ + __asm__( + " push _R0 \n" + " push _R1 \n" + " push _R2 \n" + + " mov a, #-7 \n" + " add a, sp \n" + " mov R1, a \n" + " mov A, @R1 \n" + " jz 00004$ \n" + " mov B, A \n" + " inc R1 \n" + " mov _R0, @R1 \n" + " inc R1 \n" + " mov _R1, @R1 \n" + "00003$: \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " inc dptr \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx @dptr, a \n" + " inc dptr \n" + " djnz b, 00003$ \n" + "00004$: \n" + " pop _R2 \n" + " pop _R1 \n" + " pop _R0 \n" + " ret \n" + ); + + (void)dst; + (void)src; + (void)num; +} + +#pragma callee_saves xStrLen +uint16_t xStrLen(const char __xdata *str) __reentrant __naked +{ + __asm__( + " push DPH \n" + " push DPL \n" + "00003$: \n" + " movx a, @dptr \n" + " jz 00002$ \n" + " inc dptr \n" + " sjmp 00003$ \n" + "00002$: \n" + " clr C \n" + " mov A, DPL \n" + " pop B \n" + " subb A, B \n" + " mov DPL, A \n" + " mov A, DPH \n" + " pop B \n" + " subb A, B \n" + " mov DPH, A \n" + " ret \n" + ); + + (void)str; +} + +#pragma callee_saves static mathPrvDivMod32x16 +static uint32_t mathPrvDivMod32x16(uint32_t num, uint16_t denom) __reentrant __naked +{ + //PSW.5 determines if to produce quotient (0) or remainder(1) + + __asm__( + " push _R3 \n" + " push _R2 \n" + " push _R1 \n" + " push _R0 \n" + " push A \n" + " push B \n" + " push DPH \n" + " push DPL \n" + " mov R1, sp \n" //R1: point to bottom byte of numerator + + //get denom -> B:R0 + " mov A, #-10 \n" + " add A, sp \n" + " mov R0, A \n" //R0 = &denom.hi + " mov B, @R0 \n" //B = denom.hi + " dec R0 \n" + " mov _R0, @R0 \n" //R0 = denom.lo + + //shift it off (in B:R0), record how many iters we'll need (in DPL), generate proper top 8 bits for result mask (in R3:R2) + " mov _R2, #1 \n" + " mov _R3, #0 \n" + " mov DPL, #17 \n" + "00002$: \n" + " mov C, B.7 \n" + " jc 00003$ \n" + + " clr C \n" + " mov A, R0 \n" + " rlc A \n" + " mov R0, A \n" + " mov A, B \n" + " rlc A \n" + " mov B, A \n" + + " clr C \n" + " mov A, R2 \n" + " rlc A \n" + " mov R2, A \n" + " mov A, R3 \n" + " rlc A \n" + " mov R3, A \n" + + " inc DPL \n" + " sjmp 00002$ \n" + "00003$: \n" + + " clr A \n" + + //push result_mask to stack + " push _R3 \n" + " push _R2 \n" + " push A \n" + " push A \n" + " mov R2, sp \n" //R2: point to bottom byte of result_mask + + //push denom.shifted to stack + " push B \n" + " push _R0 \n" + " push A \n" + " push A \n" + " mov R0, sp \n" //R0: point to bottom byte of denom.shifted + + //push result to stack + " push A \n" + " push A \n" + " push A \n" + " push A \n" + " mov R3, sp \n" //R3: point to bottom byte of result + + //loop-divide + "00088$: \n" + + // check if denom.shifted >= num + " push _R0 \n" + " push _R1 \n" + " clr C \n" + " mov DPH, #4 \n" + "00001$: \n" + " mov B, @R0 \n" + " dec R0 \n" + " mov A, @R1 \n" + " dec R1 \n" + " subb A, B \n" + " djnz DPH, 00001$ \n" + " pop _R1 \n" + " pop _R0 \n" + " jc 00099$ \n" //no? skip this round + + //subtract + " push _R0 \n" + " push _R1 \n" + " clr C \n" + " mov DPH, #4 \n" + "00004$: \n" + " mov B, @R0 \n" + " dec R0 \n" + " mov A, @R1 \n" + " subb A, B \n" + " mov @R1, A \n" + " dec R1 \n" + " djnz DPH, 00004$ \n" + //keep r0 & r1 pushed + + //set bit in result + //r0 & r1 still pushed + " mov _R0, _R2 \n" + " mov _R1, _R3 \n" + " mov DPH, #4 \n" + "00005$: \n" + " mov B, @R0 \n" + " dec R0 \n" + " mov A, @R1 \n" + " orl A, B \n" + " mov @R1, A \n" + " dec R1 \n" + " djnz DPH, 00005$ \n" + " pop _R1 \n" + " pop _R0 \n" + + //set up next iteration + "00099$: \n" + + // shift denom right one (pointer ends up where needed) + " mov A, #0xfc \n" + " add A, R0 \n" + " mov R0, A \n" + " clr C \n" + " mov DPH, #4 \n" + "00006$: \n" + " inc R0 \n" + " mov A, @R0 \n" + " rrc A \n" + " mov @R0, A \n" + " djnz DPH, 00006$ \n" + + // shift result mask right one + " push _R0 \n" + " mov A, #0xfc \n" + " add A, R2 \n" + " mov R0, A \n" + " clr C \n" + " mov DPH, #4 \n" + "00007$: \n" + " inc R0 \n" + " mov A, @R0 \n" + " rrc A \n" + " mov @R0, A \n" + " djnz DPH, 00007$ \n" + " pop _R0 \n" + + //check on loop limit + " djnz DPL, 00088$ \n" + + //we're done. undo the terrible things we've done to the stack and get the result + + //quotient return? + " jb PSW.5, 00009$ \n" + //return quotient + " pop DPL \n" + " pop DPH \n" + " pop B \n" + " pop _R0 \n" + "00009$: \n" + + //now clear up the rest + " mov A, #-12 \n" + " add A, sp \n" + " mov sp, A \n" + + //remainder return? + " jnb PSW.5, 00010$ \n" + //return quotient + " pop DPL \n" + " pop DPH \n" + " pop B \n" + " pop _R0 \n" + "00010$: \n" + + " mov A, R0 \n" + + //pop off regs + " pop _R0 \n" + " pop _R1 \n" + " pop _R2 \n" + " pop _R3 \n" + + " ret \n" + + ); + + (void)num; + (void)denom; +} + +#pragma callee_saves static mathPrvDiv32x16 +uint32_t mathPrvDiv32x16(uint32_t num, uint16_t denom) __reentrant __naked +{ + __asm__( + " clr PSW.5 \n" + " ljmp _mathPrvDivMod32x16 \n" + ); + + (void)num; + (void)denom; +} + +#pragma callee_saves static mathPrvDiv32x16 +uint16_t mathPrvMod32x16(uint32_t num, uint16_t denom) __reentrant __naked +{ + __asm__( + " setb PSW.5 \n" + " ljmp _mathPrvDivMod32x16 \n" + ); + + (void)num; + (void)denom; +} + +#pragma callee_saves mathPrvDiv32x8 +uint32_t mathPrvDiv32x8(uint32_t num, uint8_t denom) __reentrant __naked +{ + __asm__( + " push _R3 \n" + " push _R2 \n" + " push _R1 \n" + " push _R0 \n" + " push A \n" + " push B \n" + " push DPH \n" + " push DPL \n" + " mov R1, sp \n" //R1: point to bottom byte of numerator + + //get denom -> B + " mov A, #0xf6 \n" + " add A, sp \n" + " mov R0, A \n" + " mov B, @R0 \n" + + //shift it off (in B), record how many iters we'll need (in DPL), generate proper top 8 bits for result mask (in A) + " mov A, #1 \n" + " mov DPL, #25 \n" + "00002$: \n" + " mov C, B.7 \n" + " jc 00003$ \n" + " rl A \n" + " xch A, B \n" + " rl A \n" + " xch A, B \n" + " inc DPL \n" + " sjmp 00002$ \n" + "00003$: \n" + + //push result_mask to stack + " push A \n" + " clr A \n" + " push A \n" + " push A \n" + " push A \n" + " mov R2, sp \n" //R2: point to bottom byte of result_mask + + //push denom.shifted to stack + " push B \n" + " push A \n" + " push A \n" + " push A \n" + " mov R0, sp \n" //R0: point to bottom byte of denom.shifted + + //push result to stack + " push A \n" + " push A \n" + " push A \n" + " push A \n" + " mov R3, sp \n" //R3: point to bottom byte of result + + //loop-divide + "00088$: \n" + + // check if denom.shifted >= num + " push _R0 \n" + " push _R1 \n" + " clr C \n" + " mov DPH, #4 \n" + "00001$: \n" + " mov B, @R0 \n" + " dec R0 \n" + " mov A, @R1 \n" + " dec R1 \n" + " subb A, B \n" + " djnz DPH, 00001$ \n" + " pop _R1 \n" + " pop _R0 \n" + " jc 00099$ \n" //no? skip this round + + //subtract + " push _R0 \n" + " push _R1 \n" + " clr C \n" + " mov DPH, #4 \n" + "00004$: \n" + " mov B, @R0 \n" + " dec R0 \n" + " mov A, @R1 \n" + " subb A, B \n" + " mov @R1, A \n" + " dec R1 \n" + " djnz DPH, 00004$ \n" + //keep r0 & r1 pushed + + //set bit in result + //r0 & r1 still pushed + " mov _R0, _R2 \n" + " mov _R1, _R3 \n" + " mov DPH, #4 \n" + "00005$: \n" + " mov B, @R0 \n" + " dec R0 \n" + " mov A, @R1 \n" + " orl A, B \n" + " mov @R1, A \n" + " dec R1 \n" + " djnz DPH, 00005$ \n" + " pop _R1 \n" + " pop _R0 \n" + + //set up next iteration + "00099$: \n" + + // shift denom right one (pointer ends up where needed) + " mov A, #0xfc \n" + " add A, R0 \n" + " mov R0, A \n" + " clr C \n" + " mov DPH, #4 \n" + "00006$: \n" + " inc R0 \n" + " mov A, @R0 \n" + " rrc A \n" + " mov @R0, A \n" + " djnz DPH, 00006$ \n" + + // shift result mask right one + " push _R0 \n" + " mov A, #0xfc \n" + " add A, R2 \n" + " mov R0, A \n" + " clr C \n" + " mov DPH, #4 \n" + "00007$: \n" + " inc R0 \n" + " mov A, @R0 \n" + " rrc A \n" + " mov @R0, A \n" + " djnz DPH, 00007$ \n" + " pop _R0 \n" + + //check on loop limit + " djnz DPL, 00088$ \n" + + //we're done. undo the terrible things we've done to the stack + + //first, get the result + " pop DPL \n" + " pop DPH \n" + " pop B \n" + " pop _R0 \n" + + //now clear up the rest + " mov A, #-12 \n" + " add A, sp \n" + " mov sp, A \n" + + " mov A, R0 \n" + + //pop off regs + " pop _R0 \n" + " pop _R1 \n" + " pop _R2 \n" + " pop _R3 \n" + + " ret \n" + ); + + (void)num; + (void)denom; +} + +#pragma callee_saves mathPrvDivMod16x8 +static uint16_t mathPrvDivMod16x8(uint16_t num, uint8_t denom) __reentrant __naked +{ + __asm__( + " push _R6 \n" + " push _R5 \n" + " push _R4 \n" + " push _R3 \n" + " push _R2 \n" + " push _R1 \n" + " push _R0 \n" + + //get denom -> B + " mov A, #-9 \n" + " add A, sp \n" + " mov R0, A \n" + " mov B, @R0 \n" + + //shift it off (in B), record how many iters we'll need (in R4), generate proper top 8 bits for result mask (in A) + " mov A, #1 \n" + " mov R4, #9 \n" + "00002$: \n" + " mov C, B.7 \n" + " jc 00003$ \n" + " rl A \n" + " xch A, B \n" + " rl A \n" + " xch A, B \n" + " inc R4 \n" + " sjmp 00002$ \n" + "00003$: \n" + + //result mask in R3:R2 \n" + " mov R3, A \n" + " clr A \n" + " mov R2, A \n" + + //quotient in R1:R0 \n" + " mov R1, B \n" + " mov R0, A \n" + + //iter count in B \n" + " mov B, DPL \n" + + //result in R6:R5 + " mov R5, A \n" + " mov R6, A \n" + + //loop-divide + "00088$: \n" + + // check if denom.shifted >= num + " clr C \n" + " mov A, DPL \n" + " subb A, R0 \n" + " mov A, DPH \n" + " subb A, R1 \n" + " jc 00099$ \n" //no? skip this round + + //subtract + " clr C \n" + " mov A, DPL \n" + " subb A, R0 \n" + " mov DPL, A \n" + " mov A, DPH \n" + " subb A, R1 \n" + " mov DPH, A \n" + + //set bit in result + " mov A, R2 \n" + " orl A, R5 \n" + " mov R5, A \n" + " mov A, R3 \n" + " orl A, R6 \n" + " mov R6, A \n" + + //set up next iteration + "00099$: \n" + + // shift denom right one + " clr C \n" + " mov A, R1 \n" + " rrc A \n" + " mov R1, A \n" + " mov A, R0 \n" + " rrc A \n" + " mov R0, A \n" + + // shift result mask right one + " clr C \n" + " mov A, R3 \n" + " rrc A \n" + " mov R3, A \n" + " mov A, R2 \n" + " rrc A \n" + " mov R2, A \n" + + //check on loop limit + " djnz R4, 00088$ \n" + + //we're done - produce the result (it is already in DPT Rif we want modulus) + " jb PSW.5, 00098$ \n" + " mov DPL, R5 \n" + " mov DPH, R6 \n" + "00098$: \n" + + //pop off regs + " pop _R0 \n" + " pop _R1 \n" + " pop _R2 \n" + " pop _R3 \n" + " pop _R4 \n" + " pop _R5 \n" + " pop _R6 \n" + + " ret \n" + ); + + (void)num; + (void)denom; +} + +#pragma callee_saves mathPrvDiv16x8 +uint16_t mathPrvDiv16x8(uint16_t num, uint8_t denom) __reentrant __naked +{ + __asm__( + " clr PSW.5 \n" + " ljmp _mathPrvDivMod16x8 \n" + ); + + (void)num; + (void)denom; +} + +#pragma callee_saves mathPrvMod16x8 +uint8_t mathPrvMod16x8(uint16_t num, uint8_t denom) __reentrant __naked +{ + __asm__( + " setb PSW.5 \n" + " ljmp _mathPrvDivMod16x8 \n" + ); + + (void)num; + (void)denom; +} + +char charsPrvDerefAndIncGenericPtr(const char * __xdata* __xdata str) __naked +{ + __asm__( + " movx A, @DPTR \n" + " push A \n" + " add A, #1 \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " movx A, @DPTR \n" + " push A \n" + " addc A, #0 \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " movx A, @DPTR \n" + " pop _DPH \n" + " pop _DPL \n" + " jz 00001$ \n" //check for xdata, taken if yes + " cjne A, #0x60, 00002$ \n" //check for pdata, taken if no + + //pdata + " push _R0 \n" + " mov R0, DPL \n" + " movx A, @R0 \n" + " pop _R0 \n" + " mov DPL, A \n" + " ret \n" + + "00002$: \n" + " jc 00003$ \n" //check for idata. taken if yes + + //code + " clr A \n" + " movc A, @A+DPTR \n" + " mov DPL, A \n" + " ret \n" + + //xdata + "00001$: \n" + " movx A, @DPTR \n" + " mov DPL, A \n" + " ret \n" + + //idata + "00003$: \n" + " push _R0 \n" + " mov R0, DPL \n" + " mov A, @R0 \n" + " pop _R0 \n" + " mov DPL, A \n" + " ret \n" + ); + + (void)str; +} + +void mathPrvCopyPostinc(uint32_t __xdata *dst, uint32_t __xdata *src) __reentrant __naked +{ + __asm__( + " lcall _u64_start \n" //get second pointer into R1:R0 + + " mov b, #4 \n" //repeat 4 times: + " setb C \n" + "00003$: \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " mov r2, a \n" + " addc a, #0 \n" + " movx @dptr, a \n" + " mov a, r2 \n" + " inc dptr \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx @dptr, a \n" + " inc dptr \n" + + " djnz b, 00003$ \n" + + " ljmp _u64_end \n" + ); + (void)dst; + (void)src; +} + +__bit xMemEqual4(const void __xdata* memA, const void __xdata* memB) __reentrant __naked +{ + __asm__( + " lcall _u64_start \n" //get second pointer into R1:R0 + " mov b, #4 \n" //repeat 4 times: + " clr C \n" + "00003$: \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " mov r2, a \n" + " inc dptr \n" + " lcall _mathPrvSwapDptrR1R0 \n" + " movx a, @dptr \n" + " inc dptr \n" + " xrl a, r2 \n" + " jnz 00004$ \n" + " djnz b, 00003$ \n" + " setb C \n" + "00004$: \n" + + " ljmp _u64_end \n" + ); + + (void)memA; + (void)memB; +} + +__bit u32minusU16(uint32_t __xdata *u32, uint16_t u16) __naked __reentrant +{ + __asm__( + " lcall _u64_start \n" + " clr C \n" + " movx A, @DPTR \n" + " inc DPTR \n" + " subb A, R0 \n" + " movx A, @DPTR \n" + " inc DPTR \n" + " subb A, R1 \n" + " movx A, @DPTR \n" + " inc DPTR \n" + " subb A, #0 \n" + " movx A, @DPTR \n" + " inc DPTR \n" + " subb A, #0 \n" + " ljmp _u64_end \n" + ); + (void)u32; + (void)u16; +} + +__bit u32plusU16(uint32_t __xdata *u32, uint16_t u16) __naked __reentrant +{ + __asm__( + " lcall _u64_start \n" + " clr C \n" + " movx A, @DPTR \n" + " inc DPTR \n" + " addc A, R0 \n" + " movx A, @DPTR \n" + " inc DPTR \n" + " addc A, R1 \n" + " movx A, @DPTR \n" + " inc DPTR \n" + " addc A, #0 \n" + " movx A, @DPTR \n" + " inc DPTR \n" + " addc A, #0 \n" + " ljmp _u64_end \n" + ); + (void)u32; + (void)u16; +} + +uint8_t u32Nonzero(uint32_t __xdata *u32) __naked __reentrant +{ + __asm__( + " mov B, #4 \n" + "00001$: \n" + " movx A, @DPTR \n" + " jnz 00002$ \n" + " inc DPTR \n" + " djnz B, 00001$ \n" + "00002$: \n" + " mov DPL, A \n" + " ret \n" + ); + (void)u32; +} + +__bit i32Negative(uint32_t __xdata *u32) __naked __reentrant +{ + __asm__( + " inc DPTR \n" + " inc DPTR \n" + " inc DPTR \n" + " movx A, @DPTR \n" + " rlc A \n" + " ret \n" + ); + (void)u32; +} + + diff --git a/ap_fw/cpu/8051/cpu.c b/ap_fw/cpu/8051/cpu.c new file mode 100644 index 000000000..254cdec16 --- /dev/null +++ b/ap_fw/cpu/8051/cpu.c @@ -0,0 +1 @@ +//nothing diff --git a/ap_fw/cpu/8051/cpu.h b/ap_fw/cpu/8051/cpu.h new file mode 100644 index 000000000..f15e18f69 --- /dev/null +++ b/ap_fw/cpu/8051/cpu.h @@ -0,0 +1,18 @@ +#ifndef _CPUi_H_ +#define _CPUi_H_ + +#include + + +typedef uint16_t uintptr_near_t; + +#define VERSIONMARKER __at (0x008b) + +#define irqsOn() IEN_EA = 1 + + +#include "soc.h" + + + +#endif diff --git a/ap_fw/cpu/8051/make.mk b/ap_fw/cpu/8051/make.mk new file mode 100644 index 000000000..60d739b08 --- /dev/null +++ b/ap_fw/cpu/8051/make.mk @@ -0,0 +1,8 @@ +FLAGS += -Icpu/8051 + +FLAGS += -mmcs51 --std-sdcc2x --opt-code-size --peep-file cpu/8051/peep.def --fomit-frame-pointer +SOURCES += cpu/8051/asmUtil.c +CC = /usr/local/bin/sdcc + +TARGETS = main.ihx main.bin +OBJFILEEXT = rel diff --git a/ap_fw/cpu/8051/peep.def b/ap_fw/cpu/8051/peep.def new file mode 100644 index 000000000..6d3e47b91 --- /dev/null +++ b/ap_fw/cpu/8051/peep.def @@ -0,0 +1,115 @@ +replace restart { + movx a,@dptr + mov %1,a + inc dptr + movx a,@dptr + mov %2,a + inc dptr + movx a,@dptr + mov %3,a + inc dptr + movx a,@dptr + mov %4,a + clr c + mov a,%1 + subb a,#%5 + mov a,%2 + subb a,#%6 + mov a,%3 + subb a,#%7 + mov a,%4 + subb a,#%8 + DISABLED XXX +} by { + clr c + movx a,@dptr + mov %1,a + subb a,#%5 + inc dptr + movx a,@dptr + mov %2,a + subb a,#%6 + inc dptr + movx a,@dptr + mov %3,a + subb a,#%7 + inc dptr + movx a,@dptr + mov %4,a + subb a,#%8 ;dmitrygr - optimize dumb xdata handling +} if notSame(%1 %2),notSame(%1 %3),notSame(%1 %4),notSame(%2 %3),notSame(%3 %4),notSame(%3 %4) + +replace { + mov b,%1 + mov a,%2 + div ab + mov %3,b + mov b,%1 + mov a,%2 + div ab +} by { + mov b,%1 + mov a,%2 + div ab + mov %3,b ; Peephole dmitrygr.0006 - why divide twice? +} if notSame(%1 %3),notSame(%2 %3) + + +replace { + movx @dptr,a + movx a,@dptr +} by { + movx @dptr,a ; Peephole dmitrygr.0005 - SRSLY, WTF? +} + +replace restart { + mov r%1,%2 + mov r%3,%4 + mov r%5,%6 + mov r%7,%8 + push %9 + push %10 + mov %2,r%1 + mov %4,r%3 + mov %6,r%5 + mov %8,r%7 +} by { + mov r%1,%2 + mov r%3,%4 + mov r%5,%6 + mov r%7,%8 + push %9 + push %10; Peephole dmitrygr.000 - pointless shuffling when passing 32 from return to param +} if notSame(%1 %3),notSame(%1 %5),notSame(%1 %7),notSame(%3 %5),notSame(%3 %7),notSame(%5 %7),notSame(%2 %4),notSame(%2 %6),notSame(%2 %8),notSame(%4 %6),notSame(%4 %8),notSame(%6 %8) + + +replace restart { +%1: + mov dptr,#%2 + movx a,@dptr + mov %3,a + cjne %3,#%4,%1 +} by { + mov dptr,#%2 +%1: + movx a,@dptr + cjne a,#%4,%1 ; Peephole dmitrygr.001 - tighter loop, write extra var just once (i wish we knew if it were dead so we could write it zero times) + mov %3,a +} if labelRefCount(%1 1) + +replace restart { + mov a,#%1 + add a,#0x00 + mov %2,a + mov a,%3 +} by { + mov %2,#%1 + mov a,%3 + clr c ; Peephole dmitrygr.002 - remove pointless A-machinations +} + +replace restart { + add a,#0x00 +} by { + clr c ; Peephole dmitrygr.003 - adding zero is pointless, just clears C +} diff --git a/ap_fw/cpu/8051/printf.c b/ap_fw/cpu/8051/printf.c new file mode 100644 index 000000000..93246c9c6 --- /dev/null +++ b/ap_fw/cpu/8051/printf.c @@ -0,0 +1,795 @@ +#include +#include +#include +#include "printf.h" +#include "zbs243.h" +#include "board.h" + +typedef void (*StrFormatOutputFunc)(uint32_t param /* low byte is data, bits 24..31 is char */) __reentrant; + +static __idata __at (0x00) unsigned char R0; +static __idata __at (0x01) unsigned char R1; +static __idata __at (0x02) unsigned char R2; +static __idata __at (0x03) unsigned char R3; +static __idata __at (0x04) unsigned char R4; +static __idata __at (0x05) unsigned char R5; +static __idata __at (0x06) unsigned char R6; +static __idata __at (0x07) unsigned char R7; + +static uint8_t __xdata mCvtBuf[18]; + + +//callback must be reentrant and callee_saves +#pragma callee_saves prvPrintFormat +void prvPrintFormat(StrFormatOutputFunc formatF, uint16_t formatD, const char __code *fmt, va_list vl) __reentrant __naked +{ + //formatF is in DPTR + //sp[0..-1] is return addr + //sp[-2..-3] is formatD + //sp[-4..-5] is fmt + //sp[-6] is vl + + __asm__ ( + " push _R7 \n" + " push DPH \n" //push formatF + " push DPL \n" + " mov _R7, sp \n" //save place on stack where we stashed it so we can call it easily + " push _R4 \n" + " push _R3 \n" + " push _R2 \n" + " push _R1 \n" + " push _R0 \n" + + " mov A, #-12 \n" + " add A, sp \n" + " mov R0, A \n" + //R0 now points to pushed params, for large values, we see high bytes first + // to get next byte, we need to DECEREMENT R0 + + " mov DPH, @R0 \n" + " dec R0 \n" + " mov DPL, @R0 \n" + " dec R0 \n" + " mov _R0, @R0 \n" + " dec R0 \n" + + //now format string is in DPTR, and R0 points to the top byte of whatever was in the first param + + //main loop: get a byte of the format string + "00001$: \n" + " clr A \n" + " movc A, @A + DPTR \n" + " inc DPTR \n" + //if zero, we're done + " jz 00098$ \n" + //if not '%', print it + " cjne A, #'%', 00097$ \n" + + //we got a percent sign - init state for format processing + " mov R4, #0 \n" //bit flags: + // 0x01 = '*' = pointer provided instead of value (integers only) + // 0x02 = '0' = zero-pad (for numbers only) + // 0x04 = have pad-to length + // 0x08 = long + // 0x10 = long long + // 0x20 = signed print requested. also: need to print a negative (used to reuse hex printing for decimal once converted to bcd) + " mov R2, #0 \n" //padLen + + //loop for format string ingestion + "00002$: \n" + " clr A \n" + " movc A, @A + DPTR \n" + " inc DPTR \n" + //if zero, we're done + " jz 00098$ \n" + //check for percent sign + " cjne A, #'%', 00003$ \n" + //fallthrough to print it and go read next non-format byte + //print a char in A, go read next format byte + "00097$: \n" + " lcall 00060$ \n" + " sjmp 00001$ \n" + + //exit label - placed for easy jumping to + "00098$: \n" + " pop _R0 \n" + " pop _R1 \n" + " pop _R2 \n" + " pop _R3 \n" + " pop _R4 \n" + " pop DPL \n" + " pop DPH \n" + " pop _R7 \n" + " ret \n" + + //continue to process format string - handle %c + "00003$: \n" + " cjne A, #'c', 00004$ \n" + " dec R0 \n" //param is pushed as int (16 bits) + " mov A, @R0 \n" + " dec R0 \n" + " sjmp 00097$ \n" //print and go read next non-format byte + + //continue to process format string - handle %m + "00004$: \n" + " mov R3, A \n" + " orl A, #0x20 \n" + " cjne A, #'m', 00008$ \n" + + //sort out which hexch charset to use + " mov A, R3 \n" + " anl A, #0x20 \n" + " rr A \n" + " mov R1, A \n" + + //go, do + " push DPH \n" + " push DPL \n" + " lcall 00090$ \n" //read the short (__xdata) pointer - >DPTR + " mov R4, #8 \n" //byteSel + "00005$: \n" + " push DPH \n" + " push DPL \n" + " mov A, R4 \n" + " dec A \n" + " add A, DPL \n" + " mov DPL, A \n" + " mov A, DPH \n" + " addc A, #0 \n" + " mov DPH, A \n" + " movx A, @DPTR \n" + " mov R2, A \n" + " swap A \n" + " mov R3, #2 \n" + "00006$: \n" + " anl A, #0x0f \n" + " add A, R1 \n" + " mov DPTR, #00099$ \n" + " movc A, @A + DPTR \n" + " lcall 00060$ \n" + " mov A, R2 \n" + " djnz R3, 00006$ \n" + " pop DPL \n" + " pop DPH \n" + " djnz R4, 00007$ \n" + //done with mac addr + + "00055$: \n" + " pop DPL \n" + " pop DPH \n" + " sjmp 00001$ \n" + //print colon and contimue mac addr printing + "00007$: \n" + " mov A, #':' \n" + " lcall 00060$ \n" + " sjmp 00005$ \n" + + //continue to process format string - handle '*' + "00008$: \n" + " mov A, R3 \n" + " cjne A, #'*', 00009$ \n" + " cjne R2, #0, 00097$ \n" //only valid when no length/padding has been specified yet, else invalid specifier + " mov A, #0x01 \n" //"pointer mode" + "00010$: \n" + " orl A, R4 \n" + " mov R4, A \n" + " sjmp 00002$ \n" //get next format specifier now + + //continue to process format string - handle '0' + "00009$: \n" + " cjne A, #'0', 00011$ \n" + " cjne R2, #0, 00011$ \n" //setting "zero pad" is only valid when pad length is zero + " mov A, #0x06 \n" //"have pad length" | "zero-pad" + " sjmp 00010$ \n" //orr A into R4, get next format specifier now + + //continue to process format string - handle '1'...'9' + "00011$: \n" + " mov R3, A \n" + " add A, #-'0' \n" + " jnc 00012$ \n" //now 0..9 are valid + " add A, #-10 \n" + " jc 00012$ \n" + " add A, #10 \n" //get it back into 1..9 range + " mov R3, A \n" + " mov A, #10 \n" + " mov B, R2 \n" + " mul AB \n" + " add A, R3 \n" + " mov R2, A \n" + " mov A, #0x04 \n" //"have pad length" + " sjmp 00010$ \n" //orr A into R4, get next format specifier now + + //continue to process format string - handle 'l' + "00012$: \n" + " cjne R3, #'l', 00014$ \n" + " mov A, R4 \n" + " anl A, #0x08 \n" + " jz 00013$ \n" //no "long" yet? set that + //have long - set long log + " mov A, #0x10 \n" //"long long" + " sjmp 00010$ \n" //orr A into R4, get next format specifier now + //first 'l' - set long + "00013$: \n" + " mov A, #0x08 \n" //"long" + " sjmp 00010$ \n" //orr A into R4, get next format specifier now + + //continue to process format string - handle 's' + "00014$: \n" + " cjne R3, #'s', 00025$ \n" + " mov A, R4 \n" + " anl A, #0x08 \n" + " push DPH \n" + " push DPL \n" + " jnz 00015$ \n" + " lcall 00091$ \n" //get and resolve generic pointer into DPTR + " sjmp 00016$ \n" + "00015$: \n" //get short pointer into DPTR, record that it is to XRAM + " clr PSW.5 \n" + " clr PSW.1 \n" + " lcall 00090$ \n" + "00016$: \n" //pointer to string now in DPTR + //we have the string pointer in {DPTR,PSW}, let's see if we have padding to do + " mov A, R4 \n" + " anl A, #0x04 \n" + " jnz 00018$ \n" + //print string with no length restrictions + "00017$: \n" + " lcall 00095$ \n" + " jz 00055$ \n" + " lcall 00060$ \n" + " sjmp 00017$ \n" + + //print string with length restrictions and/or padding + "00018$: \n" + " cjne R2, #0, 00019$ \n" //verify reqested len was not zero + " sjmp 00055$ \n" + + "00019$: \n" + " lcall 00095$ \n" + " jz 00020$ \n" + " lcall 00060$ \n" + " djnz R2, 00019$ \n" + //we get here if we ran out of allowable bytes - we're done then + " ljmp 00055$ \n" + + //just a trampoline for range issues + "00035$: \n" + " ljmp 00036$ \n" + + //we need to pad with spaces + "00020$: \n" + " mov A, #' ' \n" + " lcall 00060$ \n" + " djnz R2, 00020$ \n" + " ljmp 00055$ \n" + + //continue to process format string - handle 'x'/'X' + "00025$: \n" + " mov A, R3 \n" + " orl A, #0x20 \n" + " cjne A, #'x', 00035$ \n" + " push DPH \n" + " push DPL \n" + " lcall 00080$ \n" //get pointer to the number in DPTR, length in bytes in B + //save it + + "00070$: \n" + " push DPH \n" + " push DPL \n" + + //sort out how long it would be if printed, first get a pointer to the highest + " mov A, B \n" + " rl A \n" + " mov R1, A \n" + " rr A \n" + " add A, #0xff \n" + " add A, DPL \n" + " mov DPL, A \n" + " mov A, DPH \n" + " addc A, #0x00 \n" + " mov DPH, A \n" + "00026$: \n" + " lcall 00079$ \n" + " anl A, #0xf0 \n" + " jnz 00028$ \n" + " dec R1 \n" + " lcall 00079$ \n" + " jnz 00028$ \n" + " dec R1 \n" + //dec DPTR + " dec DPL \n" + " mov A, DPL \n" + " cjne A, #0xff, 00027$ \n" + " dec DPH \n" + "00027$: \n" + " djnz B, 00026$ \n" + + //we now know how many digits the number is (in R1), except that it has "0" if the number if zero, we cannot have that + "00028$: \n" + " cjne R1, #0, 00029$ \n" + " inc R1 \n" + "00029$: \n" //we now finally have the full length of the digits + + //if the number is negative (happens when we're printing decimals) + // the length of it is one more, also in case of zero-padding, we need to print the minus sign here now + " mov A, R4 \n" + " anl A, #0x20 \n" + " jz 00051$ \n" + " inc R1 \n" //the length is one more + " mov A, R4 \n" + " anl A, #02 \n" //if zero-padding, the negative comes now + " jz 00051$ \n" + " mov A, #'-' \n" + " lcall 00060$ \n" + "00051$: \n" + + //sort out if we need padding at all and if there is space + " mov A, R4 \n" + " anl A, #0x04 \n" + " jz 00031$ \n" //no padding requested + //padding was requested len is in R2 + " mov A, R2 \n" + " clr C \n" + " subb A, R1 \n" + " jc 00031$ \n" //pad-to len < number_len -> no padding needed + " jz 00031$ \n" //pad-to len == number_len -> no padding needed + " mov R2, A \n" + + //sort out which character to use -> DPL + " mov A, R4 \n" //fancy way to create space/zero as needed + " anl A, #0x02 \n" + " swap A \n" + " rr A \n" + " add A, #0x20 \n" + " mov DPL, A \n" + + //pad! + "00030$: \n" + " mov A, DPL \n" + " lcall 00060$ \n" + " djnz R2, 00030$ \n" + "00031$: \n" + + //if the number is negative (happens when we're printing decimals) + // we made the length of it is one more, which we need to undo + // also in case of space-padding, we need to print the minus sign here now + " mov A, R4 \n" + " anl A, #0x20 \n" + " jz 00052$ \n" + " dec R1 \n" //the length is one less than we had increased it to + " mov A, R4 \n" + " anl A, #02 \n" //if space-padding, the negative comes now + " jnz 00052$ \n" + " mov A, #'-' \n" + " lcall 00060$ \n" + "00052$: \n" + + //time to print the number itself + //sort out which hexch charset to use -> R2 + " mov A, R3 \n" + " anl A, #0x20 \n" + " rr A \n" + " mov R2, A \n" + //re-get the number pointer + " pop DPL \n" + " pop DPH \n" + //currently DPTR points to the number low byte, R1 is now many digits we expect to print, R2 is the charset selection, R4 and R3 are free + //let's calculate how many bytes we expect to process -> R4 + " mov A, R1 \n" + " inc A \n" + " clr C \n" + " rrc A \n" + " mov R4, A \n" + //let's repoint DPTR to the first byte we'll print in (remember we print 2 digits per byte) + " dec A \n" + " add A, DPL \n" + " mov DPL, A \n" + " mov A, DPH \n" + " addc A, #0x00 \n" + " mov DPH, A \n" + + //decide if we need to print just a nibble of the high byte or the whole thing. Free up R1 + " mov A, R1 \n" + " anl A, #0x01 \n" + " jz 00032$ \n" + + //we're printing just the low nibble of the first byte - set up for it + " lcall 00079$ \n" + " mov R1, #1 \n" + " sjmp 00033$ \n" + + //print loop + "00032$: \n" + " lcall 00079$ \n" + " mov R1, #2 \n" + " mov R3, A \n" + " swap A \n" + "00033$: \n" + " anl A, #0x0f \n" + " add A, R2 \n" + " push DPH \n" + " push DPL \n" + " mov DPTR, #00099$ \n" + " movc A, @A + DPTR \n" + " pop DPL \n" + " pop DPH \n" + " lcall 00060$ \n" + " mov A, R3 \n" + " djnz R1, 00033$ \n" + + //dec DPTR + " dec DPL \n" + " mov A, DPL \n" + " cjne A, #0xff, 00034$ \n" + " dec DPH \n" + "00034$: \n" + " djnz R4, 00032$ \n" + + //done! + " ljmp 00055$ \n" + + //continue to process format string - handle 'd' + "00036$: \n" + " cjne R3, #'d', 00037$ \n" + " mov A, #0x20 \n" + " orl A, R4 \n" + " mov R4, A \n" + " sjmp 00040$ \n" + + //continue to process format string - handle 'u' + "00037$: \n" + " cjne R3, #'u', 00038$ \n" + " sjmp 00040$ \n" + + //no more format strings exist that we can handle - bail + "00038$: \n" + " ljmp 00001$ \n" + + //handle decimal printing + "00040$: \n" + " push DPH \n" + " push DPL \n" + " lcall 00080$ \n" //get pointer to the number in DPTR, length in bytes in B + " push B \n" + + //copy the number to the double-dabble storage at proper offset (0 for u64, 4 for u32, 6 for u16) + //we do this so that the dabble area always starts at the same place... + " mov A, #8 \n" + " clr C \n" + " subb A, B \n" + " add A, #_mCvtBuf \n" + " mov R1, A \n" + " clr A \n" + " addc A, #(_mCvtBuf >> 8) \n" + " mov R3, A \n" + "00041$: \n" + " lcall 00079$ \n" + " inc DPTR \n" + " lcall 00086$ \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " lcall 00086$ \n" + " djnz B, 00041$ \n" + //leave DPTR pointing to dabble storage, past the number + " lcall 00086$ \n" + + //we now have the top byte of the number in A, good time to check for negatives, if needed + " mov B, A \n" + " mov A, R4 \n" + " anl A, #0x20 \n" + " jz 00050$ \n" //unsigned printing requested + " mov A, B \n" + " anl A, #0x80 \n" + " jnz 00043$ \n" //is negative - we need to invert, 0x20 bit in R1 stays + //positive - 0x20 bit in R1 needs to go + " mov A, R4 \n" + " anl A, #~0x20 \n" + " mov R4, A \n" + " sjmp 00050$ \n" + + //we need to negate the number + // but first we need a pointer to it, and its size + "00043$: \n" + " pop B \n" + " push B \n" + " mov A, #8 \n" + " clr C \n" + " subb A, B \n" + " add A, #_mCvtBuf \n" + " mov DPL, A \n" + " clr A \n" + " addc A, #(_mCvtBuf >> 8) \n" + " mov DPH, A \n" + + //ok, now we are ready to negate it + " clr C \n" + "00049$: \n" + " movx A, @DPTR \n" + " mov R1, A \n" + " clr A \n" + " subb A, R1 \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " djnz B, 00049$ \n" + + //zero out the rest of the storage (10 bytes) + "00050$: \n" + " mov B, #10 \n" + " clr A \n" + "00042$: \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " djnz B, 00042$ \n" + + //calculate number of dabble steps + " pop A \n" + " swap A \n" + " rr A \n" + " mov R3, A \n" + + //do the thing + "00044$: \n" + + //dabble (10 iters for simplicity) + " mov DPTR, #(_mCvtBuf + 8) \n" + " mov B, #10 \n" + "00046$: \n" + " movx A, @DPTR \n" + " mov R1, A \n" + " anl A, #0x0f \n" + " add A,#-0x05 \n" + " mov A, R1 \n" + " jnc 00047$ \n" + " add A, #0x03 \n" + "00047$: \n" + " mov R1, A \n" + " anl A, #0xf0 \n" + " add A,#-0x50 \n" + " mov A, R1 \n" + " jnc 00048$ \n" + " add A, #0x30 \n" + "00048$: \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " djnz B, 00046$ \n" + + //double (18 iters for simplicity) + " mov DPTR, #_mCvtBuf \n" + " clr C \n" + " mov B, #18 \n" + "00045$: \n" + " movx A, @DPTR \n" + " rlc A \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " djnz B, 00045$ \n" + + " djnz R3, 00044$ \n" + + //dabbling is done, print it now using hex routine + " mov DPTR, #(_mCvtBuf + 8) \n" + " mov B, #10 \n" + " clr PSW.5 \n" //it is now for sure in XRAM + " ljmp 00070$ \n" + + //read short pointer from param stack + "00090$: \n" + " mov DPH, @R0 \n" + "00093$: \n" + " dec R0 \n" + " mov DPL, @R0 \n" + " dec R0 \n" + " ret \n" + + //read and increment pointer of the type provided by 00091$ (in {DPTR,PSW}) into A. clobber nothing + "00095$: \n" + " jb PSW.5, 00066$ \n" + " jb PSW.1, 00067$ \n" + //XRAM + " movx A, @DPTR \n" + " inc DPTR \n" + " ret \n" + //CODE + "00066$: \n" + " clr A \n" + " movc A, @A+DPTR \n" + " inc DPTR \n" + " ret \n" + //IRAM + "00067$: \n" + " mov DPH, R0 \n" + " mov R0, DPL \n" + " mov A, @R0 \n" + " mov R0, DPH \n" + " inc DPL \n" + " ret \n" + + //resolve generic pointer on param stack to an pointer in DPTR and flags in PSW.5 and PSW.1 + //PSW.5 will be 0 and PSW.1 will be 0 for XRAM (PDATA goes here too) + //PSW.5 will be 1 and PSW.1 will be 0 for CODE + //PSW.5 will be 0 and PSW.1 will be 1 for IRAM + "00091$: \n" + " clr PSW.5 \n" + " clr PSW.1 \n" + " mov A, @R0 \n" + " dec R0 \n" + " jz 00090$ \n" //0x00: pointer type: xdata + " xrl A, #0x80 \n" + " jz 00094$ \n" //0x80: pointer type: code + " xrl A, #0xc0 \n" + " jz 00092$ \n" //0x40: pointer type: idata + //pdata + " mov DPH, _XPAGE \n" + " sjmp 00093$ \n" + //idata + "00092$: \n" + " setb PSW.1 \n" + " sjmp 00093$ \n" + //code + "00094$: \n" + " setb PSW.5 \n" + " sjmp 00090$ \n" + + //read the pointer of the type that 00080$ returns (in DPTR) into A. clobber nothing + "00079$: \n" + " jnb PSW.5, 00078$ \n" + " push _R0 \n" + " mov R0, DPL \n" + " mov A, @R0 \n" + " pop _R0 \n" + " ret \n" + "00078$: \n" + " movx A, @DPTR \n" + " ret \n" + + //get pointer to a number, might be pushed or might be pointed to, size might vary. return pointer to number's LOW byte in DPTR + "00080$: \n" + " mov A, R4 \n" + " anl A, #0x01 \n" + " jnz 00083$ \n" + //param is itself on stack - now we care about size, but either way, PSW.5 will be 1 + " setb PSW.5 \n" + " mov B, #0 \n" + " mov A, R4 \n" + " anl A, #0x18 \n" + " jz 00081$ \n" + " anl A, #0x10 \n" + " jz 00082$ \n" + //long long (8 bytes) \n" + " setb B.2 \n" + " dec R0 \n" + " dec R0 \n" + " dec R0 \n" + " dec R0 \n" + //long (4 bytes) + "00082$: \n" + " setb B.1 \n" + " dec R0 \n" + " dec R0 \n" + //int (2 bytes) \n" + "00081$: \n" + " setb B.0 \n" + " dec R0 \n" + " mov DPL, R0 \n" + " dec R0 \n" + " inc B \n" + " ret \n" + //pointer it on stack itself, number is in xram, but we still need to provide the length + "00083$: \n" + " clr PSW.5 \n" //mark as "in xram" + " mov A, R4 \n" + " anl A, #0x18 \n" + " jz 00084$ \n" + " anl A, #0x10 \n" + " jz 00085$ \n" + //long long + " mov B, #8 \n" + " ljmp 00090$ \n" + //long + "00085$: \n" + " mov B, #4 \n" + " ljmp 00090$ \n" + //int + "00084$: \n" + " mov B, #2 \n" + " ljmp 00090$ \n" + + //swap R3:R1 <-> DPH:DPL + "00086$: \n" + " xch A, DPH \n" + " xch A, R3 \n" + " xch A, DPH \n" + " xch A, DPL \n" + " xch A, R1 \n" + " xch A, DPL \n" + " ret \n" + + /* putchar func + called via call. char is in A, R7 has pointer to stack as needed + can clobber B, CANNOT clobber DPTR + a mess because...8051 + */ + "00060$: \n" + " push DPH \n" + " push DPL \n" + " push _R1 \n" + " push _R0 \n" + " mov _R0, R7 \n" + " mov DPL, @R0 \n" + " dec R0 \n" + " mov DPH, @R0 \n" //DPTR is now func ptr + " dec R0 \n" + " dec R0 \n" + " dec R0 \n" + " dec R0 \n" + " mov _R1, @R0 \n" + " dec R0 \n" + " mov _R0, @R0 \n" //R1:R0 is now "formatD" + " lcall 00061$ \n" //to set ret addr + " pop _R0 \n" + " pop _R1 \n" + " pop DPL \n" + " pop DPH \n" + " ret \n" + "00061$: \n" + " push DPL \n" + " push DPH \n" + " mov DPL, _R0 \n" + " mov DPH, _R1 \n" + " ret \n" + + "00099$: \n" + " .ascii \"01234567\" \n" + " .ascii \"89ABCDEF\" \n" + " .ascii \"01234567\" \n" + " .ascii \"89abcdef\" \n" + ); + (void)fmt; + (void)vl; + (void)formatF; + (void)formatD; +} + +#pragma callee_saves prPrvPutchar +static void prPrvPutchar(uint32_t data) __reentrant +{ + char ch = data >> 24; + + if (ch == '\n') + dbgUartByte('\r'); + dbgUartByte(ch); +} + +void pr(const char __code *fmt, ...) __reentrant +{ + va_list vl; + + va_start(vl, fmt); + dbgUartOn(); + prvPrintFormat(prPrvPutchar, 0, fmt, vl); + dbgUartOff(); + va_end(vl); +} + +#pragma callee_saves prPrvPutS +static void prPrvPutS(uint32_t data) __reentrant +{ + char __xdata * __idata *strPP = (char __xdata * __idata *)data; + char ch = data >> 24; + + *(*strPP)++ = ch; +} + +void spr(char __xdata* out, const char __code *fmt, ...) __reentrant +{ + char __xdata* outStart = out; + + va_list vl; + + va_start(vl, fmt); + prvPrintFormat(prPrvPutS, (uint16_t)&out, fmt, vl); + va_end(vl); + + *out = 0; +} + diff --git a/ap_fw/cpu/8051/random.c b/ap_fw/cpu/8051/random.c new file mode 100644 index 000000000..63e7be625 --- /dev/null +++ b/ap_fw/cpu/8051/random.c @@ -0,0 +1,261 @@ +#include "cpu.h" + + +struct RngState { + uint64_t a, b; + uint32_t c; +}; + +static struct RngState mState = {0, }; + +void rndSeed(uint8_t seedA, uint8_t seedB) +{ + mState.a = seedA; + mState.b = seedB; +} + +#pragma callee_saves rndPrvGen32 +static uint32_t rndPrvGen32(struct RngState __xdata *state) __naked +{ + // //xorshift128p with a small midification ( " retval ^= ++ c" ) + // + // state->a ^= state->a << 23; + // state->a ^= state->a >> 17; + // state->a ^= state->b; + // t = state->a; + // state->a = state->b; + // state->b >>= 26 + // state->b = t ^ state->b; + // state->c++; + // + // return (state->a + state->b) >> 32 + state->c; + + __asm__( + + " push _R0 \n" + + //state->a ^= state->a << 23 + " mov A, #5 \n" //point to a[5] + " lcall 00091$ \n" + " movx A, @DPTR \n" + " mov PSW.5, A.0 \n" + " mov B, #5 \n" + " mov A, #-1 \n" + "00001$: \n" + " lcall 00089$ \n" + " movx A, @DPTR \n" + " mov C, PSW.5 \n" + " rrc A \n" + " mov PSW.5, C \n" + " inc DPTR \n" + " inc DPTR \n" + " inc DPTR \n" + " mov _R0, A \n" + " movx A, @DPTR \n" + " xrl A, R0 \n" + " movx @DPTR, A \n" + " mov A, #-4 \n" + " djnz B, 00001$ \n" + + //repoint DPTR to state->a.8 + " mov A, #5 \n" + " lcall 00091$ \n" + + //state->a ^= state->a >> 17 + + " clr PSW.5 \n" + " mov B, #6 \n" + "00002$: \n" + " mov A, #-1 \n" + " lcall 00089$ \n" + " movx A, @DPTR \n" + " mov C, PSW.5 \n" + " rrc A \n" + " mov PSW.5, C \n" + " push A \n" + " djnz B, 00002$ \n" + + " mov A, #-2 \n" + " lcall 00089$ \n" + + " mov B, #6 \n" + "00003$: \n" + " movx A, @DPTR \n" + " pop _R0 \n" + " xrl A, R0 \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " djnz B, 00003$ \n" + + " inc DPTR \n" //point to B + " inc DPTR \n" + + // pushed_t = state->a ^ state->b //last pushe di nhigh byte + // state->a = state->b; + + " mov B, #8 \n" + "00004$: \n" + " movx A, @DPTR \n" + " mov _R0, A \n" //r0 = b[i] + " mov A, #-8 \n" + " lcall 00089$ \n" + " movx A, @DPTR \n" //a = a[i] + " xrl A, R0 \n" //a = a[i] ^ b[i] + " push A \n" + " mov A, R0 \n" + " movx @DPTR, A \n" + " mov A, #9 \n" + " lcall 00091$ \n" + " djnz B, 00004$ \n" + + //repoint DPTR to state->b.3 + " mov A, #-5 \n" + " lcall 00089$ \n" + + //state->b >>= 24 (top 3 bytes are garbage) + " mov B, #5 \n" + "00005$: \n" + " movx A, @DPTR \n" + " mov _R0, A \n" + " mov A, #-3 \n" + " lcall 00089$ \n" + " mov A, R0 \n" + " movx @DPTR, A \n" + " mov A, #4 \n" + " lcall 00091$ \n" + " djnz B, 00005$ \n" + + //state->b >>= 2 + " mov A, #-4 \n" + " lcall 00089$ \n" + + " mov B, #2 \n" + "00006$: \n" + " mov _R0, #5 \n" + " clr PSW.5 \n" + "00007$: \n" + " movx A, @DPTR \n" + " mov C, PSW.5 \n" + " rrc A \n" + " mov PSW.5, C \n" + " movx @DPTR, A \n" + " mov A, #-1 \n" + " lcall 00089$ \n" + " djnz _R0, 00007$ \n" + " mov A, #5 \n" + " lcall 00091$ \n" + " djnz B, 00006$ \n" + + //reset DPTR to end of state->b + " mov A, #3 \n" + " lcall 00091$ \n" + + //state->b = t ^ state->b + " mov B, #3 \n" + "00008$: \n" + " pop A \n" + " movx @DPTR, A \n" + " mov A, #-1 \n" + " lcall 00089$ \n" + " djnz B, 00008$ \n" + + " mov B, #5 \n" + "00009$: \n" + " movx A, @DPTR \n" + " pop _R0 \n" + " xrl A, R0 \n" + " movx @DPTR, A \n" + " mov A, #-1 \n" + " lcall 00089$ \n" + " djnz B, 00009$ \n" + + " mov A, #9 \n" + " lcall 00091$ \n" + + //state->c++ + " mov B, #4 \n" + " setb C \n" + "00010$: \n" + " movx A, @DPTR \n" + " addc A, #0 \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " djnz B, 00010$ \n" + + " mov A, #-16 \n" //point to top 32 bits of A + " lcall 00089$ \n" + + // push (state->a + state->b) >> 32 ^ state->c + " mov B, #4 \n" + " clr PSW.5 \n" + "00011$: \n" + " movx A, @DPTR \n" + " mov _R0, A \n" + " mov A, #8 \n" + " lcall 00091$ \n" + " movx A, @DPTR \n" + " mov C, PSW.5 \n" + " addc A, R0 \n" + " mov PSW.5, C \n" + " mov _R0, A \n" + " mov A, #4 \n" + " lcall 00091$ \n" + " movx A, @DPTR \n" + " xrl A, R0 \n" + " push A \n" + " mov A, #-11 \n" + " lcall 00089$ \n" + " djnz B, 00011$ \n" + + //pop result (pop it large to small) + " pop A \n" + " pop B \n" + " pop DPH \n" + " pop DPL \n" + + " pop _R0 \n" + " ret \n" + + //sub from DPTR + "00089$: \n" + " add A, DPL \n" + " mov DPL, A \n" + " mov A, #0xff \n" + " addc A, DPH \n" + " mov DPH, A \n" + " ret \n" + + //add to DPTR + "00091$: \n" + " add A, DPL \n" + " mov DPL, A \n" + " clr A \n" + " addc A, DPH \n" + " mov DPH, A \n" + " ret \n" + ); + + (void)state; +} + +uint32_t rndGen32(void) __naked +{ + __asm__( + " mov DPTR, #_mState \n" + " ljmp _rndPrvGen32 \n" + ); +} + +uint8_t rndGen8(void) __naked +{ + __asm__( + " lcall _rndGen32 \n" + " rr A \n" + " xrl A, B \n" + " rr A \n" + " xrl A, DPH \n" + " rr A \n" + " xrl DPL, A \n" + " ret \n" + ); +} diff --git a/ap_fw/eeprom.c b/ap_fw/eeprom.c new file mode 100644 index 000000000..8d7e9d83d --- /dev/null +++ b/ap_fw/eeprom.c @@ -0,0 +1,322 @@ +#include "asmUtil.h" +#include "screen.h" +#include "eeprom.h" +#include "printf.h" +#include "board.h" +#include "cpu.h" + +static uint32_t __xdata mEepromSize; +static uint8_t __xdata mOpcodeErz4K = 0, mOpcodeErz32K = 0, mOpcodeErz64K = 0; +uint8_t mScreenRow[320]; + +uint32_t eepromGetSize(void) +{ + return mEepromSize; +} + +void eepromReadStart(uint32_t addr) __reentrant +{ + eepromPrvSelect(); + eepromByte(0x03); + eepromByte(addr >> 16); + eepromByte(addr >> 8); + eepromByte(addr & 0xff); +} + +void eepromRead(uint32_t addr, void __xdata *dstP, uint16_t len) __reentrant +{ + uint8_t __xdata *dst = (uint8_t __xdata*)dstP; + + eepromPrvSelect(); + eepromByte(0x03); + eepromByte(addr >> 16); + eepromByte(addr >> 8); + eepromByte(addr & 0xff); + + while (len--) + *dst++ = eepromByte(0); + eepromPrvDeselect(); +} + +static void eepromPrvSimpleCmd(uint8_t cmd) +{ + eepromPrvSelect(); + eepromByte(cmd); + eepromPrvDeselect(); +} + +static bool eepromPrvBusyWait(void) +{ + uint8_t val; + + eepromPrvSelect(); + eepromByte(0x05); + while ((val = eepromByte(0x00)) & 1); + eepromPrvDeselect(); + + return true; +} + +static bool eepromWriteLL(uint32_t addr, const void __xdata *srcP, uint16_t len) +{ + const uint8_t __xdata *src = (const uint8_t __xdata*)srcP; + + eepromPrvSimpleCmd(0x06); + + eepromPrvSelect(); + eepromByte(0x02); + eepromByte(addr >> 16); + eepromByte(addr >> 8); + eepromByte(addr & 0xff); + + while (len--) + eepromByte(*src++); + eepromPrvDeselect(); + + return eepromPrvBusyWait(); +} + +void eepromDeepPowerDown(void) +{ + eepromPrvSimpleCmd(0xb9); +} + +static void eepromPrvWakeFromPowerdown(void) +{ + eepromPrvSimpleCmd(0xab); +} + +#pragma callee_saves eepromPrvSfdpRead +static void eepromPrvSfdpRead(uint16_t ofst, uint8_t __xdata *dst, uint8_t len) +{ + eepromPrvSelect(); + eepromByte(0x5a); //cmd + eepromByte(0); //addr + eepromByte(ofst >> 8); + eepromByte(ofst); + eepromByte(0x00); //dummy + while(len--) + *dst++ = eepromByte(0); + eepromPrvDeselect(); +} + +__bit eepromInit(void) +{ + uint8_t __xdata buf[8]; + uint8_t i, nParamHdrs; + + eepromPrvWakeFromPowerdown(); + + //process SFDP + + eepromPrvSfdpRead(0, buf, 8); + if (buf[0] != 0x53 || buf[1] != 0x46 || buf[2] != 0x44 || buf[3] != 0x50 || buf[7] != 0xff) { + pr("SFDP: header not found\n"); + + __bit valid = false; + + //try manual ID for chips we know of + eepromPrvSelect(); + eepromByte(0x90); + eepromByte(0x00); + eepromByte(0x00); + eepromByte(0x00); + if (eepromByte(0) == 0xc2) { //old macronix chips + valid = true; + mOpcodeErz4K = 0x20; + switch (eepromByte(0)) { + case 0x05: //MX25V512 + mEepromSize = 0x00010000ul; + break; + + case 0x12: //MX25V4005 + mEepromSize = 0x00080000ul; + break; + + default: + valid = false; + break; + } + } + eepromPrvDeselect(); + + return valid; + } + if (buf[5] != 0x01) { + pr("SFDP: version wrong: %u.%d\n", buf[5], buf[4]); + return false; + } + nParamHdrs = buf[6]; + if (nParamHdrs == 0xff) //that case is very unlikely and we just do not care + nParamHdrs--; + + //now we need to find the JEDEC parameter table header + for (i = 0; i <= nParamHdrs; i++) { + + eepromPrvSfdpRead(mathPrvMul8x8(i, 8) + 8, buf, 8); + if (buf[0] == 0x00 && buf[2] == 0x01 && buf[3] >= 9) { + + uint8_t j; + + eepromPrvSfdpRead(*(uint16_t __xdata*)(buf + 4), mScreenRow, 9 * 4); + if ((mScreenRow[0] & 3) != 1) { + pr("SFDP: no 4K ERZ\n"); + break; + } + if (!(mScreenRow[0] & 0x04)) { + pr("SFDP: no large write buf\n"); + break; + } + if ((mScreenRow[2] & 0x06)) { + pr("SFDP: addr.len != 3\n"); + break; + } + + if (!mScreenRow[1] || mScreenRow[1] == 0xff) { + pr("SFDP: 4K ERZ opcode invalid\n"); + break; + } + mOpcodeErz4K = mScreenRow[1]; + + if (mScreenRow[7] & 0x80) { + + pr("SFDP: device too big\n"); + break; + } + else { + + uint8_t t; + + if (t = mScreenRow[7]) + mEepromSize = 0x00200000UL; + else if (t = mScreenRow[6]) + mEepromSize = 0x00002000UL; + else if (t = mScreenRow[5]) + mEepromSize = 0x00000020UL; + else { + pr("SFDP: device so small?!\n"); + break; + } + + while (t) { + mEepromSize <<= 1; + t >>= 1; + } + } + + //get erase opcodes + for (j = 0x1c; j < 0x24; j += 2) { + uint8_t instr = mScreenRow[j + 1]; + + if (!instr || instr == 0xff) + continue; + + switch (mScreenRow[j]) { + case 0x0c: + if (mOpcodeErz4K != instr) { + pr("4K ERZ opcode disagreement\n"); + return false; + } + break; + + case 0x0f: //32K erase + mOpcodeErz32K = instr; + break; + + case 0x10: //64K erase + mOpcodeErz64K = instr; + break; + } + } + + /* + pr("EEPROM accepted\n"); + pr(" ERZ opcodes: \n"); + if (mOpcodeErz4K) + pr(" 4K: %02xh\n", mOpcodeErz4K); + if (mOpcodeErz32K) + pr(" 32K: %02xh\n", mOpcodeErz32K); + if (mOpcodeErz64K) + pr(" 64K: %02xh\n", mOpcodeErz64K); + pr(" Size: 0x%*08lx\n", (uint16_t)&mEepromSize); + */ + return true; + } + } + + pr("SFDP: no JEDEC table of expected version found\n"); + return false; +} + +bool eepromWrite(uint32_t addr, const void __xdata *srcP, uint16_t len) __reentrant +{ + const uint8_t __xdata *src = (const uint8_t __xdata*)srcP; + + while (len) { + + uint16_t lenNow = EEPROM_WRITE_PAGE_SZ - (addr & (EEPROM_WRITE_PAGE_SZ - 1)); + + if (lenNow > len) + lenNow = len; + + if (!eepromWriteLL(addr, src, lenNow)) + return false; + + addr += lenNow; + src += lenNow; + len -= lenNow; + } + return true; +} + +bool eepromErase(uint32_t addr, uint16_t nSec) __reentrant +{ + uint8_t now; + + if (((uint16_t)addr) & 0x0fff) + return false; + + for (;nSec; nSec -= now) { + + eepromPrvSimpleCmd(0x06); + eepromPrvSelect(); + + if (nSec >= 16 && !(uint16_t)addr && mOpcodeErz64K) { //erase 64K + + eepromByte(mOpcodeErz64K); + now = 16; + } + else if (nSec >= 8 && !(((uint16_t)addr) & 0x7fff) && mOpcodeErz32K) { //erase 32K + + eepromByte(mOpcodeErz32K); + now = 8; + } + else { //erase 4K + + eepromByte(mOpcodeErz4K); + now = 1; + } + + eepromByte(addr >> 16); + eepromByte(addr >> 8); + eepromByte(addr); + eepromPrvDeselect(); + + if (!eepromPrvBusyWait()) + return false; + + addr += mathPrvMul16x8(EEPROM_ERZ_SECTOR_SZ, now); + } + + return true; +} + +void eepromOtpModeEnter(void) +{ + eepromPrvSimpleCmd(0xb1); +} + +void eepromOtpModeExit(void) +{ + eepromPrvSimpleCmd(0xc1); +} \ No newline at end of file diff --git a/ap_fw/eeprom.h b/ap_fw/eeprom.h new file mode 100644 index 000000000..9d7502745 --- /dev/null +++ b/ap_fw/eeprom.h @@ -0,0 +1,54 @@ +#ifndef _EEPROM_H_ +#define _EEPROM_H_ + +#include +#include + +#define EEPROM_WRITE_PAGE_SZ 256 //max write size & alignment +#define EEPROM_ERZ_SECTOR_SZ 4096 //erase size and alignment + +//device has 256 sectors, so eepromErase() cannot erase thw whole device...i can live with that + +__bit eepromInit(void); +void eepromOtpModeEnter(void); +void eepromOtpModeExit(void); + +#pragma callee_saves eepromRead +void eepromRead(uint32_t addr, void __xdata *dst, uint16_t len) __reentrant; + +#pragma callee_saves eepromWrite +bool eepromWrite(uint32_t addr, const void __xdata *src, uint16_t len) __reentrant; + +#pragma callee_saves eepromErase +bool eepromErase(uint32_t addr, uint16_t numSectors) __reentrant; + +void eepromDeepPowerDown(void); + +#pragma callee_saves eepromGetSize +uint32_t eepromGetSize(void); + +//this is for firmware update use +void eepromReadStart(uint32_t addr) __reentrant; + +//structures +#define EEPROM_IMG_INPROGRESS (0x7fffffffUL) +#define EEPROM_IMG_VALID (0x494d4722UL) + +#include "board.h" + +#define EEPROM_PIECE_SZ (88) +struct EepromImageHeader { //each image space is 0x17000 bytes, we have space for ten of them + uint64_t version; + uint32_t validMarker; + uint32_t size; + uint32_t rfu[8]; //zero-filled for now + uint8_t piecesMissing[EEPROM_PROGRESS_BYTES]; //each bit represents a EEPROM_PIECE_SZ-byte piece + uint32_t id; + + //image data here + //we pre-erase so progress can be calculated by finding the first non-0xff byte +}; + + + +#endif diff --git a/ap_fw/main.c b/ap_fw/main.c new file mode 100644 index 000000000..105394c80 --- /dev/null +++ b/ap_fw/main.c @@ -0,0 +1,891 @@ +#define __packed +#include +#include +#include +#include +#include + +#include "asmUtil.h" +#include "board.h" +#include "comms.h" +#include "cpu.h" +#include "eeprom.h" +#include "printf.h" +#include "proto.h" +#include "radio.h" +#include "timer.h" +#include "wdt.h" + +struct MacFrameFromMaster { + struct MacFcs fcs; + uint8_t seq; + uint16_t pan; + uint8_t dst[8]; + uint16_t from; +} __packed; + +struct MacFrameNormal { + struct MacFcs fcs; + uint8_t seq; + uint16_t pan; + uint8_t dst[8]; + uint8_t src[8]; +} __packed; + +struct MacFrameBcast { + struct MacFcs fcs; + uint8_t seq; + uint16_t dstPan; + uint16_t dstAddr; + uint16_t srcPan; + uint8_t src[8]; +} __packed; + +#define PKT_TIMING_REQ 0xE0 +#define PKT_TIMING_RESPONSE 0xE1 +#define PKT_AVAIL_DATA_REQ 0xE5 +#define PKT_AVAIL_DATA_INFO 0xE6 +#define PKT_BLOCK_PARTIAL_REQUEST 0xE7 +#define PKT_BLOCK_REQUEST_ACK 0xE9 +#define PKT_BLOCK_REQUEST 0xE4 +#define PKT_BLOCK_PART 0xE8 +#define PKT_XFER_COMPLETE 0xEA +#define PKT_XFER_COMPLETE_ACK 0xEB +#define PKT_SYNC_BURST 0xEF + +struct timingResponse { + uint8_t checksum; + uint32_t burstInterval; // time between burst-start + uint8_t burstLength; // in packets; due to use of sequence field, limited to a 256-packet burst + uint16_t burstLengthMs; // burst length in ms + uint32_t timerValue; // current timer value (used to sync up other RC timers/oscillators) + uint32_t burstIntervalRemaining; // time until the next sync burst + uint8_t dataAvailable; +} __packed; + +struct AvailDataReq { + uint8_t checksum; + uint8_t lastPacketLQI; // zero if not reported/not supported to be reported + int8_t lastPacketRSSI; // zero if not reported/not supported to be reported + uint8_t temperature; // zero if not reported/not supported to be reported. else, this minus CHECKIN_TEMP_OFFSET is temp in degrees C + uint16_t batteryMv; + uint8_t softVer; + uint8_t hwType; + uint8_t protoVer; +} __packed; + +#define DATATYPE_NOUPDATE 0 +#define DATATYPE_IMG 1 +#define DATATYPE_IMGRAW 2 +#define DATATYPE_UPDATE 3 + +struct AvailDataInfo { + uint8_t checksum; + uint64_t dataVer; + uint32_t dataSize; + uint8_t dataType; +} __packed; + +struct blockPart { + uint8_t checksum; + uint8_t blockId; + uint8_t blockPart; + uint8_t data[]; +} __packed; + +struct blockData { + uint16_t size; + uint16_t checksum; + uint8_t data[]; +} __packed; + +struct pendingData { + struct AvailDataInfo availdatainfo; + uint8_t attemptsLeft; + uint8_t targetMac[8]; + uint8_t includedThisBurst : 1; +} __packed; + +struct burstMacData { + uint16_t offset; + uint8_t targetMac[8]; +} __packed; + +#define BLOCK_PART_DATA_SIZE 99 +#define BLOCK_MAX_PARTS 42 +#define BLOCK_DATA_SIZE 4096 +#define BLOCK_XFER_BUFFER_SIZE BLOCK_DATA_SIZE + sizeof(struct blockData) +// #define BLOCK_XFER_BUFFER_SIZE 4096 + 4 +#define BLOCK_REQ_PARTS_BYTES 6 // BLOCK_MAX_PARTS / 8 + 1 +#define MAX_MACS_PER_SYNC 2 +#define MAX_PENDING_MACS 10 +#define SYNC_BURST_INTERVAL 30UL + +struct pendingData __xdata pendingDataArr[MAX_PENDING_MACS]; + +struct blockRequest { + uint8_t checksum; + uint64_t ver; + uint8_t blockId; + uint8_t type; + uint8_t requestedParts[BLOCK_REQ_PARTS_BYTES]; +} __packed; + +struct blockRequestAck { + uint8_t checksum; + uint16_t blockSizeMs; + uint16_t pleaseWaitMs; + uint8_t cancelXfer; +} __packed; + +struct espPendingData { + uint8_t checksum; + struct pendingData pending; +} __packed; + +struct espBlockRequest { + uint8_t checksum; + uint64_t ver; + uint8_t blockId; +} __packed; + +struct espXferComplete { + uint8_t checksum; + uint8_t src[8]; +} __packed; + +struct espJoinNetwork { + uint8_t checksum; + uint8_t src[8]; +} __packed; + + +// used to transmit AP update information - flashing the firmware +struct espSaveUpdateBlock { + uint8_t checksum; + uint8_t blockId; + uint16_t blockChecksum; +} __packed; + +#define TIMER_TICKS_PER_MS 1333UL +uint16_t __xdata version = 0x0015; +#define RAW_PKT_PADDING 2 + +static uint8_t __xdata mRxBuf[COMMS_MAX_PACKET_SZ]; + +uint8_t __xdata radiotxbuffer[128]; +uint8_t __xdata radiorxbuffer[128]; + +uint32_t __xdata burstLengthMs; // stores how fast we were able to send a syncburst +uint8_t __xdata mSelfMac[8]; + +// serial stuff +uint8_t __xdata cmdbuffer[4]; +uint8_t __xdata RXState = 0; +uint8_t __xdata serialbuffer[48]; +uint8_t *__xdata serialbufferp; +uint8_t __xdata bytesRemain = 0; + +static uint32_t __xdata burstIntervalTimer; +bool __xdata blockRequestInProgress = false; // set if we get a CRC error, or should do a full request for other reasons + +struct blockRequest __xdata requestedData = {0}; +uint8_t __xdata dstMac[8]; +uint16_t __xdata dstPan; +static uint32_t __xdata blockStartTimer = 0; + +uint8_t seq = 0; // holds current sequence number for transmission + +uint8_t __xdata blockbuffer[BLOCK_XFER_BUFFER_SIZE]; + +#define SYNC_BURST_LENGTH 142 //(about 250ms) + +void sendXferCompleteAck(uint8_t *dst); + +// tools +void addCRC(void *p, uint8_t len) { + uint8_t total = 0; + for (uint8_t c = 1; c < len; c++) { + total += ((uint8_t *)p)[c]; + } + ((uint8_t *)p)[0] = total; + // pr("%d",total); +} +bool checkCRC(void *p, uint8_t len) { + uint8_t total = 0; + for (uint8_t c = 1; c < len; c++) { + total += ((uint8_t *)p)[c]; + } + return ((uint8_t *)p)[0] == total; +} +void dump(uint8_t *__xdata a, uint16_t __xdata l) { + pr(" "); +#define ROWS 16 + for (uint8_t c = 0; c < ROWS; c++) { + pr(" %02X", c); + timerDelay(1333); + } + pr("\n--------"); + for (uint8_t c = 0; c < ROWS; c++) { + pr("---"); + timerDelay(1333); + } + for (uint16_t c = 0; c < l; c++) { + timerDelay(1333); + if ((c % ROWS) == 0) { + pr("\n0x%04X | ", c); + } + pr("%02X ", a[c]); + } + pr("\n--------"); + for (uint8_t c = 0; c < ROWS; c++) { + pr("---"); + timerDelay(1333); + } + pr("\n"); +} +uint8_t getBlockDataLength() { + uint8_t partNo = 0; + uint8_t rounds = 0; + while (partNo < BLOCK_MAX_PARTS) { + for (uint8_t c = 0; (c < BLOCK_MAX_PARTS) && (partNo < BLOCK_MAX_PARTS); c++) { + if (requestedData.requestedParts[c / 8] & (1 << (c % 8))) { + partNo++; + } + } + rounds++; + if (rounds == 4) { + return partNo; + } + } + return partNo; +} +uint8_t __xdata getPacketType(void *__xdata buffer) { + struct MacFcs *__xdata fcs = buffer; + if ((fcs->frameType == 1) && (fcs->destAddrType == 2) && (fcs->srcAddrType == 3) && (fcs->panIdCompressed == 0)) { + // broadcast frame + uint8_t __xdata type = ((uint8_t *)buffer)[sizeof(struct MacFrameBcast)]; + return type; + } else if ((fcs->frameType == 1) && (fcs->destAddrType == 3) && (fcs->srcAddrType == 3) && (fcs->panIdCompressed == 1)) { + // normal frame + uint8_t __xdata type = ((uint8_t *)buffer)[sizeof(struct MacFrameNormal)]; + return type; + } + return 0; +} +uint16_t averageXmitDelay(uint16_t xfersize) { + // returns the about maximum time the base should spend on the transfer; about 6 seconds (6000ms) for a 8000 byte transfer. This is * 3 / 4, pretty conservative + // this includes a lot of retransmissions. + return (xfersize * 3) / 5; +} + +// serial update +void eraseUpdateBlock() { + eepromErase(EEPROM_UPDATA_AREA_START, EEPROM_UPDATE_AREA_LEN / EEPROM_ERZ_SECTOR_SZ); +} +bool validateBlockData() { + struct blockData *bd = (struct blockData *)blockbuffer; + // pr("expected len = %04X, checksum=%04X\n", bd->size, bd->checksum); + uint16_t t = 0; + for (uint16_t c = 0; c < bd->size; c++) { + t += bd->data[c]; + } + return bd->checksum == t; +} +void saveBlock(uint8_t blockId) { + if (!eepromWrite(EEPROM_UPDATA_AREA_START + (blockId * BLOCK_DATA_SIZE), blockbuffer + sizeof(struct blockData), BLOCK_DATA_SIZE)) + pr("EEPROM write failed\n"); +} +void performUpdate() { + eepromReadStart(EEPROM_UPDATA_AREA_START); + selfUpdate(); +} +uint16_t getBlockChecksum() { + struct blockData *bd = (struct blockData *)blockbuffer; + return bd->checksum; +} + +// pendingdata slot stuff +int8_t findSlotForMac(const uint8_t *mac) { + for (uint8_t __xdata c = 0; c < MAX_PENDING_MACS; c++) { + if (u64_isEq((uint64_t __xdata *)mac, (uint64_t __xdata *)&(pendingDataArr[c].targetMac))) { // this costs 1 sloc :( + return c; + } + } + return -1; +} +int8_t findFreeSlot() { + for (uint8_t __xdata c = 0; c < MAX_PENDING_MACS; c++) { + if (pendingDataArr[c].attemptsLeft == 0) { + return c; + } + } + return -1; +} + +// processing serial data +#define ZBS_RX_WAIT_HEADER 0 +#define ZBS_RX_WAIT_SDA 1 +#define ZBS_RX_WAIT_UPDBLOCK 2 +void processSerial(uint8_t lastchar) { + // uartTx(lastchar); echo + switch (RXState) { + case ZBS_RX_WAIT_HEADER: + // shift characters in + for (uint8_t c = 0; c < 3; c++) { + cmdbuffer[c] = cmdbuffer[c + 1]; + } + cmdbuffer[3] = lastchar; + if (strncmp(cmdbuffer, "SDA>", 4) == 0) { + RXState = ZBS_RX_WAIT_SDA; + bytesRemain = sizeof(struct pendingData); + serialbufferp = serialbuffer; + break; + } + if (strncmp(cmdbuffer, "VER?", 4) == 0) { + pr("VER>%04X\n", version); + } + if (strncmp(cmdbuffer, "RDY?", 4) == 0) { + pr("RDY>"); + } + if (strncmp(cmdbuffer, "RSET", 4) == 0) { + wdtDeviceReset(); + } + if (strncmp(cmdbuffer, "ERAS", 4) == 0) { + // erase update space + eraseUpdateBlock(); + pr("EROK\n"); + } + if (strncmp(cmdbuffer, "UPDA", 4) == 0) { + // perform update! + pr("OK>>\n"); + performUpdate(); + } + if (strncmp(cmdbuffer, "SUBL", 4) == 0) { + // save update block + RXState = ZBS_RX_WAIT_UPDBLOCK; + bytesRemain = sizeof(struct espSaveUpdateBlock); + serialbufferp = serialbuffer; + break; + } + break; + + case ZBS_RX_WAIT_SDA: + *serialbufferp = lastchar; + serialbufferp++; + bytesRemain--; + if (bytesRemain == 0) { + if (checkCRC(serialbuffer, sizeof(struct pendingData))) { + int8_t slot = findFreeSlot(); + if (slot != -1) { + xMemCopyShort(&(pendingDataArr[slot]), serialbuffer, sizeof(struct pendingData)); + pr("ACK>\n"); + } else { + pr("NOK>\n"); + } + } else { + pr("NOK>\n"); + } + + RXState = ZBS_RX_WAIT_HEADER; + } + break; + case ZBS_RX_WAIT_UPDBLOCK: + *serialbufferp = lastchar; + serialbufferp++; + bytesRemain--; + if (bytesRemain == 0) { + if (checkCRC(serialbuffer, sizeof(struct espSaveUpdateBlock))) { + if (validateBlockData()) { + const struct espSaveUpdateBlock *updb = (struct espSaveUpdateBlock *)serialbuffer; + if (updb->blockChecksum == getBlockChecksum()) { + saveBlock(updb->blockId); + pr("BLOK\n"); + } else { + pr("BLFL> - block checksum doesn't match\n"); + } + } else { + pr("BLFL> - block doesn't validate\n"); + } + } else { + // block failed download + pr("BLFL> - update block data checksum failed\n"); + } + RXState = ZBS_RX_WAIT_HEADER; + } + break; + } +} + +// sending data to the ESP +void espBlockRequest(const struct blockRequest *br) { + struct espBlockRequest *__xdata ebr = (struct espBlockRequest *)blockbuffer; + uartTx('R'); + uartTx('Q'); + uartTx('B'); + uartTx('>'); + // u64_copy(ebr->ver, br->ver); + xMemCopy8(&(ebr->ver), &(br->ver)); + ebr->blockId = br->blockId; + addCRC(ebr, sizeof(struct espBlockRequest)); + for (uint8_t c = 0; c < sizeof(struct espBlockRequest); c++) { + uartTx(((uint8_t *)ebr)[c]); + } + // pr("req ebr ver: %02X%02X%02X%02X%02X%02X%02X%02X\n", ((uint8_t *)&(ebr->ver))[0], ((uint8_t *)&(ebr->ver))[1], ((uint8_t *)&(ebr->ver))[2], ((uint8_t *)&(ebr->ver))[3], ((uint8_t *)&(ebr->ver))[4], ((uint8_t *)&(ebr->ver))[5], ((uint8_t *)&(ebr->ver))[6], ((uint8_t *)&(ebr->ver))[7]); + // pr("req br ver: %02X%02X%02X%02X%02X%02X%02X%02X\n", ((uint8_t *)&(br->ver))[0], ((uint8_t *)&(br->ver))[1], ((uint8_t *)&(br->ver))[2], ((uint8_t *)&(br->ver))[3], ((uint8_t *)&(br->ver))[4], ((uint8_t *)&(br->ver))[5], ((uint8_t *)&(br->ver))[6], ((uint8_t *)&(br->ver))[7]); +} +void espNotifyAvailDataReq(const struct AvailDataReq *adr) { + uartTx('A'); + uartTx('D'); + uartTx('R'); + uartTx('>'); + for (uint8_t c = 0; c < sizeof(struct AvailDataReq); c++) { + uartTx(((uint8_t *)adr)[c]); + } +} +void espNotifyXferComplete(const uint8_t *src) { + struct espXferComplete exfc; + xMemCopy8(&exfc.src, src); + uartTx('X'); + uartTx('F'); + uartTx('C'); + uartTx('>'); + addCRC(&exfc, sizeof(exfc)); + for (uint8_t c = 0; c < sizeof(exfc); c++) { + uartTx(((uint8_t *)exfc)[c]); + } +} +void espNotifyTimeOut() { +} +void espNotifyJoinNetwork(const uint8_t *src) { + struct espJoinNetwork ejn; + xMemCopy8(&ejn.src, src); + uartTx('T'); + uartTx('J'); + uartTx('N'); + uartTx('>'); + addCRC(&ejn, sizeof(ejn)); + for (uint8_t c = 0; c < sizeof(ejn); c++) { + uartTx(((uint8_t *)ejn)[c]); + } +} + +// process data from tag +void processBlockRequest(const uint8_t *buffer, uint8_t forceBlockDownload) { + struct MacFrameNormal *__xdata rxHeader = (struct MacFrameNormal *)buffer; + struct blockRequest *__xdata blockReq = (struct blockRequest *)(buffer + sizeof(struct MacFrameNormal) + 1); + if (!checkCRC(blockReq, sizeof(struct blockRequest))) return; + // todo: actually do something with the block request + // uint32_t __xdata curTimerValue = *t; + bool __xdata requestDataDownload = false; + // if ((blockReq->blockId != requestedData.blockId) || (blockReq->ver != requestedData.ver)) { + if ((blockReq->blockId != requestedData.blockId) || (!u64_isEq((const uint64_t __xdata *)&blockReq->ver, (const uint64_t __xdata *)&requestedData.ver))) { + // requested block isn't already in the buffer + requestDataDownload = true; + } else { + // requested block is already in the buffer + if (forceBlockDownload) { + // force a download anyway; probably some error in the transfer between ESP32->AP-tag + if (!blockRequestInProgress) { + // block download from ESP32 not in progress + blockRequestInProgress = true; + requestDataDownload = true; + } else { + // block download from ESP32 requested, but already in progress. Maybe the transfer stalled for some reason; have the ESP32 send us some bytes. + uartTx('R'); + uartTx('Q'); + uartTx('Q'); + uartTx('>'); + } + } + } + + memcpy(&requestedData, blockReq, sizeof(struct blockRequest)); + + struct MacFrameNormal *txHeader = (struct MacFrameNormal *)(radiotxbuffer + 1); + struct blockRequestAck *blockRequestAck = (struct blockRequestAck *)(radiotxbuffer + sizeof(struct MacFrameNormal) + 2); + radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + sizeof(struct blockRequestAck) + RAW_PKT_PADDING; + radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_BLOCK_REQUEST_ACK; + + // TODO: get this data from somnewhere, dynamically. Depending on cache status we might need to return a longer or shorter wait period + if (blockStartTimer == 0) { + if (requestDataDownload) { + // check if we need to download the first block; we need to give the ESP32 some additional time to cache the file + if (blockReq->blockId == 0) { + blockRequestAck->pleaseWaitMs = 200; + } else { + blockRequestAck->pleaseWaitMs = 100; + } + blockStartTimer = timerGet() + blockRequestAck->pleaseWaitMs * TIMER_TICKS_PER_MS; + } else { + blockRequestAck->pleaseWaitMs = 30; + blockStartTimer = timerGet() + blockRequestAck->pleaseWaitMs * TIMER_TICKS_PER_MS; + } + } else { + blockRequestAck->pleaseWaitMs = (blockStartTimer - timerGet()) / TIMER_TICKS_PER_MS; + if (blockRequestAck->pleaseWaitMs < 30) { + blockRequestAck->pleaseWaitMs = 30; + blockStartTimer = timerGet() + blockRequestAck->pleaseWaitMs * TIMER_TICKS_PER_MS; + } + } + blockRequestAck->blockSizeMs = 15 + 15 + (getBlockDataLength() * 245) / BLOCK_MAX_PARTS; + blockRequestAck->cancelXfer = 0; + // pr("s=%d\n", blockRequestAck->blockSizeMs); + + memcpy(txHeader->src, mSelfMac, 8); + memcpy(txHeader->dst, rxHeader->src, 8); + memcpy(dstMac, rxHeader->src, 8); + dstPan = rxHeader->pan; + + txHeader->pan = rxHeader->pan; + txHeader->fcs.frameType = 1; + txHeader->fcs.panIdCompressed = 1; + txHeader->fcs.destAddrType = 3; + txHeader->fcs.srcAddrType = 3; + txHeader->seq = seq++; + + addCRC((void *)blockRequestAck, sizeof(struct blockRequestAck)); + + radioTx(radiotxbuffer); + radioTx(radiotxbuffer); + radioTx(radiotxbuffer); + + // pr("req blockreq: %02X%02X%02X%02X%02X%02X%02X%02X\n", ((uint8_t *)&(blockReq->ver))[0], ((uint8_t *)&(blockReq->ver))[1], ((uint8_t *)&(blockReq->ver))[2], ((uint8_t *)&(blockReq->ver))[3], ((uint8_t *)&(blockReq->ver))[4], ((uint8_t *)&(blockReq->ver))[5], ((uint8_t *)&(blockReq->ver))[6], ((uint8_t *)&(blockReq->ver))[7]); + + if (requestDataDownload) { + // espBlockRequest(blockReq); + espBlockRequest(&requestedData); + } + + /* + pr("Req: %d [", blockReq->blockId); + for (uint8_t c = 0; c < BLOCK_MAX_PARTS; c++) { + if ((c != 0) && (c % 8 == 0)) pr("]["); + if (blockReq->requestedParts[c / 8] & (1 << (c % 8))) { + pr("R"); + } else { + pr("."); + } + } + pr("]\n"); + */ +} +void processAvailDataReq(uint8_t *buffer) { + struct MacFrameNormal *rxHeader = (struct MacFrameNormal *)buffer; + struct AvailDataReq *availDataReq = (struct AvailDataReq *)(buffer + sizeof(struct MacFrameNormal) + 1); + + if (!checkCRC(availDataReq, sizeof(struct AvailDataReq))) return; + + // prepare tx buffer to send a response + memset(radiotxbuffer, 0, 120); + struct MacFrameNormal *txHeader = (struct MacFrameNormal *)(radiotxbuffer + 1); + struct AvailDataInfo *availDataInfo = (struct AvailDataInfo *)(radiotxbuffer + sizeof(struct MacFrameNormal) + 2); + radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + sizeof(struct AvailDataInfo) + RAW_PKT_PADDING; + radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_AVAIL_DATA_INFO; + + // check to see if we were addressing this mac in this burst, and if yes, copy availdatainfo to the tx buffer + bool haveData = false; + for (uint8_t __xdata c = 0; c < MAX_PENDING_MACS; c++) { + if (pendingDataArr[c].includedThisBurst == 1) { + if (memcmp(pendingDataArr[c].targetMac, rxHeader->src, 8) == 0) { + haveData = true; + xMemCopyShort((void *__xdata)availDataInfo, &(pendingDataArr[c].availdatainfo), sizeof(struct AvailDataInfo)); + break; + } + } + } + if (!haveData) return; + + xMemCopy8(txHeader->src, mSelfMac); + xMemCopy8(txHeader->dst, rxHeader->src); + + // memcpy(txHeader->src, mSelfMac, 8); + // memcpy(txHeader->dst, rxHeader->src, 8); + txHeader->pan = rxHeader->pan; + txHeader->fcs.frameType = 1; + txHeader->fcs.panIdCompressed = 1; + txHeader->fcs.destAddrType = 3; + txHeader->fcs.srcAddrType = 3; + txHeader->seq = seq++; + addCRC(availDataInfo, sizeof(struct AvailDataInfo)); + radioTx(radiotxbuffer); + espNotifyAvailDataReq(availDataReq); +} +void processXferComplete(uint8_t *buffer) { + struct MacFrameNormal *rxHeader = (struct MacFrameNormal *)buffer; + sendXferCompleteAck(rxHeader->src); + espNotifyXferComplete(rxHeader->src); + uint8_t slot = findSlotForMac(rxHeader->src); + pendingDataArr[slot].attemptsLeft = 0; +} + +// send crap to the tag +void prepareMacForSyncBurst() { + // mark all pending macs as 'not included' + for (uint8_t __xdata c = 0; c < MAX_PENDING_MACS; c++) { + pendingDataArr[c].includedThisBurst = 0; + } + + memset(radiotxbuffer, 0, sizeof(struct MacFrameBcast) + 1 + 20 + 2); // TODO, optimize + + struct burstMacData *__xdata macdata = (struct burstMacData * __xdata)(((uint8_t *)radiotxbuffer) + sizeof(struct MacFrameBcast) + 3); // total len, pkt type sync, mac-count + + uint8_t __xdata count = 0; + uint16_t __xdata currOffset = 500; + while (count < MAX_MACS_PER_SYNC) { + uint8_t __xdata tempmax = 0; + int8_t __xdata maxid = -1; + for (uint8_t __xdata c = 0; c < MAX_PENDING_MACS; c++) { + // check if this mac is already included in the planned sync burst + if (pendingDataArr[c].includedThisBurst == 0) { + // check if this current amount of 'attemptsleft' is the current maximum + if (pendingDataArr[c].attemptsLeft && (pendingDataArr[c].attemptsLeft > tempmax)) { + uint16_t timeoffset = SYNC_BURST_INTERVAL * 1000; + timeoffset -= 1000; + // check if the estimated transmission would fit in the remaining time + if (currOffset + averageXmitDelay(pendingDataArr[c].availdatainfo.dataSize) < timeoffset) { + tempmax = pendingDataArr[c].attemptsLeft; + maxid = c; + } + } + } + } + if (maxid == -1) { + // didn't find any valid mac's to add to the syncburst + return; + } else { + // found a pending-data info struct with the highest amount of attemptsLeft. Add this to the sync burst + // make sure we don't add this pending data mac twice + pendingDataArr[maxid].includedThisBurst = 1; + pendingDataArr[maxid].attemptsLeft--; + xMemCopyShort(macdata[count].targetMac, pendingDataArr[maxid].targetMac, 8); + macdata[count].offset = currOffset; + currOffset += averageXmitDelay(pendingDataArr[maxid].availdatainfo.dataSize); + count++; + *((uint8_t *)radiotxbuffer + sizeof(struct MacFrameBcast) + 2) = count; + } + } +} +void sendSyncBurst() { + struct MacFrameBcast *txframe = (struct MacFrameBcast *)(radiotxbuffer + 1); + memcpy(txframe->src, mSelfMac, 8); + *((uint8_t *)txframe + sizeof(struct MacFrameBcast)) = PKT_SYNC_BURST; + txframe->fcs.frameType = 1; + txframe->fcs.secure = 0; + txframe->fcs.framePending = 0; + txframe->fcs.ackReqd = 0; + txframe->fcs.panIdCompressed = 0; + txframe->fcs.destAddrType = 2; + txframe->fcs.frameVer = 0; + txframe->fcs.srcAddrType = 3; + txframe->seq = 0; + txframe->dstPan = 0xFFFF; + txframe->dstAddr = 0xFFFF; + txframe->srcPan = 0x4447; + + radiotxbuffer[0] = sizeof(struct MacFrameBcast) + 1 + (MAX_MACS_PER_SYNC * sizeof(struct burstMacData)) + 2; + pr("BST>\n"); + burstLengthMs = timerGet(); + for (uint16_t c = 0; c < SYNC_BURST_LENGTH; c++) { + radioTx(radiotxbuffer); + txframe->seq++; + } + burstLengthMs = (timerGet() - burstLengthMs) / 1333; + // pr("atxc in %lu,\n", burstLengthMs); +} +void sendTimingReply(void *__xdata buf) { + struct MacFrameBcast *rxframe = (struct MacFrameBcast *)buf; + + struct MacFrameNormal *frameHeader = (struct MacFrameNormal *)(radiotxbuffer + 1); + struct timingResponse *response = (struct timingResponse *)(radiotxbuffer + sizeof(struct MacFrameNormal) + 2); + radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_TIMING_RESPONSE; + radiotxbuffer[0] = sizeof(struct MacFrameNormal) + sizeof(struct timingResponse) + 1 + RAW_PKT_PADDING; + memset(response, 0, sizeof(struct timingResponse) + sizeof(struct MacFrameNormal) + 1); + memcpy(frameHeader->src, mSelfMac, 8); + memcpy(frameHeader->dst, rxframe->src, 8); + + response->timerValue = timerGet(); + response->burstInterval = SYNC_BURST_INTERVAL * 1000; + response->burstLength = SYNC_BURST_LENGTH; // in packets; due to use of sequence field, limited to a 256-packet burst + response->burstLengthMs = burstLengthMs; // burst length in ms + response->burstIntervalRemaining = (TIMER_TICKS_PER_SECOND * SYNC_BURST_INTERVAL) - (timerGet() - burstIntervalTimer); + + frameHeader->fcs.frameType = 1; + frameHeader->fcs.panIdCompressed = 1; + frameHeader->fcs.destAddrType = 3; + frameHeader->fcs.srcAddrType = 3; + frameHeader->seq = seq++; + frameHeader->pan = rxframe->srcPan; + addCRC(response, sizeof(struct timingResponse)); + radioTx(radiotxbuffer); + espNotifyJoinNetwork(rxframe->src); +} +void sendPart(uint8_t partNo) { + struct MacFrameNormal *frameHeader = (struct MacFrameNormal *)(radiotxbuffer + 1); + struct blockPart *blockPart = (struct blockPart *)(radiotxbuffer + sizeof(struct MacFrameNormal) + 2); + memset(radiotxbuffer + 1, 0, sizeof(struct blockPart) + sizeof(struct MacFrameNormal)); + radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_BLOCK_PART; + radiotxbuffer[0] = sizeof(struct MacFrameNormal) + sizeof(struct blockPart) + BLOCK_PART_DATA_SIZE + 1 + RAW_PKT_PADDING; + memcpy(frameHeader->src, mSelfMac, 8); + memcpy(frameHeader->dst, dstMac, 8); + blockPart->blockId = requestedData.blockId; + blockPart->blockPart = partNo; + memcpy(&(blockPart->data), blockbuffer + (partNo * BLOCK_PART_DATA_SIZE), BLOCK_PART_DATA_SIZE); + addCRC(blockPart, sizeof(struct blockPart) + BLOCK_PART_DATA_SIZE); + frameHeader->fcs.frameType = 1; + frameHeader->fcs.panIdCompressed = 1; + frameHeader->fcs.destAddrType = 3; + frameHeader->fcs.srcAddrType = 3; + frameHeader->seq = seq++; + frameHeader->pan = dstPan; + radioTx(radiotxbuffer); +} +void sendBlockData() { + uint8_t partNo = 0; + uint8_t rounds = 0; + while (partNo < BLOCK_MAX_PARTS) { + for (uint8_t c = 0; (c < BLOCK_MAX_PARTS) && (partNo < BLOCK_MAX_PARTS); c++) { + if (requestedData.requestedParts[c / 8] & (1 << (c % 8))) { + sendPart(c); + partNo++; + } + } + rounds++; + if (rounds == 4) { + return; + } + } + // TODO: not sure if we need this, probably not. Not sure why I added it in the first place + commsRxUnencrypted(radiorxbuffer); + commsRxUnencrypted(radiorxbuffer); + commsRxUnencrypted(radiorxbuffer); + commsRxUnencrypted(radiorxbuffer); + commsRxUnencrypted(radiorxbuffer); + commsRxUnencrypted(radiorxbuffer); +} +void sendXferCompleteAck(uint8_t *dst) { + struct MacFrameNormal *frameHeader = (struct MacFrameNormal *)(radiotxbuffer + 1); + memset(radiotxbuffer + 1, 0, sizeof(struct blockPart) + sizeof(struct MacFrameNormal)); + radiotxbuffer[sizeof(struct MacFrameNormal) + 1] = PKT_XFER_COMPLETE_ACK; + radiotxbuffer[0] = sizeof(struct MacFrameNormal) + 1 + RAW_PKT_PADDING; + memcpy(frameHeader->src, mSelfMac, 8); + memcpy(frameHeader->dst, dst, 8); + frameHeader->fcs.frameType = 1; + frameHeader->fcs.panIdCompressed = 1; + frameHeader->fcs.destAddrType = 3; + frameHeader->fcs.srcAddrType = 3; + frameHeader->seq = seq++; + frameHeader->pan = dstPan; + radioTx(radiotxbuffer); +} + +// main loop +void main(void) { + clockingAndIntsInit(); + timerInit(); + boardInit(); + P0FUNC = 0b11001111; // enable uart tx/rx and SPI bus functions + irqsOn(); + boardInitStage2(); + requestedData.blockId = 0xFF; + if (!boardGetOwnMac(mSelfMac)) { + pr("failed to get MAC. Aborting\n"); + while (1) + ; + } + for (uint8_t c = 0; c < 8; c++) { + // mSelfMac[c] = c; + } + + if (!eepromInit()) { // we'll need the eeprom here, init it. + pr("failed to init eeprom\n"); + return; + } + + // clear the array with pending information + memset(pendingDataArr, 0, sizeof(pendingDataArr)); + + radioInit(); + radioRxFilterCfg(mSelfMac, 0x10000, PROTO_PAN_ID); + + // init the "random" number generation unit + rndSeed(mSelfMac[0] ^ (uint8_t)timerGetLowBits(), mSelfMac[1]); + // wdtSetResetVal(0xFD0DCF); + // wdtOn(); + radioSetChannel(RADIO_FIRST_CHANNEL); + radioSetTxPower(10); + radioRxEnable(true, true); + + // uint8_t __xdata fromMac[8]; + pr("RDY>\n"); + + // send first burst, used to characterize the packet TX speed + burstIntervalTimer = timerGet(); // + (TIMER_TICKS_PER_SECOND * SYNC_BURST_INTERVAL); + sendSyncBurst(); + + // really... if I do the call below, it'll cost me 8 bytes IRAM. Not the kind of 'optimization' I ever dreamed of doing + // pr("MAC>%02X%02X%02X%02X%02X%02X%02X%02X\n", mSelfMac[0], mSelfMac[1], mSelfMac[2], mSelfMac[3], mSelfMac[4], mSelfMac[5], mSelfMac[6], mSelfMac[7]); + pr("MAC>%02X%02X", mSelfMac[0], mSelfMac[1]); + pr("%02X%02X", mSelfMac[2], mSelfMac[3]); + pr("%02X%02X", mSelfMac[4], mSelfMac[5]); + pr("%02X%02X\n", mSelfMac[6], mSelfMac[7]); + + pr("VER>%04X\n", version); + while (1) { + radioRxFlush(); + + // spend about 30 seconds - 100ms in this while loop. The last 100ms are for preparing the sync burst + while ((timerGet() - burstIntervalTimer) < ((TIMER_TICKS_PER_SECOND * SYNC_BURST_INTERVAL) - 100 * TIMER_TICKS_PER_MS)) { + int8_t ret = commsRxUnencrypted(radiorxbuffer); + if (ret > 1) { + // received a packet, lets see what it is + switch (getPacketType(radiorxbuffer)) { + case PKT_TIMING_REQ: + sendTimingReply(radiorxbuffer); + break; + case PKT_AVAIL_DATA_REQ: + processAvailDataReq(radiorxbuffer); + break; + case PKT_BLOCK_REQUEST: + processBlockRequest(radiorxbuffer, 1); + break; + case PKT_BLOCK_PARTIAL_REQUEST: + processBlockRequest(radiorxbuffer, 0); + break; + case PKT_XFER_COMPLETE: + processXferComplete(radiorxbuffer); + break; + // + default: + // pr("other packet...type = %02X\n", getPacketType(radiorxbuffer)); + // dump(radiorxbuffer, 128); + break; + } + } + while (uartBytesAvail()) { + processSerial(uartRx()); + } + if (blockStartTimer) { + // BUG: uint32 overflowing; this will break every once in a while. Don't know how to fix this other than ugly global variables + if (timerGet() > blockStartTimer) { + sendBlockData(); + blockStartTimer = 0; + } + } + } + + for (uint8_t __xdata c = 0; c < MAX_PENDING_MACS; c++) { + if (pendingDataArr[c].attemptsLeft == 1) { + espNotifyTimeOut(); + pendingDataArr[c].attemptsLeft = 0; + } + } + prepareMacForSyncBurst(); + + while ((timerGet() - burstIntervalTimer) < (TIMER_TICKS_PER_SECOND * SYNC_BURST_INTERVAL)) { + // wait here for maximum burst-start accuracy + } + burstIntervalTimer = timerGet(); + sendSyncBurst(); + } +} diff --git a/ap_fw/printf.h b/ap_fw/printf.h new file mode 100644 index 000000000..b04e05904 --- /dev/null +++ b/ap_fw/printf.h @@ -0,0 +1,27 @@ +#ifndef _PRINTF_H_ +#define _PRINTF_H_ + + +//our printf has some special abilities +//for example "*" will modify the param to be a __xdata pointer to whatever it would have been instead +//it must then be paramed as "(uintptr_near_t)&value" +//for cc1110 (but not for ZBS) code and xdata addrs are the same, so __code pointers will also work! + +//"%s" param takes a generic pointer, but assumes it is an xdata/code (no string support in pdata/idata) +//"%ls" takes an xdata(/code in cc111x) pointer instead :) + +//"%m/%M" will print a mac, an __xdata pointer to which has been provided + +//no support for passing NULL to %s + +//not re-entrant if %d/%u are used + +#pragma callee_saves pr +void pr(const char __code *fmt, ...) __reentrant; + +#pragma callee_saves spr +void spr(char __xdata* out, const char __code *fmt, ...) __reentrant; + + + +#endif diff --git a/ap_fw/proto.h b/ap_fw/proto.h new file mode 100644 index 000000000..2dd141598 --- /dev/null +++ b/ap_fw/proto.h @@ -0,0 +1,182 @@ +#ifndef _PROTO_H_ +#define _PROTO_H_ + +#include + +/* + All communications are direct from tag to station, EXCEPT association (tag will broadcast). + All comms shall be encrypted and authenticated with AES-CCM. Shared key shall be burned into the firmware. + Master shall provision new key at association. All non-bcast packets shall have pan id compression. + Master may skip "from" field. Tag checking in confirms it got the master's provisioning reply. + + Sadly filtering on MZ100 fails for long addr with no src addr. so short addr for src is used + + T = tag, S = station + + PACKET TYPE USE PAYLOAD STRUCT NOTES + ASSOC_REQ T2bcast TagInfo tag's info and assoc request (encrypted with shared key) + ASSOC_RESP S2T AssocInfo tag's association info (encrypted with shared key) + CHECKIN T2S CheckinInfo tag checking in occasionally + CHECKOUT S2T PendingInfo station's checkin reply telling tag what we have for it + CHUNK_REQ T2S ChunkReqInfo tag requesting a piece of data + CHUNK_RESP S2T ChunkInfo station provides chunk + +*/ + +#define PROTO_PRESHARED_KEY {0x34D906D3, 0xE3E5298E, 0x3429BF58, 0xC1022081} + +#define PROTO_PAN_ID (0x4447) //PAN ID compression shall be used + +#define PKT_ASSOC_REQ (0xF0) +#define PKT_ASSOC_RESP (0xF1) +#define PKT_CHECKIN (0xF2) +#define PKT_CHECKOUT (0xF3) +#define PKT_CHUNK_REQ (0xF4) +#define PKT_CHUNK_RESP (0xF5) + +#define PROTO_VER_0 (0) +#define PROTO_VER_CURRENT (PROTO_VER_0) + +#define PROTO_COMPR_TYPE_LZ (0x0001) +#define PROTO_COMPR_TYPE_BITPACK (0x0002) + +#define PROTO_MAX_DL_LEN (88) + +enum TagScreenType { + TagScreenEink_BW_1bpp, + TagScreenEink_BW_2bpp, + TagScreenEink_BW_4bpp, + TagScreenEink_BWY_only, //2bpp, but only 3 colors (BW?Y) + TagScreenEink_BWY_2bpp, + TagScreenEink_BWY_4bpp, + TagScreenEink_BWR_only, //2bpp, but only 3 colors (BW?R) + TagScreenEink_BWR_2bpp, + TagScreenEink_BWR_4bpp, + + TagScreenEink_BWY_3bpp, + TagScreenEink_BWR_3bpp, + TagScreenEink_BW_3bpp, + + TagScreenPersistentLcd_1bpp, + + TagScreenEink_BWY_5colors, + TagScreenEink_BWR_5colors, + + TagScreenEink_BWY_6colors, + TagScreenEink_BWR_6colors, + + TagScreenTypeOther = 0x7f, +}; + +#ifndef __packed +#define __packed __attribute__((packed)) +#endif + +struct TagState { + uint64_t swVer; + uint16_t hwType; + uint16_t batteryMv; +} __packed; + +struct TagInfo { + uint8_t protoVer; //PROTO_VER_* + struct TagState state; + uint8_t rfu1[1]; //shall be ignored for now + uint16_t screenPixWidth; + uint16_t screenPixHeight; + uint16_t screenMmWidth; + uint16_t screenMmHeight; + uint16_t compressionsSupported; //COMPR_TYPE_* bitfield + uint16_t maxWaitMsec; //how long tag will wait for packets before going to sleep + uint8_t screenType; //enum TagScreenType + uint8_t rfu[11]; //shall be zero for now +} __packed; + +struct AssocInfo { + uint32_t checkinDelay; //space between checkins, in msec + uint32_t retryDelay; //if download fails mid-way wait thi smany msec to retry (IFF progress was made) + uint16_t failedCheckinsTillBlank; //how many fails till we go blank + uint16_t failedCheckinsTillDissoc; //how many fails till we dissociate + uint32_t newKey[4]; + uint8_t rfu[8]; //shall be zero for now +} __packed; + +#define CHECKIN_TEMP_OFFSET 0x7f + +struct CheckinInfo { + struct TagState state; + uint8_t lastPacketLQI; //zero if not reported/not supported to be reported + int8_t lastPacketRSSI; //zero if not reported/not supported to be reported + uint8_t temperature; //zero if not reported/not supported to be reported. else, this minus CHECKIN_TEMP_OFFSET is temp in degrees C + uint8_t rfu[6]; //shall be zero for now +} __packed; + +struct PendingInfo { + uint64_t imgUpdateVer; + uint32_t imgUpdateSize; + uint64_t osUpdateVer; //version of OS update avail + uint32_t osUpdateSize; + uint8_t rfu[8]; //shall be zero for now +} __packed; + +struct ChunkReqInfo { + uint64_t versionRequested; + uint32_t offset; + uint8_t len; + uint8_t osUpdatePlz : 1; + uint8_t rfu[6]; //shall be zero for now +} __packed; + +struct ChunkInfo { + uint32_t offset; + uint8_t osUpdatePlz : 1; + uint8_t rfu; //shall be zero for now + uint8_t data[]; //no data means request is out of bounds of this version no longer exists +} __packed; + + + +#define MACFMT "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x" +#define MACCVT(x) ((const uint8_t*)(x))[7], ((const uint8_t*)(x))[6], ((const uint8_t*)(x))[5], ((const uint8_t*)(x))[4], ((const uint8_t*)(x))[3], ((const uint8_t*)(x))[2], ((const uint8_t*)(x))[1], ((const uint8_t*)(x))[0] + + +#define VERSION_SIGNIFICANT_MASK (0x0000ffffffffffffull) + +#define HW_TYPE_42_INCH_SAMSUNG (1) +#define HW_TYPE_42_INCH_SAMSUNG_ROM_VER_OFST (0xEFF8) + +#define HW_TYPE_74_INCH_DISPDATA (2) +#define HW_TYPE_74_INCH_DISPDATA_FRAME_MODE (3) +#define HW_TYPE_74_INCH_DISPDATA_ROM_VER_OFST (0x008b) + +#define HW_TYPE_ZBD_EPOP50 (4) +#define HW_TYPE_ZBD_EPOP50_ROM_VER_OFST (0x008b) + +#define HW_TYPE_ZBD_EPOP900 (5) +#define HW_TYPE_ZBD_EPOP900_ROM_VER_OFST (0x008b) + +#define HW_TYPE_29_INCH_DISPDATA (6) +#define HW_TYPE_29_INCH_DISPDATA_FRAME_MODE (7) +#define HW_TYPE_29_INCH_DISPDATA_ROM_VER_OFST (0x008b) + +#define HW_TYPE_29_INCH_ZBS_026 (8) +#define HW_TYPE_29_INCH_ZBS_026_FRAME_MODE (9) + +#define HW_TYPE_154_INCH_ZBS_033 (18) +#define HW_TYPE_154_INCH_ZBS_033_FRAME_MODE (19) + +#define HW_TYPE_42_INCH_ZBS_026 (28) +#define HW_TYPE_42_INCH_ZBS_026_FRAME_MODE (29) + +#define HW_TYPE_29_INCH_ZBS_025 (10) +#define HW_TYPE_29_INCH_ZBS_025_FRAME_MODE (11) + +#define HW_TYPE_29_INCH_ZBS_ROM_VER_OFST (0x008b) + + + +#endif + + + + diff --git a/ap_fw/sleep.h b/ap_fw/sleep.h new file mode 100644 index 000000000..02b06c46c --- /dev/null +++ b/ap_fw/sleep.h @@ -0,0 +1,10 @@ +#ifndef _SLEEP_H_ +#define _SLEEP_H_ + +#include + + +void sleepForMsec(uint32_t msec); +void sleepTillInt(void); //assumes you left only one int enabled! + +#endif diff --git a/ap_fw/soc/radioCommon.h b/ap_fw/soc/radioCommon.h new file mode 100644 index 000000000..61c4f8f5d --- /dev/null +++ b/ap_fw/soc/radioCommon.h @@ -0,0 +1,67 @@ +#ifndef _RADIO_COMMON_H_ +#define _RADIO_COMMON_H_ + +#include +#include + + + +#define RADIO_MAX_PACKET_LEN (125) //useful payload, not including the crc + +#define ADDR_MODE_NONE (0) +#define ADDR_MODE_SHORT (2) +#define ADDR_MODE_LONG (3) + +#define FRAME_TYPE_BEACON (0) +#define FRAME_TYPE_DATA (1) +#define FRAME_TYPE_ACK (2) +#define FRAME_TYPE_MAC_CMD (3) + +#define SHORT_MAC_UNUSED (0x10000000UL) //for radioRxFilterCfg's myShortMac + + + +struct MacFcs { + + uint8_t frameType : 3; + uint8_t secure : 1; + uint8_t framePending : 1; + uint8_t ackReqd : 1; + uint8_t panIdCompressed : 1; + uint8_t rfu1 : 1; + uint8_t rfu2 : 2; + uint8_t destAddrType : 2; + uint8_t frameVer : 2; + uint8_t srcAddrType : 2; +}; + + +void radioInit(void); +bool radioTx(const void __xdata* packet); //waits for tx end + +#pragma callee_saves radioRxAckReset +void radioRxAckReset(void); +#pragma callee_saves radioRxAckGetLast +int16_t radioRxAckGetLast(void); //get seq of lask ack we got or -1 if none +void radioRxFilterCfg(const uint8_t __xdata *filterForLong, uint32_t myShortMac, uint16_t myPan); +void radioRxEnable(__bit on, __bit autoAck); + +#pragma callee_saves radioSetTxPower +void radioSetTxPower(int8_t dBm); //-30..+10 dBm + +#pragma callee_saves radioSetChannel +void radioSetChannel(uint8_t ch); + +void radioRxFlush(void); +int8_t radioRxDequeuePktGet(const void __xdata * __xdata *dstBufP, uint8_t __xdata *lqiP, int8_t __xdata *rssiP); +void radioRxDequeuedPktRelease(void); + + + + + +#endif + + + + diff --git a/ap_fw/soc/zbs243/flash.c b/ap_fw/soc/zbs243/flash.c new file mode 100644 index 000000000..69900a244 --- /dev/null +++ b/ap_fw/soc/zbs243/flash.c @@ -0,0 +1,182 @@ +#include +#include "asmUtil.h" +#include "printf.h" +#include "flash.h" +#include "cpu.h" + + + +#pragma callee_saves flashAddrCheck +static uint8_t flashAddrCheck(uint32_t flashAddr, uint16_t len) __reentrant /* to save ram space */ // return 0xff for error, pgNo +{ + uint16_t dstOfst; + uint8_t pgNo; + + if (!len) + return 0xff; + + //verify address + if ((uint8_t)(flashAddr >> 24)) + return 0xff; + + switch ((uint8_t)(flashAddr >> 16)) { + case 0x00: + pgNo = ((uint8_t)(flashAddr >> 8)) >> 2; + break; + + case 0x80: + if (pgNo) + return 0xff; + pgNo = 0x80; + break; + + default: + return 0xff; + } + + dstOfst = flashAddr & 0x3ff; + //verify no boundary crossing + if (((uint8_t)((dstOfst + len - 1) >> 8)) >> 2) //yeah...sdcc + return 0xff; + + return pgNo; +} + +//info page is "at" 0x10000 +#pragma callee_saves flashWrite +__bit flashWrite(uint32_t dstAddr, const void __xdata *src, uint16_t len, __bit alsoErase /*whole page */) +{ + uint8_t pgNo, cfgPg, speed; + __bit irq; + + pgNo = flashAddrCheck(dstAddr, len); + if (pgNo == 0xff) + return false; + + irq = IEN_EA; + IEN_EA = false; + + speed = CLKSPEED; + CLKSPEED = 0x21; //flash ops only work at this speed + + cfgPg = CFGPAGE; + CFGPAGE = 4; + + if (alsoErase) + SETTINGS |= 0x38; + else + SETTINGS = 0x18; + FWRTHREE = 3; + FPGNO = pgNo; + FWRDSTL = (uint8_t)dstAddr; + FWRDSTH = (((uint16_t)dstAddr) >> 8) & 0x03; + FWRLENL = (uint8_t)(len - 1); + FWRLENH = (len - 1) >> 8; + FWRSRCL = (uint8_t)src; + FWRSRCH = ((uint16_t)src) >> 8; + TRIGGER |= 8; //start + while (!(TCON2 & 0x08)); //wait + + TCON2 &=~ 0x48; + SETTINGS &=~ 0x10; + + CFGPAGE = cfgPg; + + CLKSPEED = speed; + + IEN_EA = irq; + + return true; +} + +#pragma callee_saves flashRead +__bit flashRead(uint32_t srcAddr, void __xdata *dst, uint16_t len) +{ + uint8_t pgNo, cfgPg, speed; + __bit irq; + + pgNo = flashAddrCheck(srcAddr, len); + if (pgNo == 0xff) + return false; + + irq = IEN_EA; + IEN_EA = false; + + speed = CLKSPEED; + CLKSPEED = 0x21; //flash ops only work at this speed + + cfgPg = CFGPAGE; + CFGPAGE = 4; + + SETTINGS = 0x8; + FWRTHREE = 3; + FPGNO = pgNo; + FWRDSTL = (uint8_t)dst; + FWRDSTH = ((uint16_t)dst) >> 8; + FWRSRCL = (uint8_t)srcAddr; + FWRSRCH = (((uint16_t)srcAddr) >> 8) & 0x03; + FWRLENL = (uint8_t)(len - 1); + FWRLENH = (len - 1) >> 8; + TRIGGER |= 8; //start + + while (!(TCON2 & 0x08)); //wait + + TCON2 &=~ 0x48; + SETTINGS &=~ 0x10; + + CFGPAGE = cfgPg; + + CLKSPEED = speed; + + IEN_EA = irq; + + return true; +} + +#pragma callee_saves flashErase +__bit flashErase(uint32_t dstAddr) +{ + uint8_t __xdata dummyByte = 0xff; + uint8_t pgNo, cfgPg, speed; + __bit irq; + + pgNo = flashAddrCheck(dstAddr, 1); + + if (pgNo == 0xff) + return false; + + irq = IEN_EA; + IEN_EA = false; + + speed = CLKSPEED; + CLKSPEED = 0x21; //flash ops only work at this speed + + cfgPg = CFGPAGE; + CFGPAGE = 4; + + //this command does an erase AND a write (erase of page, write up to a page at given offset and len) + // i found no way to JUST erase. As len is encoded at "minus one", we cannot ask the hardware to write + // zeor bytes, so we write one - a 0xFF dummy + SETTINGS |= 0x38; + FWRTHREE = 3; + FPGNO = pgNo; + FWRDSTL = 0; + FWRDSTH = 0; + FWRLENL = 0; + FWRLENH = 0; + FWRSRCL = (uint8_t)&dummyByte; + FWRSRCH = ((uint16_t)&dummyByte) >> 8; + TRIGGER |= 8; //start + while (!(TCON2 & 0x08)); //wait + + TCON2 &=~ 0x48; + SETTINGS &=~ 0x10; + + CFGPAGE = cfgPg; + + CLKSPEED = speed; + + IEN_EA = irq; + + return true; +} diff --git a/ap_fw/soc/zbs243/flash.h b/ap_fw/soc/zbs243/flash.h new file mode 100644 index 000000000..2c03e2cb2 --- /dev/null +++ b/ap_fw/soc/zbs243/flash.h @@ -0,0 +1,23 @@ +#ifndef _FLASH_ZBS_H_ +#define _FLASH_ZBS_H_ + +#include + +#define FLASH_PAGE_SHIFT (10) +#define FLASH_PAGE_SIZE (1 << FLASH_PAGE_SHIFT) +#define FLASH_NUM_PAGES (64) + +#define FLASH_INFOPAGE_ADDR (0x00800000ul) + + + +#pragma callee_saves flashErase +__bit flashErase(uint32_t dstAddr); + +#pragma callee_saves flashRead +__bit flashRead(uint32_t srcAddr, void __xdata *dst, uint16_t len); + +#pragma callee_saves flashWrite +__bit flashWrite(uint32_t dstAddr, const void __xdata *src, uint16_t len, __bit alsoErase /*whole page */); + +#endif diff --git a/ap_fw/soc/zbs243/i2c.c b/ap_fw/soc/zbs243/i2c.c new file mode 100644 index 000000000..6f42168a5 --- /dev/null +++ b/ap_fw/soc/zbs243/i2c.c @@ -0,0 +1,128 @@ +#include "zbs243.h" +#include "i2c.h" + + +static volatile struct I2cTransaction __xdata * __xdata mCurTrans; +static volatile uint8_t __xdata mNumTrans; +static volatile uint8_t __xdata mResult; + +#pragma callee_saves i2cInit +void i2cInit(void) +{ + uint8_t bkp; + + CLKEN |= 0x10; + + bkp = CFGPAGE; + CFGPAGE = 0; + + IEN1 |= 4; //int on + I2CUNKNOWN |= 4; + + I2CCTL = 0x43; //master mode + I2CSPEED = 0x1a; //100KHz + I2CCTL |= 0x80; //irq on + + CFGPAGE = bkp; +} + +//this code assumes HW stat emachine acts as it should. for this chip that seems to be true +//this is an 8051 so we value speed & code size over defensive programming. So sue me! +void I2C_IRQ(void) __interrupt (8) +{ + uint8_t bkp, sta; + + bkp = CFGPAGE; + CFGPAGE = 0; + + sta = I2CSTATE; + + switch (sta >> 3) { + case 0x08 / 8: //start completed + case 0x10 / 8: //restart completed + I2CBUF = mCurTrans->deviceAddr; + break; + + case 0x30 / 8: //byte write was NAKed + if (mCurTrans->numBytes) { //we still had more bytes to send? + I2CCTL |= 0x10; //stop + mResult = I2cNonLastDataByteNAKed; + break; + } + //fallthough since it is ok to NAK last written byte + + case 0x28 / 8: //byte write was ACKed + case 0x18 / 8: //addr ACKED in write mode + + if (mCurTrans->numBytes) { + + mCurTrans->numBytes--; + I2CBUF = *mCurTrans->bytes++; + break; + } + transaction_over: + if (!--mNumTrans) { //byte(s)written and no more transactions? STOP + + mResult = I2cOK; + I2CCTL |= 0x10; //stop + } + else { //we have another transaction? RESTART + + mCurTrans++; + I2CCTL |= 0x20; //do a restart + } + break; + + case 0x20 / 8: //addr NAKEd in write mode + mResult = I2cWrAddrNAKed; + I2CCTL |= 0x10; //stop + break; + + case 0x48 / 8: //addr NACKed in read mode + mResult = I2cRdAddrNAKed; + I2CCTL |= 0x10; //stop + break; + + case 0x58 / 8: //NAK to RXed byte sent (we still have th ebyte we got in buffer) + case 0x50 / 8: //got byte in read mode + *mCurTrans->bytes++ = I2CBUF; + mCurTrans->numBytes--; + //fallthrough + + case 0x40 / 8: //addr ACKED in read mode + if (!mCurTrans->numBytes) + goto transaction_over; + else if (mCurTrans->numBytes == 1) + I2CCTL &=~ 0x04; //NAK + else + I2CCTL |= 0x04; //ACK + break; + + default: + mResult = I2cInternalError; + I2CCTL |= 0x10; //stop + mNumTrans = 0; + break; + } + + I2CCTL &=~ 0x08; + CFGPAGE = bkp; +} + +#pragma callee_saves i2cTransact +enum I2cResult i2cTransact(struct I2cTransaction __xdata *trans, uint8_t nTrans) +{ + mCurTrans = trans; + mNumTrans = nTrans; + + mResult = I2cOK; + + if (nTrans) { + + I2CCTL &=~ 0x10; //clear stop + I2CCTL |= 0x20; //issue start + + while (mNumTrans); //wait + } + return mResult; +} diff --git a/ap_fw/soc/zbs243/i2c.h b/ap_fw/soc/zbs243/i2c.h new file mode 100644 index 000000000..682209ce5 --- /dev/null +++ b/ap_fw/soc/zbs243/i2c.h @@ -0,0 +1,34 @@ +#ifndef _I2C_H_ +#define _I2C_H_ + +#include + +enum I2cResult { + I2cOK, + I2cWrAddrNAKed, + I2cRdAddrNAKed, + I2cNonLastDataByteNAKed, + I2cInternalError, +}; + +struct I2cTransaction { + uint8_t deviceAddr; + uint8_t numBytes; //will be updated as we go along so you can figure out what was NAKed + uint8_t __xdata *bytes; +}; + +#pragma callee_saves i2cInit +void i2cInit(void); + +#pragma callee_saves i2cTransact +uint8_t i2cTransact(struct I2cTransaction __xdata *trans, uint8_t nTrans); //returns enum I2cResult + +void I2C_IRQ(void) __interrupt (8); + + + + + + +#endif + diff --git a/ap_fw/soc/zbs243/make.mk b/ap_fw/soc/zbs243/make.mk new file mode 100644 index 000000000..c40d8ba99 --- /dev/null +++ b/ap_fw/soc/zbs243/make.mk @@ -0,0 +1,8 @@ +FLAGS += -Isoc/zbs243 + +FLAGS += -DSOC_ZBS243 --xram-loc 0xe000 --xram-size 0x2000 --model-large + +SOURCES += soc/zbs243/soc.c soc/zbs243/wdt.c soc/zbs243/sleep.c soc/zbs243/spi.c soc/zbs243/uart.c soc/zbs243/timer.c soc/zbs243/radio.c +SOURCES += soc/zbs243/flash.c soc/zbs243/temperature.c cpu/8051/random.c cpu/8051/printf.c + +CPU = 8051 \ No newline at end of file diff --git a/ap_fw/soc/zbs243/radio.c b/ap_fw/soc/zbs243/radio.c new file mode 100644 index 000000000..802f25614 --- /dev/null +++ b/ap_fw/soc/zbs243/radio.c @@ -0,0 +1,336 @@ +#include "radio.h" + +#include "asmUtil.h" +#include "board.h" +#include "cpu.h" +#include "printf.h" +#include "timer.h" + +#define RX_BUFFER_SIZE (RADIO_MAX_PACKET_LEN + 1 /* len byte */ + 2 /* RSSI & LQI */) +#define RX_BUFFER_NUM 3 + +static volatile uint8_t __xdata mRxBufs[RX_BUFFER_NUM][RX_BUFFER_SIZE]; +static volatile uint8_t __xdata mLastRSSI, mLastTxedSeq, mRxOn, mRxBufNextR, mRxBufNextW, mRxBufNumFree; +static volatile __bit mAckTimePassed, mGotAck; + +// some things look like: https://www.ti.com/lit/ds/symlink/cc2430.pdf +// maybe a licensed and heavily modified version? + +// maybe rx on and tx cal? +// see segmented_ota.<> + +void RF_IRQ1(void) __interrupt(4) { + uint8_t cause = RADIO_IRQ4_pending; + static uint8_t __xdata lastRSSI; + + RADIO_IRQ4_pending = 0; + + if (cause & 0x40) { // ack time happened + + // radio will report ACK if we (1) got an ack or (2) sent a packet that did not require it + mAckTimePassed = true; + mGotAck = !!(cause & 0x10); + + } + if (cause & 0x20) { // radio has RXed a packet into its internal buffer. vet it quickly and set up DMA + + uint8_t len = RADIO_GOTLEN; + + if (len < 3 || len >= 0x80 || RADIO_rxFirstByte + 1 != len || !mRxOn || !mRxBufNumFree) { // detect invalid packets right away, or RX being off + + RADIO_command = RADIO_CMD_FLUSH_RX_FIFO; + } else { + uint8_t __xdata *buf = mRxBufs[mRxBufNextW]; + uint8_t bkp; + + buf[0] = len; + + bkp = CFGPAGE; + CFGPAGE = 4; + RADIO_RXLEN = len - 1; + RADIO_RXPTRL = ((uint16_t)(buf + 1)) & 0xff; + RADIO_RXPTRH = ((uint16_t)(buf + 1)) >> 8; + TRIGGER |= 4; // start rx dma + + TCON2 &= ~4; // without this we trigger next irq too fast and get garbage + + CFGPAGE = bkp; + } + } + if ((cause & 0x10) && !(RADIO_curRfState & 0x20)) { // radio got a valid preamble and is RXing a packet. this is our chance to sample some RSSI + uint8_t i; + + // we get here if radio is RXing a packet - tells us to capture some RSSI vals. + // Seems there is an offset. Value is signed and offset by 56 + for (i = 0; i < 0x3c; i++) + mLastRSSI = RADIO_currentRSSI; + } +} + +void RF_IRQ2(void) __interrupt(5) { + uint8_t bck = CFGPAGE; + CFGPAGE = 4; + + if (TCON2 & 0x04) { // RX dma over - time to check packet for valid CRC + + uint8_t __xdata *buf = mRxBufs[mRxBufNextW]; + uint8_t len; + + TCON2 &= ~0x04; + RADIO_command = RADIO_CMD_FLUSH_RX_FIFO; + + // last byte we got DMAed to us has top bit as flags for "crc ok" + + len = buf[0]; + + if (!(buf[len] & 0x80)) { + // CRC failed on packet + } else { + buf[len - 1] = mLastRSSI - 56; + + if (++mRxBufNextW == RX_BUFFER_NUM) + mRxBufNextW = 0; + mRxBufNumFree--; + } + } + if (TCON2 & 0x02) { // TX DMA completed + TCON2 &= ~2; + + // nothing to do here + } + if (TCON2 & 0x08) { // radio init over + TCON2 &= ~0x48; + SETTINGS &= ~0x10; + } + CFGPAGE = bck; +} + +bool radioTx(const void __xdata *packetP) // waits for tx end +{ + const uint8_t __xdata *packet = (const uint8_t __xdata *)packetP; + uint16_t bkp, wait; + __bit irqs; + + // this is how to do CCA. we do not bother because fuck it + // this is how WE do CCA. 'Fuck it' still somewhat applies if we don't get a clear channel in a reasonable amount of time + // okay fuck it. + /* + for (uint8_t i = 0; i < 0x80; i++) { + if (!(RADIO_curRfState & 1)) { + //pr("radio CCA fail\n"); + timerDelay(TIMER_TICKS_PER_SECOND / 2000); + //return; + } + } + */ + + mAckTimePassed = false; + mGotAck = false; + mLastTxedSeq = packet[3]; + + bkp = CFGPAGE; + CFGPAGE = 4; + + irqs = IEN_EA; + IEN_EA = 0; + + RADIO_TXLEN = packet[0] - 2; + RADIO_TXPTRL = ((uint16_t)packet) & 0xff; + RADIO_TXPTRH = ((uint16_t)packet) >> 8; + RADIO_command = RADIO_CMD_LOAD_TX_FIFO; + TRIGGER |= 2; // start TX fifo DMA + + IEN_EA = irqs; + + CFGPAGE = bkp; + + //RADIO_unk_C8 = 0xff; /// stock fw does this but seems unnecessary + + // wait for tx to start + wait = 0; + wait--; + do { + if (RADIO_curRfState & 0x80) + break; + } while (--wait); + + // wait for tx to end + if (wait) { + while (!mAckTimePassed) + ; + return true; + } else { + return false; + } + + //RADIO_unk_C8 = 0x7f; /// stock fw does this but seems unnecessary +} + +void radioRxAckReset(void) { + mGotAck = false; +} + +int16_t radioRxAckGetLast(void) { + if (mGotAck) + return (uint16_t)mLastTxedSeq; + else + return -1; +} + +void radioRxFilterCfg(const uint8_t __xdata *filterForLong, uint32_t myShortMac, uint16_t myPan) { + uint16_t shortMac = (myShortMac == SHORT_MAC_UNUSED) ? 0xffff : myShortMac; + uint8_t i; + + RADIO_PANID_Hi = myPan >> 8; + RADIO_PANID_Lo = myPan; + + RADIO_ownShortAddress_Hi = shortMac >> 8; + RADIO_ownShortAddress_Lo = shortMac; + + for (i = 0; i < 8; i++) + ((volatile uint8_t __xdata *)&RADIO_ownMac_7)[i] = filterForLong[(uint8_t)((uint8_t)7 - (uint8_t)i)]; +} + +void radioRxEnable(__bit on, __bit autoAck) { + if (!autoAck) { + pr("auto ack forced for now\n"); + while (1) + ; + } + mRxOn = on; +} + +void radioSetTxPower(int8_t dBm) { + if (dBm < -27) + dBm = -27; + else if (dBm > 8) + dBm = 8; + + dBm += 27; + + RADIO_txPower = (uint8_t)(((uint8_t)dBm) + 3) / 5; +} + +void radioSetChannel(uint8_t ch) { + static const uint8_t perChannelSetting1[] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33, 0x33}; + static const uint8_t perChannelSetting2[] = {4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 2, 2, 2}; + + if (ch < RADIO_FIRST_CHANNEL || ch >= RADIO_FIRST_CHANNEL + RADIO_NUM_CHANNELS) + return; + + RADIO_channel = ch; // configmed to be at least RX channel + RADIO_command = RADIO_CMD_RECEIVE; + RADIO_perChannelSetting1 = perChannelSetting1[ch - 11]; + RADIO_perChannelSetting2 = perChannelSetting2[ch - 11]; +} + +void radioRxFlush(void) { + mRxBufNumFree = RX_BUFFER_NUM; +} + +int8_t radioRxDequeuePktGet(const void __xdata *__xdata *dstBufP, uint8_t __xdata *lqiP, int8_t __xdata *rssiP) { + const uint8_t __xdata *buf = mRxBufs[mRxBufNextR]; + uint8_t lqi, len = buf[0]; + + if (mRxBufNumFree == RX_BUFFER_NUM) + return -1; + + lqi = (buf[len] & 0x7f); + + *lqiP = lqi; + *rssiP = buf[len - 1]; + *dstBufP = buf + 1; + + return len - 2; +} + +void radioRxDequeuedPktRelease(void) { + if (++mRxBufNextR == RX_BUFFER_NUM) + mRxBufNextR = 0; + + __critical { + mRxBufNumFree++; + } +} + +void radioInit(void) { + uint8_t bkp; + + mRxBufNextW = 0; + mRxBufNextR = 0; + mRxBufNumFree = RX_BUFFER_NUM; + + timerDelay(TIMER_TICKS_PER_SECOND / 1000); + RADIO_unk_F0 |= 0x80; + timerDelay(TIMER_TICKS_PER_SECOND / 1000); + CFGPAGE = 0; + RESET &= ~4; + RESET |= 4; + RESET &= ~4; + timerDelay(TIMER_TICKS_PER_SECOND / 10000); + + RADIO_RadioPowerCtl |= 4; + + bkp = CFGPAGE; + CFGPAGE = 4; + SETTINGS |= 2; + RADIO_INITSEQ0 = 2; + RADIO_INITSEQ1 = 0xFA; + RADIO_INITSEQ2 = 0xDD; + SETTINGS |= 4; + RADIO_INITSEQ3 = 1; + RADIO_INITSEQ4 = 0xFA; + RADIO_INITSEQ5 = 0xDD; + IEN_RF2 = 1; + CFGPAGE = bkp; + + RADIO_command = 0xC1; + + RADIO_unk_C1 = 0x02; + RADIO_calibration_C2 = 0xf7; // mdmctrl0L? + RADIO_calibration_C3 = 0x05; + RADIO_calibration_C4 = 0x35; + RADIO_calibration_C5 = 0x24; + RADIO_calibration_C6 = 0x33; + RADIO_calibration_C7 = 0x70; + RADIO_unk_CA = 0x58; + RADIO_perChannelSetting2 = 0x02; + RADIO_unk_CD = (RADIO_unk_CD & ~7) | (0x11 & 7); + RADIO_txPower = 0; + RADIO_calibration_CF = 0x30; + RADIO_calibration_D0 = 0x00; + RADIO_calibration_D1 = 0x49; + RADIO_calibration_D2 = 0x06; + RADIO_unk_D7 = 0x43; + RADIO_unk_E2 = 0x08; // setting bit 0x40 breaks rx entirely + RADIO_unk_83 = 0xe5; // maybe sync word? + + RADIO_unk_C8 = 0x7f; // setting bit 0x80 breaks rx entirely + RADIO_calibration_81 = 0xf0; // removing bit 0x20 breask rx and tx + RADIO_FLAGS |= 0x08; + RADIO_unk_D8 = 0; + RADIO_calibration_9D = 0x3f; // firts untpouched calib val + RADIO_calibration_A1 = 0x04; + RADIO_calibration_94 = 0x7f; // IOCFG0 ? + RADIO_unk_CE = 0x55; + RADIO_calibration_D3 = 0x30; + RADIO_calibration_D4 = 0xcc; + RADIO_calibration_D5 = 0xf6; + RADIO_calibration_86 = 0x40; // setting this to values like 0x20, 0x80, and 0x60 breaks auto-ack and maybe tx + RADIO_calibration_95 = 0x08; + RADIO_calibration_96 = 0xd3; + RADIO_calibration_70 = 0x01; + RADIO_calibration_71 = 0x40; + + RADIO_unk_A6 = 0x00; + RADIO_command = 0xC8; + RADIO_command = 0xC7; + RADIO_command = 0xC6; + RADIO_unk_AF = 0x60; // int enable (we need 0x60) + + RADIO_unk_AF |= 0x10; + + IEN_RF1 = 1; + + RADIO_FLAGS |= 8; +} diff --git a/ap_fw/soc/zbs243/radio.h b/ap_fw/soc/zbs243/radio.h new file mode 100644 index 000000000..d59227819 --- /dev/null +++ b/ap_fw/soc/zbs243/radio.h @@ -0,0 +1,22 @@ +#ifndef _RADIO_H_ +#define _RADIO_H_ + +#include +#include + + +void RF_IRQ1(void) __interrupt (4); +void RF_IRQ2(void) __interrupt (5); +#define RADIO_PAD_LEN_BY 2 + +#include "../radioCommon.h" + + + + + +#endif + + + + diff --git a/ap_fw/soc/zbs243/sleep.c b/ap_fw/soc/zbs243/sleep.c new file mode 100644 index 000000000..fa7acae3e --- /dev/null +++ b/ap_fw/soc/zbs243/sleep.c @@ -0,0 +1,66 @@ +#include "sleep.h" +#include "cpu.h" + +void sleepForMsec(uint32_t length) +{ + __bit irqEn = IEN_EA; + uint8_t prescaler, cfgPg; + IEN_EA = 0; + + if (!length) + length = 0xfffffffful; + + RADIO_IRQ4_pending = 0; + UNK_C1 &=~ 0x81; + TCON &=~ 0x20; + cfgPg = CFGPAGE; + CFGPAGE = 4; + RADIO_command = 0xCA; + RADIO_command = 0xC5; + + if (length <= 0x00008000ul) { + + length <<= 5; + prescaler = 0x56; //0x56 = one tick is 1/32k of sec + } + else { + if (length != 0xfffffffful) + length += 500; + + length /= 1000; + prescaler = 0x16; //0x16 = one tick is 1 second + } + + if (length > 0x000fffff) { + + RADIO_SleepTimerLo = 0xff; + RADIO_SleepTimerMid = 0xff; + RADIO_SleepTimerHi = 0x0f; + } + else { + + RADIO_SleepTimerLo = length; + RADIO_SleepTimerMid = length >> 8; + RADIO_SleepTimerHi = ((uint8_t)(length >> 16)) & 0x0f; + } + + __asm__("nop"); + RADIO_SleepTimerSettings = prescaler; + __asm__("nop\nnop\nnop\nnop\n"); + RADIO_SleepTimerSettings |= 0x80; + __asm__("nop\nnop\n"); + RADIO_RadioPowerCtl = 0x44; + __asm__("nop\nnop\n"); + + CFGPAGE = cfgPg; + + //make sure time does not run backwards + TL0 = 0x0; + TH0 = 0xFF; + while (TH0 == 0xFF); + + UNK_C1 |= 0x81; + TCON |= 0x20; + + IEN_EA = irqEn; +} \ No newline at end of file diff --git a/ap_fw/soc/zbs243/soc.c b/ap_fw/soc/zbs243/soc.c new file mode 100644 index 000000000..36ab6f4e7 --- /dev/null +++ b/ap_fw/soc/zbs243/soc.c @@ -0,0 +1,12 @@ +#include "asmUtil.h" +#include "printf.h" +#include "screen.h" +#include "cpu.h" + + +void clockingAndIntsInit(void) +{ + IEN0 = 0; + CLKEN = 0x00; //timers only for now + CLKSPEED = 0x01; //fast crystal +} \ No newline at end of file diff --git a/ap_fw/soc/zbs243/soc.h b/ap_fw/soc/zbs243/soc.h new file mode 100644 index 000000000..bc8fd3db1 --- /dev/null +++ b/ap_fw/soc/zbs243/soc.h @@ -0,0 +1,29 @@ +#ifndef _SOCi_H_ +#define _SOCi_H_ + +#define PDATA +#include "zbs243.h" + +#include + + +#pragma callee_saves clockingAndIntsInit +void clockingAndIntsInit(void); + +#pragma callee_saves rndGen8 +uint8_t rndGen8(void); + +#pragma callee_saves rndGen32 +uint32_t rndGen32(void); + +#pragma callee_saves rndSeed +void rndSeed(uint8_t seedA, uint8_t seedB); + +#pragma callee_saves selfUpdate +void selfUpdate(void); + + +void TEMP_ISR(void) __interrupt (10); + + +#endif diff --git a/ap_fw/soc/zbs243/spi.c b/ap_fw/soc/zbs243/spi.c new file mode 100644 index 000000000..c4088d4bf --- /dev/null +++ b/ap_fw/soc/zbs243/spi.c @@ -0,0 +1,32 @@ +#include "spi.h" +#include "cpu.h" + + +void spiInit(void) +{ + uint8_t bcp; + + //clock it up + CLKEN |= 0x08; + + //enable the unit + bcp = CFGPAGE; + CFGPAGE = 4; + SPIENA = 0x81; + CFGPAGE = bcp; +} + +uint8_t spiByte(uint8_t val) +{ + uint8_t bcp = CFGPAGE; + CFGPAGE = 4; + + SPITX = val; + SPICFG = 0xa0; //spi at 4mhz, mode 0 + while(SPICFG & 0x20); + val = SPIRX; + + CFGPAGE = bcp; + + return val; +} \ No newline at end of file diff --git a/ap_fw/soc/zbs243/spi.h b/ap_fw/soc/zbs243/spi.h new file mode 100644 index 000000000..b869e4a9d --- /dev/null +++ b/ap_fw/soc/zbs243/spi.h @@ -0,0 +1,16 @@ +#ifndef _SPI_ZBS_H_ +#define _SPI_ZBS_H_ + +#include + +//pre-configured for 4mhz mode 0, but can be changed + +#pragma callee_saves spiInit +void spiInit(void); + +#pragma callee_saves spiByte +uint8_t spiByte(uint8_t val); + + + +#endif diff --git a/ap_fw/soc/zbs243/temperature.c b/ap_fw/soc/zbs243/temperature.c new file mode 100644 index 000000000..f51b492fd --- /dev/null +++ b/ap_fw/soc/zbs243/temperature.c @@ -0,0 +1,113 @@ +#include "asmUtil.h" +#include "flash.h" +#include "timer.h" +#include "adc.h" +#include "cpu.h" + +static volatile uint8_t __xdata mTempRet[4]; + + +void TEMP_ISR(void) __interrupt (10) +{ + uint8_t i; + + i = CFGPAGE; + CFGPAGE = 4; + mTempRet[0] = TEMPRETH; + mTempRet[1] = TEMPRETL; + CFGPAGE = i; + IEN1 &=~ 0x10; +} + +int8_t adcSampleTemperature(void) +{ + uint16_t sum = 0; + uint8_t i; + + CLKEN |= 0x80; + + //adcConfig + i = CFGPAGE; + CFGPAGE = 4; + TEMPCFG = 0x81; //0x08 set when done + TEMPCAL2 = 0x22; + TEMPCAL1 = 0x55; + TEMPCAL4 = 0; + TEMPCAL3 = 0; + TEMPCAL6 = 3; + TEMPCAL5 = 0xff; + TEMPCFG &=~ 0x08; + CFGPAGE = i; + IEN1 &=~ 0x10; + //adcConfig over + + /* + TEMPCAL2: (seemingly analog settings: gain + ???) + removing 0x02 lowers vals a little (9%) \__ these stack just as you'd expect + setting 0x04 moves the values up a bit (18%) / + setting 0x08 adds 2.5% or so + 0x30 is ADC gain/bitness. (x4 for each increment) + 0x80 seems to make no difference + + TEMPCAL1: (seemingly analog settings) + removing 1 decreases value by 5.7% \__ these stack + removing 4 decreases by 22% / + removing 0x10 increments value by 1% + removnig 0x40 increases by 5% + + using 0xaa increases width but decreases average + + other TEMPCALx values seem to have no effect + */ + + for (i = 0; i < 9; i++) { + + //int on + IEN1 |= 0x10; + + //wait for it to self-turn-off + while (IEN1 & 0x10); + + if (i) { //skip first + + sum += ((uint16_t)mathPrvU8bitswap(mTempRet[0])) << 2; + if (mTempRet[1] & 1) + sum += 2; + if (mTempRet[1] & 2) + sum += 1; + } + + timerDelay(TIMER_TICKS_PER_SECOND / 10000); + } + //turn it off + CLKEN &=~ 0x80; + + //reuse tempRet to get calib data + if (!flashRead(FLASH_INFOPAGE_ADDR + 0x0b, mTempRet, 4) || (mTempRet[0] == 0xff && mTempRet[1] == 0xff) || (mTempRet[2] == 0xff && mTempRet[3] == 0xff)) { + + //no calibration data - reporting 20 degrees, same as stock firmware + return 20; + } + else { + + uint16_t __xdata valB = mathPrvU16from2xU8(mTempRet[0], mTempRet[1]); //temp sensor reading at 30 degrees C + uint16_t __xdata valD = mathPrvU16from2xU8(mTempRet[2], mTempRet[3]); //temp sensor reading at 45 degrees C + uint16_t __xdata slope = valD - valB; + + sum = (sum + 4) / 8; //average it + + //we can get 1/10 degree accuracy as this is well calibrated, but our api only need degrees so let's do that + /* + //high-precision code is this: + if (sum > valB) //our math routines are unsigned + temp = (uint16_t)mathPrvDiv32x16(mathPrvMul16x16(sum - valB, 150) + (slope / 2), slope) + 300; + else + temp = 300 - (uint16_t)mathPrvDiv32x16(mathPrvMul16x16(valB - sum, 150) + (slope / 2), slope); + */ + + if (sum > valB) //our math routines are unsigned + return mathPrvDiv16x8(5 + (uint16_t)mathPrvDiv32x16(mathPrvMul16x16(sum - valB, 150) + (slope / 2), slope), 10) + 30; + else + return 30 - mathPrvDiv16x8(5 + (uint16_t)mathPrvDiv32x16(mathPrvMul16x16(valB - sum, 150) + (slope / 2), slope), 10); + } +} \ No newline at end of file diff --git a/ap_fw/soc/zbs243/timer.c b/ap_fw/soc/zbs243/timer.c new file mode 100644 index 000000000..3f0d9ec13 --- /dev/null +++ b/ap_fw/soc/zbs243/timer.c @@ -0,0 +1,65 @@ +#include "timer.h" +#include "cpu.h" + + +static volatile uint16_t mTmrHi; + +void T0_ISR(void) __interrupt (1) +{ + TCON &=~ 0x20; //clear flag + mTmrHi++; +} + +uint32_t timerGet(void) +{ + union { + struct { + uint8_t tL; + uint8_t tH; + uint16_t hi; + }; + uint32_t ret; + } val; + + do { + val.hi = mTmrHi; + val.tH = TH0; + val.tL = TL0; //read order is important due ot hardware buffering + } while (val.hi != mTmrHi || val.tH != TH0); + + return val.ret; +} + +uint8_t timerGetLowBits(void) +{ + return TL0; +} + +void timerInit(void) +{ + //clock up timers + CLKEN |= 0x01; + + + //stop and clear int flags + TCON &=~ 0xf0; + + //reset + mTmrHi = 0; + T0 = 0; + + //timer 0 in 16 bit mode, timer 1 off + TMOD = 0x31; + + //start + TCON |= 0x10; + + //int on + IEN_TMR0 = 1; +} + +void timerDelay(uint32_t ticks) +{ + uint32_t start = timerGet(); + while (timerGet() - start <= ticks); +} diff --git a/ap_fw/soc/zbs243/timer.h b/ap_fw/soc/zbs243/timer.h new file mode 100644 index 000000000..bce2b700c --- /dev/null +++ b/ap_fw/soc/zbs243/timer.h @@ -0,0 +1,24 @@ +#ifndef _TIMER_H_ +#define _TIMER_H_ + +#include + +#define TIMER_TICKS_PER_SECOND (16000000 / 12) //overflows every 53 minutes + +//this is a requirement by SDCC. is this prototype is missing when compiling main(), we get no irq handler +void T0_ISR(void) __interrupt (1); + + +void timerInit(void); + +#pragma callee_saves timerGet +uint32_t timerGet(void); + +#pragma callee_saves timerGetLowBits +uint8_t timerGetLowBits(void); //probaly only useful for random seeds + +void timerDelay(uint32_t ticks); + + + +#endif diff --git a/ap_fw/soc/zbs243/uart.c b/ap_fw/soc/zbs243/uart.c new file mode 100644 index 000000000..708347c3c --- /dev/null +++ b/ap_fw/soc/zbs243/uart.c @@ -0,0 +1,97 @@ +#include "uart.h" + +#include "cpu.h" +#include "stdbool.h" +#include "string.h" + +void uartInit(void) { + // clock it up + CLKEN |= 0x20; + + // configure + UARTBRGH = 0x00; // at the speeds we want, this reg is always 0 + + //UARTBRGL = 0x8A; // 115200 + UARTBRGL = 69; // nice. 230400 baud + // UARTBRGL = 0x1F; // 500kbaud, unreliable RX here... + // UARTBRGL = 39; //400kbaud? also unreliable... + // UARTBRGL = 49; // 320kbaud? nope. bummer. + UARTSTA = 0x12; // also set the "empty" bit else we wait forever for it to go up + IEN_UART0 = 1; +} + +extern uint8_t __xdata blockbuffer[]; +extern bool __xdata blockRequestInProgress; + +volatile uint8_t txtail = 0; +volatile uint8_t txhead = 0; +uint8_t __xdata txbuf[256] = {0}; + +volatile uint8_t __idata rxtail = 0; +volatile uint8_t __idata rxhead = 0; +uint8_t __xdata rxbuf[256] = {0}; + +void uartTx(uint8_t val) { + __critical { + txbuf[txhead] = val; + if (txhead == txtail) { + UARTBUF = val; + } + txhead++; + } +} + +uint8_t uartRx() { + if (rxhead == rxtail) { + return 0; + } else { + uint8_t ret = rxbuf[rxtail]; + rxtail++; + return ret; + } +} + +uint8_t uartBytesAvail() { + return rxhead - rxtail; +} + + + +uint8_t* __idata blockp; +uint8_t __idata cmd[3]; +bool __idata bypass = false; + +void checkcommand(uint8_t rx) { + for (uint8_t c = 0; c < 2; c++) { + cmd[c] = cmd[c + 1]; + } + cmd[2] = rx; + if (strncmp(cmd, ">D>", 3) == 0) { + blockp = blockbuffer; + bypass = true; + } +} + +void UART_IRQ1(void) __interrupt(0) { + if (UARTSTA & 1) { // RXC + UARTSTA &= 0xfe; + if (bypass) { + *blockp++ = UARTBUF; + if (blockp == (blockbuffer+4100)) { + bypass = false; + blockRequestInProgress = false; + } + } else { + rxbuf[rxhead] = UARTBUF; + rxhead++; + checkcommand(UARTBUF); + } + } + if (UARTSTA & 2) { // TXC + UARTSTA &= 0xfd; + txtail++; + if (txhead != txtail) { + UARTBUF = txbuf[txtail]; + } + } +} \ No newline at end of file diff --git a/ap_fw/soc/zbs243/uart.h b/ap_fw/soc/zbs243/uart.h new file mode 100644 index 000000000..109c89658 --- /dev/null +++ b/ap_fw/soc/zbs243/uart.h @@ -0,0 +1,25 @@ +#ifndef _UART_ZBS_H_ +#define _UART_ZBS_H_ + +#include + +//pre-configured for 115200 8n1 +//RX can be done but i do not need it + +#pragma callee_saves uartInit +void uartInit(void); + +#pragma callee_saves uartTx +void uartTx(uint8_t val); + +void UART_IRQ1(void) __interrupt (0); + +#pragma callee_saves uartBytesAvail +uint8_t uartBytesAvail(void); + +#pragma callee_saves uartRX +uint8_t uartRx(); + + +#endif + diff --git a/ap_fw/soc/zbs243/wdt.c b/ap_fw/soc/zbs243/wdt.c new file mode 100644 index 000000000..f34887d73 --- /dev/null +++ b/ap_fw/soc/zbs243/wdt.c @@ -0,0 +1,59 @@ +#include "cpu.h" +#include "wdt.h" + +void wdtOn(void) +{ + uint8_t cfgPageBck; + + cfgPageBck = CFGPAGE; + CFGPAGE = 4; + WDTCONF |= 0x80; + WDTENA = 1; + CFGPAGE = cfgPageBck; +} + +void wdtOff(void) +{ + uint8_t cfgPageBck; + + cfgPageBck = CFGPAGE; + CFGPAGE = 4; + WDTENA = 0; + WDTCONF &=~ 0x80; + CFGPAGE = cfgPageBck; +} + +void wdtPet(void) +{ + uint8_t cfgPageBck; + + cfgPageBck = CFGPAGE; + CFGPAGE = 4; + WDTPET = 0; + CFGPAGE = cfgPageBck; +} + +void wdtSetResetVal(uint32_t val) //also pets it +{ + uint8_t cfgPageBck; + + cfgPageBck = CFGPAGE; + CFGPAGE = 4; + WDTPET = 0; + WDTRSTVALH = val >> 16; + WDTRSTVALM = val >> 8; + WDTRSTVALL = val; + + CFGPAGE = cfgPageBck; +} + +void wdtDeviceReset(void) +{ + CFGPAGE = 4; + WDTCONF = 0x80; + WDTENA = 1; + WDTRSTVALH = 0xff; + WDTRSTVALM = 0xff; + WDTRSTVALL = 0xff; + while(1); +} \ No newline at end of file diff --git a/ap_fw/soc/zbs243/zbs243.h b/ap_fw/soc/zbs243/zbs243.h new file mode 100644 index 000000000..88315e3b1 --- /dev/null +++ b/ap_fw/soc/zbs243/zbs243.h @@ -0,0 +1,289 @@ +#ifndef _ZBS243_H_ +#define _ZBS243_H_ + +#include + +//P2 selects XPAGE which sucks for us +//IRAM is not at all mapped into XRAM +//id page is not in xram either + + +static __idata __at (0x00) unsigned char R0; +static __idata __at (0x01) unsigned char R1; +static __idata __at (0x02) unsigned char R2; +static __idata __at (0x03) unsigned char R3; +static __idata __at (0x04) unsigned char R4; +static __idata __at (0x05) unsigned char R5; +static __idata __at (0x06) unsigned char R6; +static __idata __at (0x07) unsigned char R7; +__sfr __at (0xf0) B; +__sfr __at (0xe0) ACC; +__sfr __at (0x82) DPL; +__sfr __at (0x83) DPH; +__sfr __at (0x84) DPL1; +__sfr __at (0x85) DPH1; +__sfr __at (0x92) DPS; //low bit switched DPTR +__sfr __at (0x87) PCON; //lower 4 bits as per 8051 spec, bit4 set disables xram + +__sfr __at (0xb2) PERFMON0; +__sfr __at (0xb3) PERFMON1; +__sfr __at (0xb4) PCH; //current PC...yes... +__sfr __at (0xb5) PCL; +__sfr __at (0xb6) PERFMON4; + + + +__sbit __at (0x80) P0_0; +__sbit __at (0x81) P0_1; +__sbit __at (0x82) P0_2; +__sbit __at (0x83) P0_3; +__sbit __at (0x84) P0_4; +__sbit __at (0x85) P0_5; +__sbit __at (0x86) P0_6; +__sbit __at (0x87) P0_7; +__sbit __at (0x90) P1_0; +__sbit __at (0x91) P1_1; +__sbit __at (0x92) P1_2; +__sbit __at (0x93) P1_3; +__sbit __at (0x94) P1_4; +__sbit __at (0x95) P1_5; +__sbit __at (0x96) P1_6; +__sbit __at (0x97) P1_7; +__sbit __at (0xa0) P2_0; +__sbit __at (0xa1) P2_1; +__sbit __at (0xa2) P2_2; +__sbit __at (0xa3) P2_3; +__sbit __at (0xa4) P2_4; +__sbit __at (0xa5) P2_5; +__sbit __at (0xa6) P2_6; +__sbit __at (0xa7) P2_7; + +__sbit __at (0xA8) IEN_UART0; +__sbit __at (0xA9) IEN_TMR0; +__sbit __at (0xAB) IEN_TMR1; +__sbit __at (0xAC) IEN_RF1; //enable for RF interrupt #1 (irq #2) +__sbit __at (0xAD) IEN_RF2; //enable for RF interrupt #2 (irq #5) +__sbit __at (0xAF) IEN_EA; + +//gpio configs (all in cfg page 0 only) +__sfr __at (0x80) P0; //RO for input pins, WO for output pins (written state not read back) +__sfr __at (0x90) P1; +__sfr __at (0xA0) P2; //disabled ot avoid accidental use +__sfr __at (0xA3) P0LVLSEL; //pin change interrupt edge desired. 0 - rising 1 - falling +__sfr __at (0xA4) P1LVLSEL; +__sfr __at (0xA5) P2LVLSEL; +__sfr __at (0xA6) P0INTEN; //pin change interrupt enable (1 = enabled). will wake device from sleep +__sfr __at (0xA7) P1INTEN; +__sfr __at (0xA9) P2INTEN; +__sfr __at (0xAA) P0CHSTA; //pin change status 1 = changed (remember to clear in ISR & before enabling ISR) +__sfr __at (0xAB) P1CHSTA; +__sfr __at (0xAC) P2CHSTA; +__sfr __at (0xAD) P0FUNC; //1 = func, 0 = gpio +__sfr __at (0xAE) P1FUNC; +__sfr __at (0xAF) P2FUNC; +__sfr __at (0xB9) P0DIR; //1 = in, 0 = out +__sfr __at (0xBA) P1DIR; +__sfr __at (0xBB) P2DIR; +__sfr __at (0xBC) P0PULL; //1 = pull up +__sfr __at (0xBD) P1PULL; +__sfr __at (0xBE) P2PULL; + +__sfr __at (0xA0) XPAGE; //sadly this is accurate! same as P2 + +//these are both avail in both config pages +__sfr __at (0xA8) IEN0; +__sfr __at (0xA1) IEN1; //low 1, bits unknown, top 7 bits eable irqs (7..13) + + +__sfr __at (0x8E) CLKSPEED; //top bit likely selects osc (1 = crystal??), next 3 set divisor FOR CPU, not periphs, bottom 4 unknown + +__sfr __at (0xB7) CLKEN; //one bit per periph to enable clocks. 0x08 - spi, 0x10 - i2c, 0x20 - uart, 0x80 temp sensor, 0x01 - in-cpu-timers(T0 & T1) + +__sfr __at (0x8F) RESET; //resets periphs AND stores reset cause. bit 0x01 - we were reset due to WDT, 0x04 - resets radio (Active high), 0x02 resets cpu core + +//wdt (accessible in CFG page 4), runs at about 62KHz. counter counts up, overflow causes reset +__sfr __at (0xBA) WDTENA; //low bit is used for WDT off. same SFR addr used as P1DIR when cfg page 0 +__sfr __at (0xBB) WDTPET; //write 0 to pet, write 0xff to reset immediately. same SFR addr used as P2DIR when cfg page 0 +__sfr __at (0xBC) WDTRSTVALL; //wdt reset value low +__sfr __at (0xBD) WDTRSTVALM; //wdt reset value mid +__sfr __at (0xBE) WDTRSTVALH; //wdt reset value high +__sfr __at (0xBF) WDTCONF; //top bit enable, 0x08 to hang instead of reset. top bit only accessible in CFG page 4 + + +//SPI (accessible in CFG page 4) +__sfr __at (0xEB) SPIUNKNOWN; //existing code sets 0x08 after a byte tx. no idea why. not doing it works +__sfr __at (0xEC) SPICFG; //0x80 - 1, 0x40 - 0, 0x20 - do TX (cleared on done), {0x10,0x08}) - clock div {4mhz,2mhz,1mhz,500khz}, 0x04 - CPHA, 0x02 - CPOL, 0x01 ?? (breaks rx, maybe bidirectional or rx off) +__sfr __at (0xED) SPIENA; //0x80 - 1, 0x01 - unit enable. stock code also enables/disables after every byte but i do not think we need to +__sfr __at (0xEE) SPITX; //tx buffer +__sfr __at (0xEF) SPIRX; //rx buffer + +//I2C (accessible in CFG page 0) +//it is in theory possible to drive it without IRQs but there seem to be hw bugs in figuring out when handling is needed, so i wouldn't. stock also doesn't +__sfr __at (0x91) I2CSTATE; //top 5 bits are current state: {???, START_SENT, RESTART_SENT, WR_ADDR_ACKED, WR_ADDR_NAKED, TX_BYTE_ACKED, TX_BYTE_NAKED, unused, RD_ADDR_ACKED, RD_ADDR_NAKED, RXED_BYTE} +//0x93 written by 0xe0 by stock but seems not needed +__sfr __at (0x94) I2CBUF; //data for RX/TX +__sfr __at (0x95) I2CCTL; //0x80 = irqOn (#8), 0x40 = master(?), 0x20 = start/restart, 0x10 = stop, 0x08 = irqReqd, 0x04 = sendAck (to RXed btes) +__sfr __at (0x96) I2CSPEED; //bottom 3 bits = dividerA. next 4 are dividerB. clock = 16MHz / ((dividerB ? 10 *(1 + dividerB) : 12) << dividerA) +__sfr __at (0xA2) I2CUNKNOWN; //4 ORRED at init or it will not work + + +//uart is basically hard wired to be a uart. clock is divided from 16MHz. only bottom 4 bits of BRGH are used. divisor is (BRGH:BRGL + 1) +__sbit __at (0x98) UART_RXF; +__sbit __at (0x99) UART_TXE; +__sfr __at (0x98) UARTSTA; //0x10 needed. 0x02 - have tx space, 0x01 - have rx byte. both can cause interrupts. both need to be cleared in irq. flag bits do NOT work if accessed using "jbc" +__sfr __at (0x99) UARTBUF; //fifo access +__sfr __at (0x9A) UARTBRGL; +__sfr __at (0x9B) UARTBRGH; //divisor for uart + +//timers (same as 8051-classic) +__sfr __at (0x88) TCON; +__sfr __at (0x89) TMOD; +__sfr __at (0x8A) TL0; +__sfr __at (0x8B) TL1; +__sfr __at (0x8C) TH0; +__sfr __at (0x8D) TH1; +__sfr16 __at (0x8C8A) T0; +__sfr16 __at (0x8D8B) T1; //used by timer code for storage + +//flash writing (must be done with CFGPAGE == 4). each page is 0x400 bytes +__sfr __at (0xD8) FPGNO; //page number (0x00..0x3f for main flash, 0x80 for info block) +__sfr __at (0xD9) FWRSRCL; //(flash source data for write).lo for write, (flash start offset in page).lo for read +__sfr __at (0xDA) FWRSRCH; //(flash source data for write).hi for write, (flash start offset in page).hi for read +__sfr __at (0xDB) FWRDSTL; //(flash start offset in page).lo for write, (dest xram addr).lo for read +__sfr __at (0xDC) FWRDSTH; //(flash start offset in page).hi for write, (dest xram addr).hi for read +__sfr __at (0xDD) FWRLENL; //(num bytes to write minus 1).lo +__sfr __at (0xDE) FWRLENH; //(num bytes to write minus 1).hi +__sfr __at (0xDF) FWRTHREE; //always written to three + + +//temp sensor (CLKEN bit 0x80). these only accessible in config page 4 +__sfr __at (0xE6) TEMPCAL1; //purpose guessed +__sfr __at (0xE7) TEMPCAL2; //purpose guessed + +__sfr __at (0xF7) TEMPCFG; //purpose guessed + +__sfr __at (0xF8) TEMPRETH; //has high 8 bits of result. bitswapped +__sfr __at (0xF9) TEMPRETL; //has low two bits of result in lower 2 bits. bitswapped + +__sfr __at (0xFB) TEMPCAL3; //purpose guessed +__sfr __at (0xFC) TEMPCAL4; //purpose guessed +__sfr __at (0xFD) TEMPCAL5; //purpose guessed +__sfr __at (0xFE) TEMPCAL6; //purpose guessed + +//radio regs (cfg page 4) +__sfr __at (0xC9) RADIO_TXPTRL; //xram pointer to tx buffer's "length" byte. low byte +__sfr __at (0xCA) RADIO_TXPTRH; //xram pointer to tx buffer's "length" byte. high byte +__sfr __at (0xCB) RADIO_INITSEQ1; +__sfr __at (0xCC) RADIO_INITSEQ2; +__sfr __at (0xCD) RADIO_TXLEN; //data bytes to send (not incl "len" byte or crc) +__sfr __at (0xCE) RADIO_INITSEQ0; +__sfr __at (0xD1) RADIO_INITSEQ4; +__sfr __at (0xD2) RADIO_INITSEQ5; +__sfr __at (0xD3) RADIO_RXPTRL; //xram pointer to tx buffer's "length" byte. low byte +__sfr __at (0xD4) RADIO_RXPTRH; //xram pointer to tx buffer's "length" byte. high byte +__sfr __at (0xD5) RADIO_RXLEN; //data bytes to RX (not incl "len" byte or crc) 2 more than this will be RXed +__sfr __at (0xD6) RADIO_INITSEQ3; + +//radio regs (any cfg page) +__sfr __at (0xFA) RADIO_GOTLEN; //size packet we GOT (incl len byte) + + + +//shared regs +__sfr __at (0xC1) UNK_C1; //in cfg page 4 only, purpose unknown + +__sfr __at (0xD7) TRIGGER; //in both cfg page, seach bit starts something. 0x08 starts flash op, 0x04 - start radio rx fifo dma, 0x02 - start radio tx fifo dma + + +//in cfg page 4 +__sfr __at (0xC7) SETTINGS; //misc settings incl flasg access. 0x18 is flash write, 0x08 is flash read, +__sfr __at (0xCF) TCON2; //interrupt flags (0x08 is flash write) + + + +__sfr __at (0xFF) CFGPAGE; //partially swaps SFR set to another. only 0x04 bit is used + + +/* + 84 85 92 B2 B3 B4 B5 B6 B8 are visible on both pages + + 93 98 9D 9E 9F B9 C1-C7 E0-E9 are swapped + + 86 9C B0 B1 C0 C8 EA F1-F6 ?????? + +*/ + + + +//radio MMIO + +static __xdata __at (0xdf48) unsigned char RADIO_command; +static __xdata __at (0xdf70) unsigned char RADIO_calibration_70; +static __xdata __at (0xdf71) unsigned char RADIO_calibration_71; +static __xdata __at (0xdf80) unsigned char RADIO_FLAGS; +static __xdata __at (0xdf81) unsigned char RADIO_calibration_81; +static __xdata __at (0xdf83) unsigned char RADIO_unk_83; +static __xdata __at (0xdf84) unsigned char RADIO_currentRSSI; +static __xdata __at (0xdf86) unsigned char RADIO_calibration_86; +static __xdata __at (0xdf88) unsigned char RADIO_ownMac_7; +static __xdata __at (0xdf89) unsigned char RADIO_ownMac_6; +static __xdata __at (0xdf8a) unsigned char RADIO_ownMac_5; +static __xdata __at (0xdf8b) unsigned char RADIO_ownMac_4; +static __xdata __at (0xdf8c) unsigned char RADIO_ownMac_3; +static __xdata __at (0xdf8d) unsigned char RADIO_ownMac_2; +static __xdata __at (0xdf8e) unsigned char RADIO_ownMac_1; +static __xdata __at (0xdf8f) unsigned char RADIO_ownMac_0; +static __xdata __at (0xdf90) unsigned char RADIO_PANID_Hi; +static __xdata __at (0xdf91) unsigned char RADIO_PANID_Lo; +static __xdata __at (0xdf92) unsigned char RADIO_ownShortAddress_Hi; +static __xdata __at (0xdf93) unsigned char RADIO_ownShortAddress_Lo; +static __xdata __at (0xdf94) unsigned char RADIO_calibration_94; +static __xdata __at (0xdf95) unsigned char RADIO_calibration_95; +static __xdata __at (0xdf96) unsigned char RADIO_calibration_96; +static __xdata __at (0xdf98) unsigned char RADIO_rxFirstByte; // first byte of the message actualyl RXed +static __xdata __at (0xdf9b) unsigned char RADIO_curRfState; // RFSTATUS ? +static __xdata __at (0xdf9d) unsigned char RADIO_calibration_9D; +static __xdata __at (0xdfa1) unsigned char RADIO_calibration_A1; +static __xdata __at (0xdfa5) unsigned char RADIO_unk_rxAckSta; //in irqh4, when RADIO_IRQ4_pending has 0x40 set, this is 0x04 if we got ack, and 0x02 if not +static __xdata __at (0xdfa6) unsigned char RADIO_unk_A6; +static __xdata __at (0xdfad) unsigned char RADIO_IRQ4_pending; +static __xdata __at (0xdfaf) unsigned char RADIO_unk_AF; +static __xdata __at (0xdfc0) unsigned char RADIO_channel; +static __xdata __at (0xdfc1) unsigned char RADIO_unk_C1; +static __xdata __at (0xdfc2) unsigned char RADIO_calibration_C2; +static __xdata __at (0xdfc3) unsigned char RADIO_calibration_C3; +static __xdata __at (0xdfc4) unsigned char RADIO_calibration_C4; +static __xdata __at (0xdfc5) unsigned char RADIO_calibration_C5; +static __xdata __at (0xdfc6) unsigned char RADIO_calibration_C6; +static __xdata __at (0xdfc7) unsigned char RADIO_calibration_C7; +static __xdata __at (0xdfc8) unsigned char RADIO_unk_C8; +static __xdata __at (0xdfc9) unsigned char RADIO_txPower; // 0..7 for -5..+8 dBm +static __xdata __at (0xdfca) unsigned char RADIO_unk_CA; +static __xdata __at (0xdfcb) unsigned char RADIO_perChannelSetting2; //relevant for tx +static __xdata __at (0xdfcd) unsigned char RADIO_unk_CD; +static __xdata __at (0xdfce) unsigned char RADIO_unk_CE; +static __xdata __at (0xdfcf) unsigned char RADIO_calibration_CF; +static __xdata __at (0xdfd0) unsigned char RADIO_calibration_D0; +static __xdata __at (0xdfd1) unsigned char RADIO_calibration_D1; +static __xdata __at (0xdfd2) unsigned char RADIO_calibration_D2; +static __xdata __at (0xdfd3) unsigned char RADIO_calibration_D3; +static __xdata __at (0xdfd4) unsigned char RADIO_calibration_D4; +static __xdata __at (0xdfd5) unsigned char RADIO_calibration_D5; +static __xdata __at (0xdfd7) unsigned char RADIO_unk_D7; +static __xdata __at (0xdfd8) unsigned char RADIO_unk_D8; +static __xdata __at (0xdfd9) unsigned char RADIO_SleepTimerHi; //only bottom 4 bits implemented, counts down to wake +static __xdata __at (0xdfda) unsigned char RADIO_SleepTimerMid; +static __xdata __at (0xdfdb) unsigned char RADIO_SleepTimerLo; +static __xdata __at (0xdfe2) unsigned char RADIO_unk_E2; +static __xdata __at (0xdff0) unsigned char RADIO_unk_F0; +static __xdata __at (0xdff3) unsigned char RADIO_SleepTimerSettings; //0x16 for one second tick, 0x56 for 1/32k second tick +static __xdata __at (0xdff4) unsigned char RADIO_RadioPowerCtl; +static __xdata __at (0xdffd) unsigned char RADIO_perChannelSetting1; //relevant fo rRX + +#define RADIO_CMD_RECEIVE 0xc2 //tx always goes to RX anyways +#define RADIO_CMD_FLUSH_RX_FIFO 0xc6 +#define RADIO_CMD_FLUSH_TX_FIFO 0xc7 +#define RADIO_CMD_LOAD_TX_FIFO 0xcb + + +#endif diff --git a/ap_fw/wdt.h b/ap_fw/wdt.h new file mode 100644 index 000000000..b6441ef44 --- /dev/null +++ b/ap_fw/wdt.h @@ -0,0 +1,23 @@ +#ifndef _WDT_H_ +#define _WDT_H_ + +#include + + +#pragma callee_saves wdtOn +void wdtOn(void); + +#pragma callee_saves wdtOff +void wdtOff(void); + +#pragma callee_saves wdtPet +void wdtPet(void); + +#pragma callee_saves wdtSetResetVal +void wdtSetResetVal(uint32_t val); //speed is CPU-specific. On ZBS it is 62KHz or so + +#pragma callee_saves wdtDeviceReset +void wdtDeviceReset(void); + + +#endif diff --git a/esp32_fw/.clang-format b/esp32_fw/.clang-format new file mode 100644 index 000000000..e2b830c76 --- /dev/null +++ b/esp32_fw/.clang-format @@ -0,0 +1,4 @@ +Language: Cpp +BasedOnStyle: Google +IndentWidth: 4 +ColumnLimit: 0 \ No newline at end of file diff --git a/esp32_fw/.gitignore b/esp32_fw/.gitignore new file mode 100644 index 000000000..89cc49cbd --- /dev/null +++ b/esp32_fw/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/esp32_fw/.vscode/extensions.json b/esp32_fw/.vscode/extensions.json new file mode 100644 index 000000000..080e70d08 --- /dev/null +++ b/esp32_fw/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/esp32_fw/.vscode/settings.json b/esp32_fw/.vscode/settings.json new file mode 100644 index 000000000..5f294ebf1 --- /dev/null +++ b/esp32_fw/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "C_Cpp.clang_format_fallbackStyle": "{ BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 0}" +} \ No newline at end of file diff --git a/esp32_fw/data/index.html b/esp32_fw/data/index.html new file mode 100644 index 000000000..022390a98 --- /dev/null +++ b/esp32_fw/data/index.html @@ -0,0 +1,65 @@ + + + + + + +Solum - alternative proto AP + + + +
+ +
+ +
+ +
    + + +
    + + + + +
    +
    + DST: + Filename: + +
    +
    + +
    +
    + DST: + Filename: + +
    +
    + +
    +
    + DST: + +
    +
    + +
    +
      +
    + +
    +
    + + +
    + + + + + + + + + \ No newline at end of file diff --git a/esp32_fw/data/jquery.js b/esp32_fw/data/jquery.js new file mode 100644 index 000000000..ee381345c --- /dev/null +++ b/esp32_fw/data/jquery.js @@ -0,0 +1,6 @@ +/*! jQuery v2.0.3 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery.min.map +*/ +(function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],p="2.0.3",f=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:p,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return f.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,p,f,h,d,g,m,y,v="sizzle"+-new Date,b=e.document,w=0,T=0,C=st(),k=st(),N=st(),E=!1,S=function(e,t){return e===t?(E=!0,0):0},j=typeof undefined,D=1<<31,A={}.hasOwnProperty,L=[],q=L.pop,H=L.push,O=L.push,F=L.slice,P=L.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",W="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",$=W.replace("w","w#"),B="\\["+M+"*("+W+")"+M+"*(?:([*^$|!~]?=)"+M+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+$+")|)|)"+M+"*\\]",I=":("+W+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+B.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=RegExp("^"+M+"*,"+M+"*"),X=RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=RegExp(M+"*[+~]"),Y=RegExp("="+M+"*([^\\]'\"]*)"+M+"*\\]","g"),V=RegExp(I),G=RegExp("^"+$+"$"),J={ID:RegExp("^#("+W+")"),CLASS:RegExp("^\\.("+W+")"),TAG:RegExp("^("+W.replace("w","w*")+")"),ATTR:RegExp("^"+B),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:RegExp("^(?:"+R+")$","i"),needsContext:RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Q=/^[^{]+\{\s*\[native \w/,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Z=/^(?:input|select|textarea|button)$/i,et=/^h\d$/i,tt=/'|\\/g,nt=RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),rt=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{O.apply(L=F.call(b.childNodes),b.childNodes),L[b.childNodes.length].nodeType}catch(it){O={apply:L.length?function(e,t){H.apply(e,F.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function ot(e,t,r,i){var o,s,a,u,l,f,g,m,x,w;if((t?t.ownerDocument||t:b)!==p&&c(t),t=t||p,r=r||[],!e||"string"!=typeof e)return r;if(1!==(u=t.nodeType)&&9!==u)return[];if(h&&!i){if(o=K.exec(e))if(a=o[1]){if(9===u){if(s=t.getElementById(a),!s||!s.parentNode)return r;if(s.id===a)return r.push(s),r}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(a))&&y(t,s)&&s.id===a)return r.push(s),r}else{if(o[2])return O.apply(r,t.getElementsByTagName(e)),r;if((a=o[3])&&n.getElementsByClassName&&t.getElementsByClassName)return O.apply(r,t.getElementsByClassName(a)),r}if(n.qsa&&(!d||!d.test(e))){if(m=g=v,x=t,w=9===u&&e,1===u&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(g=t.getAttribute("id"))?m=g.replace(tt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",l=f.length;while(l--)f[l]=m+mt(f[l]);x=U.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return O.apply(r,x.querySelectorAll(w)),r}catch(T){}finally{g||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,r,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>i.cacheLength&&delete t[e.shift()],t[n]=r}return t}function at(e){return e[v]=!0,e}function ut(e){var t=p.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function lt(e,t){var n=e.split("|"),r=e.length;while(r--)i.attrHandle[n[r]]=t}function ct(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return at(function(t){return t=+t,at(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}s=ot.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},n=ot.support={},c=ot.setDocument=function(e){var t=e?e.ownerDocument||e:b,r=t.defaultView;return t!==p&&9===t.nodeType&&t.documentElement?(p=t,f=t.documentElement,h=!s(t),r&&r.attachEvent&&r!==r.top&&r.attachEvent("onbeforeunload",function(){c()}),n.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ut(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=ut(function(e){return e.innerHTML="
    ",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),n.getById=ut(function(e){return f.appendChild(e).id=v,!t.getElementsByName||!t.getElementsByName(v).length}),n.getById?(i.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){return e.getAttribute("id")===t}}):(delete i.find.ID,i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=n.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.CLASS=n.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&h?t.getElementsByClassName(e):undefined},g=[],d=[],(n.qsa=Q.test(t.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll(":checked").length||d.push(":checked")}),ut(function(e){var n=t.createElement("input");n.setAttribute("type","hidden"),e.appendChild(n).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&d.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||d.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),d.push(",.*:")})),(n.matchesSelector=Q.test(m=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&ut(function(e){n.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",I)}),d=d.length&&RegExp(d.join("|")),g=g.length&&RegExp(g.join("|")),y=Q.test(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,r){if(e===r)return E=!0,0;var i=r.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(r);return i?1&i||!n.sortDetached&&r.compareDocumentPosition(e)===i?e===t||y(b,e)?-1:r===t||y(b,r)?1:l?P.call(l,e)-P.call(l,r):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],u=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:l?P.call(l,e)-P.call(l,n):0;if(o===s)return ct(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)u.unshift(r);while(a[i]===u[i])i++;return i?ct(a[i],u[i]):a[i]===b?-1:u[i]===b?1:0},t):p},ot.matches=function(e,t){return ot(e,null,null,t)},ot.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Y,"='$1']"),!(!n.matchesSelector||!h||g&&g.test(t)||d&&d.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return ot(t,p,null,[e]).length>0},ot.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},ot.attr=function(e,t){(e.ownerDocument||e)!==p&&c(e);var r=i.attrHandle[t.toLowerCase()],o=r&&A.call(i.attrHandle,t.toLowerCase())?r(e,t,!h):undefined;return o===undefined?n.attributes||!h?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null:o},ot.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ot.uniqueSort=function(e){var t,r=[],i=0,o=0;if(E=!n.detectDuplicates,l=!n.sortStable&&e.slice(0),e.sort(S),E){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return e},o=ot.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=ot.selectors={cacheLength:50,createPseudo:at,match:J,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(nt,rt),e[3]=(e[4]||e[5]||"").replace(nt,rt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ot.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ot.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return J.CHILD.test(e[0])?null:(e[3]&&e[4]!==undefined?e[2]=e[4]:n&&V.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(nt,rt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ot.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,y=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){p=t;while(p=p[g])if(a?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[v]||(m[v]={}),l=c[e]||[],h=l[0]===w&&l[1],f=l[0]===w&&l[2],p=h&&m.childNodes[h];while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[w,h,f];break}}else if(x&&(l=(t[v]||(t[v]={}))[e])&&l[0]===w)f=l[1];else while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if((a?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(x&&((p[v]||(p[v]={}))[e]=[w,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||ot.error("unsupported pseudo: "+e);return r[v]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?at(function(e,n){var i,o=r(e,t),s=o.length;while(s--)i=P.call(e,o[s]),e[i]=!(n[i]=o[s])}):function(e){return r(e,0,n)}):r}},pseudos:{not:at(function(e){var t=[],n=[],r=a(e.replace(z,"$1"));return r[v]?at(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:at(function(e){return function(t){return ot(e,t).length>0}}),contains:at(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:at(function(e){return G.test(e||"")||ot.error("unsupported lang: "+e),e=e.replace(nt,rt).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return et.test(e.nodeName)},input:function(e){return Z.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},i.pseudos.nth=i.pseudos.eq;for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})i.pseudos[t]=ft(t);function dt(){}dt.prototype=i.filters=i.pseudos,i.setFilters=new dt;function gt(e,t){var n,r,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=i.preFilter;while(a){(!n||(r=_.exec(a)))&&(r&&(a=a.slice(r[0].length)||a),u.push(o=[])),n=!1,(r=X.exec(a))&&(n=r.shift(),o.push({value:n,type:r[0].replace(z," ")}),a=a.slice(n.length));for(s in i.filter)!(r=J[s].exec(a))||l[s]&&!(r=l[s](r))||(n=r.shift(),o.push({value:n,type:s,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ot.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,n){var i=t.dir,o=n&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,a){var u,l,c,p=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[v]||(t[v]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,a)||r,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[v]&&(r=bt(r)),i&&!i[v]&&(i=bt(i,o)),at(function(o,s,a,u){var l,c,p,f=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,f,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(p=l[c])&&(y[h[c]]=!(m[h[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?P.call(o,p):f[c])>-1&&(o[l]=!(s[l]=p))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):O.apply(s,y)})}function wt(e){var t,n,r,o=e.length,s=i.relative[e[0].type],a=s||i.relative[" "],l=s?1:0,c=yt(function(e){return e===t},a,!0),p=yt(function(e){return P.call(t,e)>-1},a,!0),f=[function(e,n,r){return!s&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>l;l++)if(n=i.relative[e[l].type])f=[yt(vt(f),n)];else{if(n=i.filter[e[l].type].apply(null,e[l].matches),n[v]){for(r=++l;o>r;r++)if(i.relative[e[r].type])break;return bt(l>1&&vt(f),l>1&&mt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&wt(e.slice(l,r)),o>r&&wt(e=e.slice(r)),o>r&&mt(e))}f.push(n)}return vt(f)}function Tt(e,t){var n=0,o=t.length>0,s=e.length>0,a=function(a,l,c,f,h){var d,g,m,y=[],v=0,x="0",b=a&&[],T=null!=h,C=u,k=a||s&&i.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(u=l!==p&&l,r=n);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,c)){f.push(d);break}T&&(w=N,r=++n)}o&&((d=!m&&d)&&v--,a&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,c);if(a){if(v>0)while(x--)b[x]||y[x]||(y[x]=q.call(f));y=xt(y)}O.apply(f,y),T&&!a&&y.length>0&&v+t.length>1&&ot.uniqueSort(f)}return T&&(w=N,u=C),b};return o?at(a):a}a=ot.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[v]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ot(e,t[r],n);return n}function kt(e,t,r,o){var s,u,l,c,p,f=gt(e);if(!o&&1===f.length){if(u=f[0]=f[0].slice(0),u.length>2&&"ID"===(l=u[0]).type&&n.getById&&9===t.nodeType&&h&&i.relative[u[1].type]){if(t=(i.find.ID(l.matches[0].replace(nt,rt),t)||[])[0],!t)return r;e=e.slice(u.shift().value.length)}s=J.needsContext.test(e)?0:u.length;while(s--){if(l=u[s],i.relative[c=l.type])break;if((p=i.find[c])&&(o=p(l.matches[0].replace(nt,rt),U.test(u[0].type)&&t.parentNode||t))){if(u.splice(s,1),e=o.length&&mt(u),!e)return O.apply(r,o),r;break}}}return a(e,f)(o,t,!h,r,U.test(e)),r}n.sortStable=v.split("").sort(S).join("")===v,n.detectDuplicates=E,c(),n.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(p.createElement("div"))}),ut(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||lt("type|href|height|width",function(e,t,n){return n?undefined:e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||lt("value",function(e,t,n){return n||"input"!==e.nodeName.toLowerCase()?undefined:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||lt(R,function(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}),x.find=ot,x.expr=ot.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ot.uniqueSort,x.text=ot.getText,x.isXMLDoc=ot.isXML,x.contains=ot.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(p){for(t=e.memory&&p,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(p[0],p[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!a||n&&!u||(t=t||[],t=[e,t.slice?t.slice():t],r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))x.extend(this.cache[i],t);else for(r in t)o[r]=t[r];return o},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){var r;return t===undefined||t&&"string"==typeof t&&n===undefined?(r=this.get(e,t),r!==undefined?r:this.get(e,x.camelCase(t))):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i,o=this.key(e),s=this.cache[o];if(t===undefined)this.cache[o]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):(i=x.camelCase(t),t in s?r=[t,i]:(r=i,r=r in s?[r]:r.match(w)||[])),n=r.length;while(n--)delete s[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){e[this.expando]&&delete this.cache[e[this.expando]]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.slice(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t) +};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t);x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n\f]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,i=0,o=x(this),s=e.match(w)||[];while(t=s[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,x(this).val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.bool.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,p,f,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(f=x.event.special[d]||{},d=(o?f.delegateType:f.bindType)||d,f=x.event.special[d]||{},p=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,f.setup&&f.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),f.add&&(f.add.call(e,p),p.handler.guid||(p.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,p):h.push(p),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,p,f,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){p=x.event.special[h]||{},h=(r?p.delegateType:p.bindType)||h,f=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=f.length;while(o--)c=f[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(f.splice(o,1),c.selector&&f.delegateCount--,p.remove&&p.remove.call(e,c));s&&!f.length&&(p.teardown&&p.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,p,f,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),f=x.event.special[d]||{},i||!f.trigger||f.trigger.apply(r,n)!==!1)){if(!i&&!f.noBubble&&!x.isWindow(r)){for(l=f.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:f.bindType||d,p=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),p&&p.apply(a,n),p=c&&a[c],p&&x.acceptData(a)&&p.apply&&p.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||f._default&&f._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,s=e,a=this.fixHooks[i];a||(this.fixHooks[i]=a=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=a.props?this.props.concat(a.props):this.props,e=new x.Event(s),t=r.length;while(t--)n=r[t],e[n]=s[n];return e.target||(e.target=o),3===e.target.nodeType&&(e.target=e.target.parentNode),a.filter?a.filter(e,s):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=/^(?:parents|prev(?:Until|All))/,Q=x.expr.match.needsContext,K={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(et(this,e||[],!0))},filter:function(e){return this.pushStack(et(this,e||[],!1))},is:function(e){return!!et(this,"string"==typeof e&&Q.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],s=Q.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function Z(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return Z(e,"nextSibling")},prev:function(e){return Z(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return e.contentDocument||x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(K[e]||x.unique(i),J.test(e)&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function et(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,nt=/<([\w:]+)/,rt=/<|&#?\w+;/,it=/<(?:script|style|link)/i,ot=/^(?:checkbox|radio)$/i,st=/checked\s*(?:[^=]|=\s*.checked.)/i,at=/^$|\/(?:java|ecma)script/i,ut=/^true\/(.*)/,lt=/^\s*\s*$/g,ct={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ct.optgroup=ct.option,ct.tbody=ct.tfoot=ct.colgroup=ct.caption=ct.thead,ct.th=ct.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(mt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&dt(mt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(mt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!it.test(e)&&!ct[(nt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(tt,"<$1>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(mt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=f.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,p=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&st.test(d))return this.each(function(r){var i=p.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(mt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,mt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,ht),l=0;s>l;l++)a=o[l],at.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(lt,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=mt(a),o=mt(e),r=0,i=o.length;i>r;r++)yt(o[r],s[r]);if(t)if(n)for(o=o||mt(e),s=s||mt(a),r=0,i=o.length;i>r;r++)gt(o[r],s[r]);else gt(e,a);return s=mt(a,"script"),s.length>0&&dt(s,!u&&mt(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,p=e.length,f=t.createDocumentFragment(),h=[];for(;p>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(rt.test(i)){o=o||f.appendChild(t.createElement("div")),s=(nt.exec(i)||["",""])[1].toLowerCase(),a=ct[s]||ct._default,o.innerHTML=a[1]+i.replace(tt,"<$1>")+a[2],l=a[0];while(l--)o=o.lastChild;x.merge(h,o.childNodes),o=f.firstChild,o.textContent=""}else h.push(t.createTextNode(i));f.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=mt(f.appendChild(i),"script"),u&&dt(o),n)){l=0;while(i=o[l++])at.test(i.type||"")&&n.push(i)}return f},cleanData:function(e){var t,n,r,i,o,s,a=x.event.special,u=0;for(;(n=e[u])!==undefined;u++){if(F.accepts(n)&&(o=n[q.expando],o&&(t=q.cache[o]))){if(r=Object.keys(t.events||{}),r.length)for(s=0;(i=r[s])!==undefined;s++)a[i]?x.event.remove(n,i):x.removeEvent(n,i,t.handle);q.cache[o]&&delete q.cache[o]}delete L.cache[n[L.expando]]}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}});function pt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function ht(e){var t=ut.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function dt(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function gt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=q.set(t,o),l=o.events)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function mt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function yt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ot.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var vt,xt,bt=/^(none|table(?!-c[ea]).+)/,wt=/^margin/,Tt=RegExp("^("+b+")(.*)$","i"),Ct=RegExp("^("+b+")(?!px)[a-z%]+$","i"),kt=RegExp("^([+-])=("+b+")","i"),Nt={BODY:"block"},Et={position:"absolute",visibility:"hidden",display:"block"},St={letterSpacing:0,fontWeight:400},jt=["Top","Right","Bottom","Left"],Dt=["Webkit","O","Moz","ms"];function At(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Dt.length;while(i--)if(t=Dt[i]+n,t in e)return t;return r}function Lt(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function qt(t){return e.getComputedStyle(t,null)}function Ht(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&Lt(r)&&(o[s]=q.access(r,"olddisplay",Rt(r.nodeName)))):o[s]||(i=Lt(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=qt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return Ht(this,!0)},hide:function(){return Ht(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){Lt(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=vt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=At(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=kt.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=At(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=vt(e,t,r)),"normal"===i&&t in St&&(i=St[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),vt=function(e,t,n){var r,i,o,s=n||qt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Ct.test(a)&&wt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ot(e,t,n){var r=Tt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ft(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+jt[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+jt[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+jt[o]+"Width",!0,i))):(s+=x.css(e,"padding"+jt[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+jt[o]+"Width",!0,i)));return s}function Pt(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=qt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=vt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Ct.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ft(e,t,n||(s?"border":"content"),r,o)+"px"}function Rt(e){var t=o,n=Nt[e];return n||(n=Mt(e,t),"none"!==n&&n||(xt=(xt||x("