From 7c56be8a724e2b0647e5a6cbd55c7b0eb948206b Mon Sep 17 00:00:00 2001 From: Sebastian Muszytowski Date: Fri, 3 Jun 2016 15:48:16 +0200 Subject: initial commit of all data --- src/Hamming/Hamming.c | 174 +++++++++++ src/Hamming/Hamming.h | 68 +++++ src/Hamming/HammingCalculateParityFast.c | 67 +++++ src/Hamming/HammingCalculateParitySmall.c | 99 ++++++ src/Hamming/HammingCalculateParitySmallAndFast.c | 64 ++++ src/Hamming/HammingCalculateParityTextbook.c | 100 ++++++ src/Hamming/LICENSE | 201 +++++++++++++ src/Hamming/README.md | 4 + src/display.cc | 236 +++++++++++++++ src/display.h | 223 ++++++++++++++ src/fecmodem.cc | 96 ++++++ src/fecmodem.h | 67 +++++ src/font.h | 244 +++++++++++++++ src/gpio.cc | 102 +++++++ src/gpio.h | 28 ++ src/hamming.h | 47 +++ src/main.cc | 34 +++ src/modem.cc | 117 ++++++++ src/modem.h | 82 +++++ src/static_patterns.h | 75 +++++ src/storage.cc | 322 ++++++++++++++++++++ src/storage.h | 199 ++++++++++++ src/system.cc | 367 +++++++++++++++++++++++ src/system.h | 144 +++++++++ 24 files changed, 3160 insertions(+) create mode 100644 src/Hamming/Hamming.c create mode 100644 src/Hamming/Hamming.h create mode 100644 src/Hamming/HammingCalculateParityFast.c create mode 100644 src/Hamming/HammingCalculateParitySmall.c create mode 100644 src/Hamming/HammingCalculateParitySmallAndFast.c create mode 100644 src/Hamming/HammingCalculateParityTextbook.c create mode 100644 src/Hamming/LICENSE create mode 100644 src/Hamming/README.md create mode 100644 src/display.cc create mode 100644 src/display.h create mode 100644 src/fecmodem.cc create mode 100644 src/fecmodem.h create mode 100644 src/font.h create mode 100644 src/gpio.cc create mode 100644 src/gpio.h create mode 100644 src/hamming.h create mode 100644 src/main.cc create mode 100644 src/modem.cc create mode 100644 src/modem.h create mode 100644 src/static_patterns.h create mode 100644 src/storage.cc create mode 100644 src/storage.h create mode 100644 src/system.cc create mode 100644 src/system.h (limited to 'src') diff --git a/src/Hamming/Hamming.c b/src/Hamming/Hamming.c new file mode 100644 index 0000000..cb1fba3 --- /dev/null +++ b/src/Hamming/Hamming.c @@ -0,0 +1,174 @@ +/* +Hamming Error-Correcting Code (ECC) +Optimized for avr-gcc 4.8.1 in Atmel AVR Studio 6.2 +August 12, 2014 + +You should include Hamming.c, Hamming.h, and only one of the other Hamming files in your project. +The other Hamming files implement the same methods in different ways, that may be better or worse for your needs. + +This was created for LoFi in the TheHackadayPrize contest. +http://hackaday.io/project/1552-LoFi + +Copyright 2014 David Cook +RobotRoom.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include + +#include "Hamming.h" + +/****************************/ +/* */ +/* Constants and structures */ +/* */ +/****************************/ + +#ifndef null +#define null ((void*) 0) +#endif + +// If transmitting/writing only, you don't need to include this file. +// If receiving/reading, then this provides the methods to correct bit errors. + +#define UNCORRECTABLE 0xFF +#define ERROR_IN_PARITY 0xFE +#define NO_ERROR 0x00 + + +/****************************/ +/* */ +/* Global variables */ +/* */ +/****************************/ + +// Private table. Faster and more compact than multiple if statements. +static const byte _hammingCorrect128Syndrome[16] PROGMEM = +{ + NO_ERROR, // 0 + ERROR_IN_PARITY, // 1 + ERROR_IN_PARITY, // 2 + 0x01, // 3 + ERROR_IN_PARITY, // 4 + 0x02, // 5 + 0x04, // 6 + 0x08, // 7 + ERROR_IN_PARITY, // 8 + 0x10, // 9 + 0x20, // 10 + 0x40, // 11 + 0x80, // 12 + UNCORRECTABLE, // 13 + UNCORRECTABLE, // 14 + UNCORRECTABLE, // 15 +}; + +/****************************/ +/* */ +/* Private methods */ +/* */ +/****************************/ + +// Give a pointer to a received byte, +// and given a nibble difference in parity (parity ^ calculated parity) +// this will correct the received byte value if possible. +// It returns the number of bits corrected: +// 0 means no errors +// 1 means one corrected error +// 3 means corrections not possible +static byte HammingCorrect128Syndrome(byte* value, byte syndrome) +{ + // Using only the lower nibble (& 0x0F), look up the bit + // to correct in a table + byte correction = pgm_read_byte(&(_hammingCorrect128Syndrome[syndrome & 0x0F])); + + if (correction != NO_ERROR) + { + if (correction == UNCORRECTABLE || value == null) + { + return 3; // Non-recoverable error + } + else + { + if ( correction != ERROR_IN_PARITY) + { + *value ^= correction; + } + + return 1; // 1-bit recoverable error; + } + } + + return 0; // No errors +} + + +/****************************/ +/* */ +/* Public methods */ +/* */ +/****************************/ + +// Given a pointer to a received byte and the received parity (as a lower nibble), +// this calculates what the parity should be and fixes the received value if needed. +// It returns the number of bits corrected: +// 0 means no errors +// 1 means one corrected error +// 3 means corrections not possible +byte HammingCorrect128(byte* value, nibble parity) +{ + byte syndrome; + + if (value == null) + { + return 3; // Non-recoverable error + } + + syndrome = HammingCalculateParity128(*value) ^ parity; + + if (syndrome != 0) + { + return HammingCorrect128Syndrome(value, syndrome); + } + + return 0; // No errors +} + + +// Given a pointer to a first value and a pointer to a second value and +// their combined given parity (lower nibble first parity, upper nibble second parity), +// this calculates what the parity should be and fixes the values if needed. +// It returns the number of bits corrected: +// 0 means no errors +// 1 means one corrected error +// 2 means two corrected errors +// 3 means corrections not possible +byte HammingCorrect2416(byte* first, byte* second, byte parity) +{ + byte syndrome; + + if (first == null || second == null) + { + return 3; // Non-recoverable error + } + + syndrome = HammingCalculateParity2416(*first, *second) ^ parity; + + if (syndrome != 0) + { + return HammingCorrect128Syndrome(first, syndrome) + HammingCorrect128Syndrome(second, syndrome >> 4); + } + + return 0; // No errors +} diff --git a/src/Hamming/Hamming.h b/src/Hamming/Hamming.h new file mode 100644 index 0000000..7778a59 --- /dev/null +++ b/src/Hamming/Hamming.h @@ -0,0 +1,68 @@ +/* +Hamming Error-Correcting Code (ECC) +Optimized for avr-gcc 4.8.1 in Atmel AVR Studio 6.2 +August 12, 2014 + +You should include Hamming.c, Hamming.h, and only one of the other Hamming files in your project. +The other Hamming files implement the same methods in different ways, that may be better or worse for your needs. + +This was created for LoFi in the TheHackadayPrize contest. +http://hackaday.io/project/1552-LoFi + +Copyright 2014 David Cook +RobotRoom.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _HAMMING_H +#define _HAMMING_H + +#ifndef _AVR_H +typedef unsigned char byte; +typedef unsigned char nibble; +#endif + +/**** These are needed to transmit and receive ****/ + +// Given a byte to transmit, this returns the parity as a nibble +nibble HammingCalculateParity128(byte value); + +// Given two bytes to transmit, this returns the parity +// as a byte with the lower nibble being for the first byte, +// and the upper nibble being for the second byte. +byte HammingCalculateParity2416(byte first, byte second); + + + +/**** These are needed only to receive ****/ + +// Given a pointer to a received byte and the received parity (as a lower nibble), +// this calculates what the parity should be and fixes the received value if needed. +// It returns the number of bits corrected: +// 0 means no errors +// 1 means one corrected error +// 3 means corrections not possible +byte HammingCorrect128(byte* value, nibble parity); + +// Given a pointer to a first value and a pointer to a second value and +// their combined given parity (lower nibble first parity, upper nibble second parity), +// this calculates what the parity should be and fixes the values if needed. +// It returns the number of bits corrected: +// 0 means no errors +// 1 means one corrected error +// 2 means two corrected errors +// 3 means corrections not possible +byte HammingCorrect2416(byte* first, byte* second, byte parity); + +#endif // _HAMMING_H diff --git a/src/Hamming/HammingCalculateParityFast.c b/src/Hamming/HammingCalculateParityFast.c new file mode 100644 index 0000000..6cc5df6 --- /dev/null +++ b/src/Hamming/HammingCalculateParityFast.c @@ -0,0 +1,67 @@ +/* +Hamming Error-Correcting Code (ECC) +Optimized for avr-gcc 4.8.1 in Atmel AVR Studio 6.2 +August 12, 2014 + +You should include Hamming.c, Hamming.h, and only one of the other Hamming files in your project. +The other Hamming files implement the same methods in different ways, that may be better or worse for your needs. + +This was created for LoFi in the TheHackadayPrize contest. +http://hackaday.io/project/1552-LoFi + +Copyright 2014 David Cook +RobotRoom.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include + +#include "Hamming.h" + + +// This contains all of the precalculated parity values for a byte (8 bits). +// This is very fast, but takes up more program space than calculating on the fly. +static const byte _hammingCalculateParityFast128[256] PROGMEM = +{ + 0, 3, 5, 6, 6, 5, 3, 0, 7, 4, 2, 1, 1, 2, 4, 7, + 9, 10, 12, 15, 15, 12, 10, 9, 14, 13, 11, 8, 8, 11, 13, 14, + 10, 9, 15, 12, 12, 15, 9, 10, 13, 14, 8, 11, 11, 8, 14, 13, + 3, 0, 6, 5, 5, 6, 0, 3, 4, 7, 1, 2, 2, 1, 7, 4, + 11, 8, 14, 13, 13, 14, 8, 11, 12, 15, 9, 10, 10, 9, 15, 12, + 2, 1, 7, 4, 4, 7, 1, 2, 5, 6, 0, 3, 3, 0, 6, 5, + 1, 2, 4, 7, 7, 4, 2, 1, 6, 5, 3, 0, 0, 3, 5, 6, + 8, 11, 13, 14, 14, 13, 11, 8, 15, 12, 10, 9, 9, 10, 12, 15, + 12, 15, 9, 10, 10, 9, 15, 12, 11, 8, 14, 13, 13, 14, 8, 11, + 5, 6, 0, 3, 3, 0, 6, 5, 2, 1, 7, 4, 4, 7, 1, 2, + 6, 5, 3, 0, 0, 3, 5, 6, 1, 2, 4, 7, 7, 4, 2, 1, + 15, 12, 10, 9, 9, 10, 12, 15, 8, 11, 13, 14, 14, 13, 11, 8, + 7, 4, 2, 1, 1, 2, 4, 7, 0, 3, 5, 6, 6, 5, 3, 0, + 14, 13, 11, 8, 8, 11, 13, 14, 9, 10, 12, 15, 15, 12, 10, 9, + 13, 14, 8, 11, 11, 8, 14, 13, 10, 9, 15, 12, 12, 15, 9, 10, + 4, 7, 1, 2, 2, 1, 7, 4, 3, 0, 6, 5, 5, 6, 0, 3, +}; + +// Given a byte to transmit, this returns the parity as a nibble +nibble HammingCalculateParity128(byte value) +{ + return pgm_read_byte(&(_hammingCalculateParityFast128[value])); +} + +// Given two bytes to transmit, this returns the parity +// as a byte with the lower nibble being for the first byte, +// and the upper nibble being for the second byte. +byte HammingCalculateParity2416(byte first, byte second) +{ + return (pgm_read_byte(&(_hammingCalculateParityFast128[second]))<<4) | pgm_read_byte(&(_hammingCalculateParityFast128[first])); +} diff --git a/src/Hamming/HammingCalculateParitySmall.c b/src/Hamming/HammingCalculateParitySmall.c new file mode 100644 index 0000000..d446f8f --- /dev/null +++ b/src/Hamming/HammingCalculateParitySmall.c @@ -0,0 +1,99 @@ +/* +Hamming Error-Correcting Code (ECC) +Optimized for avr-gcc 4.8.1 in Atmel AVR Studio 6.2 +August 12, 2014 + +You should include Hamming.c, Hamming.h, and only one of the other Hamming files in your project. +The other Hamming files implement the same methods in different ways, that may be better or worse for your needs. + +This was created for LoFi in the TheHackadayPrize contest. +http://hackaday.io/project/1552-LoFi + +Copyright 2014 David Cook +RobotRoom.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "Hamming.h" + +/****************************/ +/* */ +/* Public methods */ +/* */ +/****************************/ + +// This is slower than using a table, but faster than +// using the textbook method. + +// Given a byte to transmit, this returns the parity as a nibble +nibble HammingCalculateParity128(byte value) +{ + // Exclusive OR is associative and commutative, so order of operations and values does not matter. + nibble parity; + + if ( ( value & 1 ) != 0 ) + { + parity = 0x3; + } + else + { + parity = 0x0; + } + + if ( ( value & 2 ) != 0 ) + { + parity ^= 0x5; + } + + if ( ( value & 4 ) != 0 ) + { + parity ^= 0x6; + } + + if ( ( value & 8 ) != 0 ) + { + parity ^= 0x7; + } + + if ( ( value & 16 ) != 0 ) + { + parity ^= 0x9; + } + + if ( ( value & 32 ) != 0 ) + { + parity ^= 0xA; + } + + if ( ( value & 64 ) != 0 ) + { + parity ^= 0xB; + } + + if ( ( value & 128 ) != 0 ) + { + parity ^= 0xC; + } + + return parity; +} + +// Given two bytes to transmit, this returns the parity +// as a byte with the lower nibble being for the first byte, +// and the upper nibble being for the second byte. +byte HammingCalculateParity2416(byte first, byte second) +{ + return (HammingCalculateParity128(second) << 4) | HammingCalculateParity128(first); +} + diff --git a/src/Hamming/HammingCalculateParitySmallAndFast.c b/src/Hamming/HammingCalculateParitySmallAndFast.c new file mode 100644 index 0000000..ad27bf8 --- /dev/null +++ b/src/Hamming/HammingCalculateParitySmallAndFast.c @@ -0,0 +1,64 @@ +/* +Hamming Error-Correcting Code (ECC) +Optimized for avr-gcc 4.8.1 in Atmel AVR Studio 6.2 +August 12, 2014 + +You should include Hamming.c, Hamming.h, and only one of the other Hamming files in your project. +The other Hamming files implement the same methods in different ways, that may be better or worse for your needs. + +This was created for LoFi in the TheHackadayPrize contest. +http://hackaday.io/project/1552-LoFi + +Copyright 2014 David Cook +RobotRoom.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include + +#include "Hamming.h" + +/****************************/ +/* */ +/* Global variables */ +/* */ +/****************************/ + +// This contains all of the precalculated parity values for a nibble (4 bits). +static const byte _hammingCalculateParityLowNibble[] PROGMEM = +{ 0, 3, 5, 6, 6, 5, 3, 0, 7, 4, 2, 1, 1, 2, 4, 7 }; + +static const byte _hammingCalculateParityHighNibble[] PROGMEM = +{ 0, 9, 10, 3, 11, 2, 1, 8, 12, 5, 6, 15, 7, 14, 13, 4 }; + + +/****************************/ +/* */ +/* Public methods */ +/* */ +/****************************/ + +// Given a byte to transmit, this returns the parity as a nibble +nibble HammingCalculateParity128(byte value) +{ + return pgm_read_byte(&(_hammingCalculateParityLowNibble[value&0x0F])) ^ pgm_read_byte(&(_hammingCalculateParityHighNibble[value >> 4])); +} + +// Given two bytes to transmit, this returns the parity +// as a byte with the lower nibble being for the first byte, +// and the upper nibble being for the second byte. +byte HammingCalculateParity2416(byte first, byte second) +{ + return HammingCalculateParity128(second) << 4 | HammingCalculateParity128(first); +} diff --git a/src/Hamming/HammingCalculateParityTextbook.c b/src/Hamming/HammingCalculateParityTextbook.c new file mode 100644 index 0000000..08e5cca --- /dev/null +++ b/src/Hamming/HammingCalculateParityTextbook.c @@ -0,0 +1,100 @@ +/* +Hamming Error-Correcting Code (ECC) +Optimized for avr-gcc 4.8.1 in Atmel AVR Studio 6.2 +August 12, 2014 + +You should include Hamming.c, Hamming.h, and only one of the other Hamming files in your project. +The other Hamming files implement the same methods in different ways, that may be better or worse for your needs. + +This was created for LoFi in the TheHackadayPrize contest. +http://hackaday.io/project/1552-LoFi + +Copyright 2014 David Cook +RobotRoom.com + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "Hamming.h" + +// This performs poorly on a processor that can't do multiple shifts in one instruction +#define BitToBool(byte, n) ((byte>>(n-1)) & 1) + +// This is a private method that calculates half the parity +// while shifting over any previously calculated parity for another byte. +// This was designed for processors that cannot shift more than one bit place at a time +nibble HammingCalculateParity2416Half(byte value, byte paritySoFar) +{ + // Calculate the most significant bit + paritySoFar |= BitToBool(value, 5) ^ BitToBool(value, 6) ^ BitToBool(value, 7) ^ BitToBool(value, 8); + // Shift it over + paritySoFar <<= 1; + + // Calculate the next most significant bit + paritySoFar |= BitToBool(value, 2) ^ BitToBool(value, 3) ^ BitToBool(value, 4) ^ BitToBool(value, 8); + // Shift it over, as well as the previously calculated bit + paritySoFar <<= 1; + + // Calculate the next most significant bit + paritySoFar |= BitToBool(value, 1) ^ BitToBool(value, 3) ^ BitToBool(value, 4) ^ BitToBool(value, 6) ^ BitToBool(value, 7); + // Shift it over, as well as the previously calculated bits + paritySoFar <<= 1; + + // Calculate the least significant bit + paritySoFar |= BitToBool(value, 1) ^ BitToBool(value, 2) ^ BitToBool(value, 4) ^ BitToBool(value, 5) ^ BitToBool(value, 7); + + return paritySoFar; +} + +// Given a byte to transmit, this returns the parity as a nibble +nibble HammingCalculateParity128(byte value) +{ + return HammingCalculateParity2416Half(value, 0); +} + +// If your processor can shift multiple bit places in a single instruction, then set this to 1. +// It will be twice as fast. +// If your processor cannot, then set this to 0, otherwise it will be slow and large. +#define CPU_HAS_MULTIPLE_SHIFT_INSTRUCTION 0 + +#if CPU_HAS_MULTIPLE_SHIFT_INSTRUCTION +// Given two bytes to transmit, this returns the parity +// as a byte with the lower nibble being for the first byte, +// and the upper nibble being for the second byte. +byte HammingCalculateParity2416(byte first, byte second) +{ + // This is the textbook way to calculate hamming parity. + return ((BitToBool(first, 1) ^ BitToBool(first, 2) ^ BitToBool(first, 4) ^ BitToBool(first, 5) ^ BitToBool(first, 7))) + + ((BitToBool(first, 1) ^ BitToBool(first, 3) ^ BitToBool(first, 4) ^ BitToBool(first, 6) ^ BitToBool(first, 7))<<1) + + ((BitToBool(first, 2) ^ BitToBool(first, 3) ^ BitToBool(first, 4) ^ BitToBool(first, 8))<<2) + + ((BitToBool(first, 5) ^ BitToBool(first, 6) ^ BitToBool(first, 7) ^ BitToBool(first, 8))<<3) + + + ((BitToBool(second, 1) ^ BitToBool(second, 2) ^ BitToBool(second, 4) ^ BitToBool(second, 5) ^ BitToBool(second, 7))<<4) + + ((BitToBool(second, 1) ^ BitToBool(second, 3) ^ BitToBool(second, 4) ^ BitToBool(second, 6) ^ BitToBool(second, 7))<<5) + + ((BitToBool(second, 2) ^ BitToBool(second, 3) ^ BitToBool(second, 4) ^ BitToBool(second, 8))<<6) + + ((BitToBool(second, 5) ^ BitToBool(second, 6) ^ BitToBool(second, 7) ^ BitToBool(second, 8))<<7); +} +#else +// Given two bytes to transmit, this returns the parity +// as a byte with the lower nibble being for the first byte, +// and the upper nibble being for the second byte. +byte HammingCalculateParity2416(byte first, byte second) +{ + // This makes two calls, one for each byte. + // It passes the result of the second byte into the calculation for the first + // such that the four shift instructions and the 'or' instruction are free. + return HammingCalculateParity2416Half(first, HammingCalculateParity2416Half(second, 0) << 1); +} +#endif + + diff --git a/src/Hamming/LICENSE b/src/Hamming/LICENSE new file mode 100644 index 0000000..5c304d1 --- /dev/null +++ b/src/Hamming/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/Hamming/README.md b/src/Hamming/README.md new file mode 100644 index 0000000..b6b8269 --- /dev/null +++ b/src/Hamming/README.md @@ -0,0 +1,4 @@ +Hamming +======= + +Hamming(12,8) and (24,16) error-correcting code (ECC) in avr-gcc diff --git a/src/display.cc b/src/display.cc new file mode 100644 index 0000000..dfe4c1f --- /dev/null +++ b/src/display.cc @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#include +#include +#include +#include + +#include "display.h" +#include "font.h" +#include "storage.h" + +Display display; + +Display::Display() +{ + char_pos = -1; +} + +void Display::disable() +{ + TIMSK0 &= ~_BV(TOIE0); + PORTB = 0; + PORTD = 0; +} + +void Display::enable() +{ + // Ports B and D drive the dot matrix display -> set all as output + DDRB = 0xff; + DDRD = 0xff; + + // Enable 8bit counter with prescaler=8 (-> timer frequency = 1MHz) + TCCR0A = _BV(CS01); + // raise timer interrupt on counter overflow (-> interrupt frequency = ~4kHz) + TIMSK0 = _BV(TOIE0); +} + +void Display::multiplex() +{ + /* + * To avoid flickering, do not put any code (or expensive index + * calculations) between the following three lines. + */ + PORTB = 0; + PORTD = disp_buf[active_col]; + PORTB = _BV(active_col); + + if (++active_col == 8) { + active_col = 0; + if (++update_cnt == update_threshold) { + update_cnt = 0; + need_update = 1; + } + } +} + +void Display::update() { + uint8_t i, glyph_len; + uint8_t *glyph_addr; + if (need_update) { + need_update = 0; + + if (status == RUNNING) { + if (current_anim->type == AnimationType::TEXT) { + + /* + * Scroll display contents to the left/right + */ + if (current_anim->direction == 0) { + for (i = 0; i < 7; i++) { + disp_buf[i] = disp_buf[i+1]; + } + } else if (current_anim->direction == 1) { + for (i = 7; i > 0; i--) { + disp_buf[i] = disp_buf[i-1]; + } + } + + /* + * Load current character + */ + glyph_addr = (uint8_t *)pgm_read_ptr(&font[current_anim->data[str_pos]]); + glyph_len = pgm_read_byte(&glyph_addr[0]); + char_pos++; + + if (char_pos > glyph_len) { + char_pos = 0; + if (current_anim->direction == 0) + str_pos++; + else + str_pos--; // may underflow, but that's okay + } + + /* + * Append one character column (or whitespace if we are + * between two characters) + */ + if (current_anim->direction == 0) { + if (char_pos == 0) { + disp_buf[7] = 0xff; // whitespace + } else { + disp_buf[7] = ~pgm_read_byte(&glyph_addr[char_pos]); + } + } else { + if (char_pos == 0) { + disp_buf[0] = 0xff; // whitespace + } else { + disp_buf[0] = ~pgm_read_byte(&glyph_addr[glyph_len - char_pos + 1]); + } + } + + } else if (current_anim->type == AnimationType::FRAMES) { + for (i = 0; i < 8; i++) { + disp_buf[i] = ~current_anim->data[str_pos+i]; + } + str_pos += 8; + } + + if (current_anim->direction == 0) { + /* + * Check whether we reached the end of the pattern + * (that is, we're in the last chunk and reached the + * remaining pattern length) + */ + if ((str_chunk == ((current_anim->length - 1) / 128)) + && (str_pos > ((current_anim->length - 1) % 128))) { + str_chunk = 0; + str_pos = 0; + if (current_anim->delay > 0) { + status = PAUSED; + update_threshold = 244; + } + if (current_anim->length > 128) { + storage.loadChunk(str_chunk, current_anim->data); + } + /* + * Otherwise, check whether the pattern is split into + * several chunks and we reached the end of the chunk + * kept in current_anim->data + */ + } else if ((current_anim->length > 128) && (str_pos >= 128)) { + str_pos = 0; + str_chunk++; + storage.loadChunk(str_chunk, current_anim->data); + } + } else { + /* + * In this branch we keep doing str_pos--, so check for + * underflow + */ + if (str_pos >= 128) { + /* + * Check whether we reached the end of the pattern + * (and whether we need to load a new chunk) + */ + if (str_chunk == 0) { + if (current_anim->length > 128) { + str_chunk = (current_anim->length - 1) / 128; + storage.loadChunk(str_chunk, current_anim->data); + } + if (current_anim->delay > 0) { + str_pos = 0; + status = PAUSED; + update_threshold = 244; + } else { + str_pos = (current_anim->length - 1) % 128; + } + /* + * Otherwise, we reached the end of the active chunk + */ + } else { + str_chunk--; + storage.loadChunk(str_chunk, current_anim->data); + str_pos = 127; + } + } + } + } else if (status == PAUSED) { + str_pos++; + if (str_pos >= current_anim->delay) { + if (current_anim->direction == 0) + str_pos = 0; + else if (current_anim->length <= 128) + str_pos = current_anim->length - 1; + else + str_pos = (current_anim->length - 1) % 128; + status = RUNNING; + update_threshold = current_anim->speed; + } + } + } +} + +void Display::reset() +{ + for (uint8_t i = 0; i < 8; i++) + disp_buf[i] = 0xff; + update_cnt = 0; + str_pos = 0; + str_chunk = 0; + char_pos = -1; + need_update = 1; + status = RUNNING; +} + +void Display::show(animation_t *anim) +{ + current_anim = anim; + reset(); + update_threshold = current_anim->speed; + if (current_anim->direction == 1) { + if (current_anim->length > 128) { + str_chunk = (current_anim->length - 1) / 128; + storage.loadChunk(str_chunk, current_anim->data); + } + str_pos = (current_anim->length - 1) % 128; + } +} + +/* + * Current configuration: + * One interrupt per 256 microseconds. The whole display is refreshed every + * 2048us, giving a refresh rate of ~500Hz + */ +ISR(TIMER0_OVF_vect) +{ + display.multiplex(); +} diff --git a/src/display.h b/src/display.h new file mode 100644 index 0000000..ef133a0 --- /dev/null +++ b/src/display.h @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#include +#include + +/** + * Describes the type of an animation object. The Storage class reserves four + * bits for the animation type, so up to 16 types are supported. + */ +enum class AnimationType : uint8_t { + TEXT = 1, + FRAMES = 2 +}; + +/** + * Generic struct for anything which can be displayed, e.g. texts or + * sequences of frames. + */ +struct animation { + /** + * Type of patern/animation described in this struct. Controls the + * behaviour of Display::multiplex() and Display::update(). + */ + AnimationType type; + + /** + * Length of animation in bytes. Also controls the behaviour of + * Display::update(). + * + * For length <= 128, the whole animation is stored in data and + * Display::update() simply traverses the data array. + * + * For length > 128, only up to 128 bytes of animation data are stored + * in data. When Display::update() reaches the end of the array, it will + * use Storage::loadChunk() to load the next 128 byte chunk of animation + * data into the data array. + */ + uint16_t length; + + /** + * * If type == AnimationType::TEXT: Text scroll speed in columns per TODO + * * If type == AnimationType::FRAMES: Frames per TODO + */ + uint8_t speed; + + /** + * Delay after the last text symbol / animation frame. + */ + uint8_t delay; + + /** + * Scroll mode / direction. Must be set to 0 if type != TEXT. + */ + uint8_t direction; + + /** + * * If type == AnimationType::TEXT: pointer to an arary containing the + * animation text in standard ASCII format (+ special font chars) + * * If type == AnimationType::FRAMES: Frame array. Each element encodes + * a display column (starting with the leftmost one), each group of + * eight elements is a frame. + * + * The data array must always hold at least 128 elements. + */ + uint8_t *data; +}; + +typedef struct animation animation_t; + +/** + * Controls the display. Handles multiplexing, scrolling and supports loading + * arbitrary animations. Also handles partial animations and chunk loading. + */ +class Display { + private: + + /** + * The currently active animation + */ + animation_t *current_anim; + + /** + * Internal display update counter. Incremented by multiplex(). + * update() is called (and the counter reset) whenever + * update_cnt == need_update. + */ + uint8_t update_cnt; + + /** + * Set to a true value by multiplex() if an update (that is, + * a scroll step or a new frame) is needed. Checked and reset to + * false by update(). + */ + uint8_t need_update; + + /** + * Number of frames after which update() is called. This value + * holds either the current animation's speed or its delay. + */ + uint8_t update_threshold; + + /** + * The currently active column in multiplex() + */ + uint8_t active_col; + + /** + * The current display content which multiplex() will show + */ + uint8_t disp_buf[8]; + + /** + * The current position inside current_anim->data. For a TEXT + * animation, this indicates the currently active character. + * In case of FRAMES, it indicates the leftmost column of an + * eight-column frame. + * + * This variable is also used as delay counter for status == PAUSED, + * so it must be re-initialized when the pause is over. + */ + uint8_t str_pos; + + /** + * The currently active animation chunk. For an animation which is + * not longer than 128 bytes, this will always read 0. Otherwise, + * it indicates the offset in 128 byte-chunks from the start of the + * animation which is currently held in memory. So, str_chunk == 1 + * means current->anim_data contains animation bytes 129 to 256, + * and so on. The current position in the complete animation is + * str_chunk * 128 + str_pos. + */ + uint8_t str_chunk; + + /** + * If current_anim->type == TEXT: The column of the character + * pointed to by str_pos which was last added to the display. + * + * For current_anim->direction == 0, this refers to the character's + * left border and counts from 0 onwards. so char_pos == 0 means the + * leftmost column of the character was last added to the display. + * + * For current_anim->direction == 1, this refers to the character's + * right border and also counts from 0 onwards, so char_pos == 0 + * means the rightmost column of the character was last added to the + * display. + */ + int8_t char_pos; + + enum AnimationStatus : uint8_t { + RUNNING, + SCROLL_BACK, + PAUSED + }; + + /** + * The current animation status: RUNNING (text/frames are being + * displayed) or PAUSED (the display isn't changed until the + * delay specified by current_anim->delay has passed) + */ + AnimationStatus status; + + public: + Display(); + + /** + * Enables the display driver. + * Configures ports B and D as output and enables the display + * timer and corresponding interrupt. + */ + void enable(void); + + /** + * Disables the display driver. + * Turns off both the display itself and the display timer. + */ + void disable(void); + + /** + * Draws a single display column. Called every 256 microseconds + * by the timer interrupt (TIMER0_OVF_vect), resulting in + * a display refresh rate of ~500Hz (one refresh per 2048µs) + */ + void multiplex(void); + + /** + * Reset display and animation state. Fills the screen with "black" + * (that is, no active pixels) and sets the animation offset to zero. + */ + void reset(void); + + /** + * Update display content. + * Checks current_anim->speed and current_anim->type and scrolls + * the text / advances a frame when appropriate. Also uses + * Storage::loadChunk() to load the next 128 pattern bytes if + * current_anim->length is greater than 128 and the end of the + * 128 byte pattern buffer is reached. + */ + void update(void); + + /** + * Sets the active animation to be shown on the display. Automatically + * calls reset(). If direction == 1, uses Storage::loadChunk() to + * load the last 128 byte-chunk of anim (so that the text can start + * scrolling from its last position). If direction == 0, the first + * 128 bytes of animation data are expected to already be present in + * anim->data. + * + * @param anim active animation. Note that the data is not copied, + * so anim has to be kept in memory until a new one is loaded + */ + void show(animation_t *anim); +}; + +extern Display display; diff --git a/src/fecmodem.cc b/src/fecmodem.cc new file mode 100644 index 0000000..a75c27b --- /dev/null +++ b/src/fecmodem.cc @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#include +#include +#include "fecmodem.h" + +uint8_t FECModem::parity128(uint8_t byte) +{ + return pgm_read_byte(&hammingParityLow[byte & 0x0f]) ^ pgm_read_byte(&hammingParityHigh[byte >> 4]); +} + +uint8_t FECModem::parity2416(uint8_t byte1, uint8_t byte2) +{ + return parity128(byte1) | (parity128(byte2) << 4); +} + +uint8_t FECModem::correct128(uint8_t *byte, uint8_t err) +{ + uint8_t result = pgm_read_byte(&hammingParityCheck[err & 0x0f]); + + if (result != NO_ERROR) { + if (byte == NULL) + return 3; + if (result == UNCORRECTABLE) { + *byte = 0; + return 3; + } + if (result != ERROR_IN_PARITY) { + *byte ^= result; + } + return 1; + } + return 0; +} + +uint8_t FECModem::hamming2416(uint8_t *byte1, uint8_t *byte2, uint8_t parity) +{ + uint8_t err; + + if (byte1 == NULL || byte2 == NULL) { + return 3; + } + + err = parity2416(*byte1, *byte2) ^ parity; + + if (err) { + return correct128(byte1, err) + correct128(byte2, err >> 4); + } + + return 0; +} + +void FECModem::enable() +{ + this->Modem::enable(); + hammingState = FIRST_BYTE; +} + +uint8_t FECModem::buffer_available() +{ +// XXX this reset implementation is _completely_ broken +// if (newTransmission()) +// hammingState = FIRST_BYTE; + if (this->Modem::buffer_available() >= 3) + return 2; + if (hammingState == SECOND_BYTE) + return 1; + return 0; +} + +uint8_t FECModem::buffer_get() +{ + uint8_t byte1, parity; + if (hammingState == SECOND_BYTE) { + hammingState = FIRST_BYTE; + return buf_byte; + } + hammingState = SECOND_BYTE; + byte1 = this->Modem::buffer_get(); + buf_byte = this->Modem::buffer_get(); + parity = this->Modem::buffer_get(); + + hamming2416(&byte1, &buf_byte, parity); + + return byte1; +} + +FECModem modem; diff --git a/src/fecmodem.h b/src/fecmodem.h new file mode 100644 index 0000000..9d43fa3 --- /dev/null +++ b/src/fecmodem.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#include +#include + +#ifndef FECMODEM_H_ +#define FECMODEM_H_ + +#include "hamming.h" +#include "modem.h" + +/** + * Receive-only modem with forward error correction. + * Uses the Modem class to read raw modem data and uses the Hamming 2416 + * algorithm to detect and, if possible, correct transmission errors. + * Exposes a global modem object for convenience. + */ +class FECModem : public Modem { + private: + enum HammingState : uint8_t { + FIRST_BYTE, + SECOND_BYTE + }; + HammingState hammingState; + uint8_t buf_byte; + + uint8_t parity128(uint8_t byte); + uint8_t parity2416(uint8_t byte1, uint8_t byte2); + uint8_t correct128(uint8_t *byte, uint8_t parity); + uint8_t hamming2416(uint8_t *byte1, uint8_t *byte2, uint8_t parity); + public: + FECModem() : Modem() {}; + + /** + * Enable the modem. Resets the internal Hamming state and calls + * Modem::enable(). + */ + void enable(void); + + /** + * Checks if there are unprocessed bytes in the receive buffer. + * Parity bytes are accounted for, so if three raw bytes (two data, + * one parity) were received by Modem::receive(), this function will + * return 2. + * @return number of unprocessed data bytes + */ + uint8_t buffer_available(void); + + /** + * Get next byte from the receive buffer. + * @return received byte (0 if it contained uncorrectable errors + * or the buffer is empty) + */ + uint8_t buffer_get(void); +}; + +extern FECModem modem; + +#endif /* FECMODEM_H_ */ diff --git a/src/font.h b/src/font.h new file mode 100644 index 0000000..1cbf20b --- /dev/null +++ b/src/font.h @@ -0,0 +1,244 @@ +#ifndef FONT_H_ +#define FONT_H_ + +#include + +/* + * Font face is based on "Pixel Operator 8" which is licensed under the + * SIL Open Font License 1.1 (compatible to GPL) + */ + +typedef const unsigned char* glyph_t; + +const unsigned char PROGMEM chr_001[] = {0x08,0x00,0x04,0x22,0x02,0x22,0x04,0x00,0x00}; // happy smiley +const unsigned char PROGMEM chr_002[] = {0x08,0x00,0x02,0x24,0x04,0x24,0x02,0x00,0x00}; // sad smiley +const unsigned char PROGMEM chr_003[] = {0x05,0x18,0x24,0x12,0x24,0x18}; // heart +const unsigned char PROGMEM chr_032[] = {0x03,0x00,0x00,0x00}; // +const unsigned char PROGMEM chr_033[] = {0x01,0x7D}; // ! +const unsigned char PROGMEM chr_034[] = {0x04,0x30,0x40,0x30,0x40}; // " +const unsigned char PROGMEM chr_035[] = {0x06,0x12,0x3F,0x12,0x12,0x3F,0x12}; // # +const unsigned char PROGMEM chr_036[] = {0x05,0x12,0x2A,0x7F,0x2A,0x24}; // $ +const unsigned char PROGMEM chr_037[] = {0x07,0x30,0x4A,0x34,0x08,0x16,0x29,0x06}; // % +const unsigned char PROGMEM chr_038[] = {0x05,0x36,0x49,0x49,0x49,0x27}; // & +const unsigned char PROGMEM chr_039[] = {0x01,0x70}; // ' +const unsigned char PROGMEM chr_040[] = {0x03,0x1C,0x22,0x41}; // ( +const unsigned char PROGMEM chr_041[] = {0x03,0x41,0x22,0x1C}; // ) +const unsigned char PROGMEM chr_042[] = {0x05,0x28,0x10,0x7C,0x10,0x28}; // * +const unsigned char PROGMEM chr_043[] = {0x05,0x08,0x08,0x3E,0x08,0x08}; // + +const unsigned char PROGMEM chr_044[] = {0x02,0x01,0x02}; // , +const unsigned char PROGMEM chr_045[] = {0x05,0x08,0x08,0x08,0x08,0x08}; // - +const unsigned char PROGMEM chr_046[] = {0x01,0x01}; // . +const unsigned char PROGMEM chr_047[] = {0x03,0x03,0x1C,0x60}; // / +const unsigned char PROGMEM chr_048[] = {0x05,0x3E,0x45,0x49,0x51,0x3E}; // 0 +const unsigned char PROGMEM chr_049[] = {0x03,0x10,0x20,0x7F}; // 1 +const unsigned char PROGMEM chr_050[] = {0x05,0x21,0x43,0x45,0x49,0x31}; // 2 +const unsigned char PROGMEM chr_051[] = {0x05,0x22,0x41,0x49,0x49,0x36}; // 3 +const unsigned char PROGMEM chr_052[] = {0x05,0x0C,0x14,0x24,0x44,0x7F}; // 4 +const unsigned char PROGMEM chr_053[] = {0x05,0x72,0x51,0x51,0x51,0x4E}; // 5 +const unsigned char PROGMEM chr_054[] = {0x05,0x3E,0x49,0x49,0x49,0x26}; // 6 +const unsigned char PROGMEM chr_055[] = {0x05,0x43,0x44,0x48,0x50,0x60}; // 7 +const unsigned char PROGMEM chr_056[] = {0x05,0x36,0x49,0x49,0x49,0x36}; // 8 +const unsigned char PROGMEM chr_057[] = {0x05,0x32,0x49,0x49,0x49,0x3E}; // 9 +const unsigned char PROGMEM chr_058[] = {0x01,0x12}; // : +const unsigned char PROGMEM chr_059[] = {0x02,0x01,0x12}; // ; +const unsigned char PROGMEM chr_060[] = {0x03,0x08,0x14,0x22}; // < +const unsigned char PROGMEM chr_061[] = {0x05,0x14,0x14,0x14,0x14,0x14}; // = +const unsigned char PROGMEM chr_062[] = {0x03,0x22,0x14,0x08}; // > +const unsigned char PROGMEM chr_063[] = {0x05,0x20,0x40,0x45,0x48,0x30}; // ? +const unsigned char PROGMEM chr_064[] = {0x07,0x3E,0x41,0x49,0x55,0x5D,0x45,0x38}; // @ +const unsigned char PROGMEM chr_065[] = {0x05,0x3F,0x48,0x48,0x48,0x3F}; // A +const unsigned char PROGMEM chr_066[] = {0x05,0x7F,0x49,0x49,0x49,0x36}; // B +const unsigned char PROGMEM chr_067[] = {0x05,0x3E,0x41,0x41,0x41,0x22}; // C +const unsigned char PROGMEM chr_068[] = {0x05,0x7F,0x41,0x41,0x41,0x3E}; // D +const unsigned char PROGMEM chr_069[] = {0x05,0x7F,0x49,0x49,0x41,0x41}; // E +const unsigned char PROGMEM chr_070[] = {0x05,0x7F,0x48,0x48,0x40,0x40}; // F +const unsigned char PROGMEM chr_071[] = {0x05,0x3E,0x41,0x41,0x49,0x2F}; // G +const unsigned char PROGMEM chr_072[] = {0x05,0x7F,0x08,0x08,0x08,0x7F}; // H +const unsigned char PROGMEM chr_073[] = {0x01,0x7F}; // I +const unsigned char PROGMEM chr_074[] = {0x05,0x02,0x01,0x01,0x01,0x7E}; // J +const unsigned char PROGMEM chr_075[] = {0x05,0x7F,0x08,0x14,0x22,0x41}; // K +const unsigned char PROGMEM chr_076[] = {0x05,0x7F,0x01,0x01,0x01,0x01}; // L +const unsigned char PROGMEM chr_077[] = {0x07,0x7F,0x10,0x08,0x04,0x08,0x10,0x7F}; // M +const unsigned char PROGMEM chr_078[] = {0x05,0x7F,0x10,0x08,0x04,0x7F}; // N +const unsigned char PROGMEM chr_079[] = {0x05,0x3E,0x41,0x41,0x41,0x3E}; // O +const unsigned char PROGMEM chr_080[] = {0x05,0x7F,0x48,0x48,0x48,0x30}; // P +const unsigned char PROGMEM chr_081[] = {0x05,0x3E,0x41,0x45,0x42,0x3D}; // Q +const unsigned char PROGMEM chr_082[] = {0x05,0x7F,0x44,0x44,0x46,0x39}; // R +const unsigned char PROGMEM chr_083[] = {0x05,0x32,0x49,0x49,0x49,0x26}; // S +const unsigned char PROGMEM chr_084[] = {0x05,0x40,0x40,0x7F,0x40,0x40}; // T +const unsigned char PROGMEM chr_085[] = {0x05,0x7E,0x01,0x01,0x01,0x7E}; // U +const unsigned char PROGMEM chr_086[] = {0x05,0x7C,0x02,0x01,0x02,0x7C}; // V +const unsigned char PROGMEM chr_087[] = {0x07,0x7E,0x01,0x01,0x1E,0x01,0x01,0x7E}; // W +const unsigned char PROGMEM chr_088[] = {0x05,0x63,0x14,0x08,0x14,0x63}; // X +const unsigned char PROGMEM chr_089[] = {0x05,0x60,0x10,0x0F,0x10,0x60}; // Y +const unsigned char PROGMEM chr_090[] = {0x05,0x43,0x45,0x49,0x51,0x61}; // Z +const unsigned char PROGMEM chr_091[] = {0x03,0x7F,0x41,0x41}; // [ +const unsigned char PROGMEM chr_092[] = {0x03,0x60,0x1C,0x03}; // backslash +const unsigned char PROGMEM chr_093[] = {0x03,0x41,0x41,0x7F}; // ] +const unsigned char PROGMEM chr_094[] = {0x05,0x10,0x20,0x40,0x20,0x10}; // ^ +const unsigned char PROGMEM chr_095[] = {0x05,0x01,0x01,0x01,0x01,0x01}; // _ +const unsigned char PROGMEM chr_096[] = {0x02,0x40,0x20}; // ` +const unsigned char PROGMEM chr_097[] = {0x05,0x02,0x15,0x15,0x15,0x0F}; // a +const unsigned char PROGMEM chr_098[] = {0x05,0x7F,0x11,0x11,0x11,0x0E}; // b +const unsigned char PROGMEM chr_099[] = {0x05,0x0E,0x11,0x11,0x11,0x0A}; // c +const unsigned char PROGMEM chr_100[] = {0x05,0x0E,0x11,0x11,0x11,0x7F}; // d +const unsigned char PROGMEM chr_101[] = {0x05,0x0E,0x15,0x15,0x15,0x0C}; // e +const unsigned char PROGMEM chr_102[] = {0x05,0x10,0x3F,0x50,0x50,0x40}; // f +const unsigned char PROGMEM chr_103[] = {0x05,0x08,0x15,0x15,0x15,0x1E}; // g +const unsigned char PROGMEM chr_104[] = {0x05,0x7F,0x10,0x10,0x10,0x0F}; // h +const unsigned char PROGMEM chr_105[] = {0x01,0x5F}; // i +const unsigned char PROGMEM chr_106[] = {0x05,0x02,0x01,0x01,0x01,0x5E}; // j +const unsigned char PROGMEM chr_107[] = {0x05,0x7F,0x04,0x0C,0x12,0x01}; // k +const unsigned char PROGMEM chr_108[] = {0x01,0x7F}; // l +const unsigned char PROGMEM chr_109[] = {0x07,0x1F,0x10,0x10,0x0C,0x10,0x10,0x0F}; // m +const unsigned char PROGMEM chr_110[] = {0x05,0x1F,0x10,0x10,0x10,0x0F}; // n +const unsigned char PROGMEM chr_111[] = {0x05,0x0E,0x11,0x11,0x11,0x0E}; // o +const unsigned char PROGMEM chr_112[] = {0x05,0x0F,0x14,0x14,0x14,0x08}; // p +const unsigned char PROGMEM chr_113[] = {0x05,0x08,0x14,0x14,0x14,0x0F}; // q +const unsigned char PROGMEM chr_114[] = {0x05,0x1F,0x04,0x08,0x10,0x10}; // r +const unsigned char PROGMEM chr_115[] = {0x05,0x09,0x15,0x15,0x15,0x02}; // s +const unsigned char PROGMEM chr_116[] = {0x05,0x10,0x3E,0x11,0x11,0x01}; // t +const unsigned char PROGMEM chr_117[] = {0x05,0x1E,0x01,0x01,0x01,0x1E}; // u +const unsigned char PROGMEM chr_118[] = {0x05,0x1C,0x02,0x01,0x02,0x1C}; // v +const unsigned char PROGMEM chr_119[] = {0x07,0x1E,0x01,0x01,0x02,0x01,0x01,0x1E}; // w +const unsigned char PROGMEM chr_120[] = {0x05,0x11,0x0A,0x04,0x0A,0x11}; // x +const unsigned char PROGMEM chr_121[] = {0x05,0x19,0x05,0x05,0x05,0x1E}; // y +const unsigned char PROGMEM chr_122[] = {0x05,0x11,0x13,0x15,0x19,0x11}; // z +const unsigned char PROGMEM chr_123[] = {0x04,0x08,0x36,0x41,0x41}; // { +const unsigned char PROGMEM chr_124[] = {0x01,0x7F}; // | +const unsigned char PROGMEM chr_125[] = {0x04,0x41,0x41,0x36,0x08}; // } +const unsigned char PROGMEM chr_126[] = {0x06,0x20,0x40,0x40,0x20,0x20,0x40}; // ~ + +const glyph_t font[] PROGMEM = { + chr_002, // special character for uncorrectable byte errors + chr_001, + chr_002, + chr_003, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_032, + chr_033, + chr_034, + chr_035, + chr_036, + chr_037, + chr_038, + chr_039, + chr_040, + chr_041, + chr_042, + chr_043, + chr_044, + chr_045, + chr_046, + chr_047, + chr_048, + chr_049, + chr_050, + chr_051, + chr_052, + chr_053, + chr_054, + chr_055, + chr_056, + chr_057, + chr_058, + chr_059, + chr_060, + chr_061, + chr_062, + chr_063, + chr_064, + chr_065, + chr_066, + chr_067, + chr_068, + chr_069, + chr_070, + chr_071, + chr_072, + chr_073, + chr_074, + chr_075, + chr_076, + chr_077, + chr_078, + chr_079, + chr_080, + chr_081, + chr_082, + chr_083, + chr_084, + chr_085, + chr_086, + chr_087, + chr_088, + chr_089, + chr_090, + chr_091, + chr_092, + chr_093, + chr_094, + chr_095, + chr_096, + chr_097, + chr_098, + chr_099, + chr_100, + chr_101, + chr_102, + chr_103, + chr_104, + chr_105, + chr_106, + chr_107, + chr_108, + chr_109, + chr_110, + chr_111, + chr_112, + chr_113, + chr_114, + chr_115, + chr_116, + chr_117, + chr_118, + chr_119, + chr_120, + chr_121, + chr_122, + chr_123, + chr_124, + chr_125, + chr_126 +}; + + + +#endif /* FONT_H_ */ diff --git a/src/gpio.cc b/src/gpio.cc new file mode 100644 index 0000000..23fc6dc --- /dev/null +++ b/src/gpio.cc @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#include +#include + +#include "gpio.h" + +GPIO gpio; + +void GPIO::pinMode(const uint8_t pin, uint8_t mode) +{ + /* + * pin is const, so this switch statement is optimized out by the + * compiler. If mode is also a literal, the function call will also + * be optimized out, resulting in just 2 Bytes per pinMode call. + */ + switch (pin) { + case 1: + if (mode == 1) + DDRC |= _BV(PC0); + else + DDRC &= ~_BV(PC0); + break; + case 2: + if (mode == 1) + DDRC |= _BV(PC1); + else + DDRC &= ~_BV(PC1); + break; + case 3: + if (mode == 1) + DDRC |= _BV(PC2); + else + DDRC &= ~_BV(PC2); + break; + case 4: + if (mode == 1) + DDRA |= _BV(PA1); + else + DDRA &= ~_BV(PA1); + break; + } +} + +void GPIO::digitalWrite(const uint8_t pin, uint8_t value) +{ + /* + * will be optimized out -- see above + */ + switch (pin) { + case 1: + if (value == 1) + PORTC |= _BV(PC0); + else + PORTC &= ~_BV(PC0); + break; + case 2: + if (value == 1) + PORTC |= _BV(PC1); + else + PORTC &= ~_BV(PC1); + break; + case 3: + if (value == 1) + PORTC |= _BV(PC2); + else + PORTC &= ~_BV(PC2); + break; + case 4: + if (value == 1) + PORTA |= _BV(PA1); + else + PORTA &= ~_BV(PA1); + break; + } +} + +uint8_t GPIO::digitalRead(const uint8_t pin) +{ + /* + * will be optimized out -- see above + */ + switch (pin) { + case 1: + return (PINC & _BV(PC0)); + case 2: + return (PINC & _BV(PC1)); + case 3: + return (PINC & _BV(PC2)); + case 4: + return (PINA & _BV(PA1)); + } + return 0; +} diff --git a/src/gpio.h b/src/gpio.h new file mode 100644 index 0000000..addc6e8 --- /dev/null +++ b/src/gpio.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#include +#include + +/** + * GPIO class for the GPIO pins E1 ... E4 + * + * For now, we only support very simple Arduino-like digital pin operations. + */ +class GPIO { + public: + GPIO() {}; + + void pinMode(const uint8_t pin, uint8_t mode); + void digitalWrite(const uint8_t pin, uint8_t value); + uint8_t digitalRead(const uint8_t pin); +}; + +extern GPIO gpio; diff --git a/src/hamming.h b/src/hamming.h new file mode 100644 index 0000000..275185b --- /dev/null +++ b/src/hamming.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#ifndef HAMMING_H_ +#define HAMMING_H_ + +#include + +enum HammingResult : uint8_t { + NO_ERROR = 0, + ERROR_IN_PARITY = 0xfe, + UNCORRECTABLE = 0xff, +}; + +const uint8_t hammingParityLow[] PROGMEM = +{ 0, 3, 5, 6, 6, 5, 3, 0, 7, 4, 2, 1, 1, 2, 4, 7 }; + +const uint8_t hammingParityHigh[] PROGMEM = +{ 0, 9, 10, 3, 11, 2, 1, 8, 12, 5, 6, 15, 7, 14, 13, 4 }; + +const uint8_t PROGMEM hammingParityCheck[] = { + NO_ERROR, + ERROR_IN_PARITY, + ERROR_IN_PARITY, + 0x01, + ERROR_IN_PARITY, + 0x02, + 0x04, + 0x08, + ERROR_IN_PARITY, + 0x10, + 0x20, + 0x40, + 0x80, + UNCORRECTABLE, + UNCORRECTABLE, + UNCORRECTABLE +}; + +#endif /* HAMMING_H_ */ diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 0000000..9e37b59 --- /dev/null +++ b/src/main.cc @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#include +#include + +#include "system.h" + +int main (void) +{ + rocket.initialize(); + + while (1) { + // nothing to do here, go to idle to save power + SMCR = _BV(SE); + asm("sleep"); + /* + * The display timer causes a wakeup after 256µs. Run the system + * loop after the timer's ISR is done. + * The Modem also causes wakeups, which is pretty convenient since + * it means we can immediately process the received data. + */ + rocket.loop(); + } + + return 0; +} diff --git a/src/modem.cc b/src/modem.cc new file mode 100644 index 0000000..e34910a --- /dev/null +++ b/src/modem.cc @@ -0,0 +1,117 @@ +/* Name: modem.c + * + * Audio modem for Attiny85 & other AVR chips with modifications + * + * Author: Jari Tulilahti + * Copyright: 2014 Rakettitiede Oy and 2016 Daniel Friesel + * License: LGPLv3, see COPYING, and COPYING.LESSER -files for more info + */ + +#include +#include +#include "modem.h" +#include "fecmodem.h" + +extern FECModem modem; + +bool Modem::newTransmission() +{ + if (new_transmission) { + new_transmission = false; + return true; + } + return false; +} + +/* + * Returns number of available bytes in ringbuffer or 0 if empty + */ +uint8_t Modem::buffer_available() { + return buffer_head - buffer_tail; +} + +/* + * Store 1 byte in ringbuffer + */ +inline void Modem::buffer_put(const uint8_t c) { + if (buffer_available() != MODEM_BUFFER_SIZE) { + buffer[buffer_head++ % MODEM_BUFFER_SIZE] = c; + } +} + +/* + * Fetch 1 byte from ringbuffer + */ +uint8_t Modem::buffer_get() { + uint8_t b = 0; + if (buffer_available() != 0) { + b = buffer[buffer_tail++ % MODEM_BUFFER_SIZE]; + } + return b; +} + +/* + * Start the modem by enabling Pin Change Interrupts & Timer + */ +void Modem::enable() { + /* Enable R1 */ + DDRA |= _BV(PA3); + PORTA |= _BV(PA3); + + /* Modem pin as input */ + MODEM_DDR &= ~_BV(MODEM_PIN); + + /* Enable Pin Change Interrupts and PCINT for MODEM_PIN */ + MODEM_PCMSK |= _BV(MODEM_PCINT); + PCICR |= _BV(MODEM_PCIE); + + /* Timer: TCCR1: CS10 and CS11 bits: 8MHz clock with Prescaler 64 = 125kHz timer clock */ + TCCR1B = _BV(CS11) | _BV(CS10); +} + +void Modem::disable() +{ + PORTA &= ~_BV(PA3); + DDRA &= ~_BV(PA3); +} + +void Modem::receive() { + /* Static variables instead of globals to keep scope inside ISR */ + static uint8_t modem_bit = 0; + static uint8_t modem_bitlen = 0; + static uint8_t modem_byte = 0; + + /* Read & Zero Timer/Counter 1 value */ + uint8_t modem_pulselen = MODEM_TIMER; + MODEM_TIMER = 0; + + /* + * Check if we received Start/Sync -pulse. + * Calculate bit signal length middle point from pulse. + * Return from ISR immediately. + */ + if (modem_pulselen > MODEM_SYNC_LEN) { + modem_bitlen = (modem_pulselen >> 2); + modem_bit = 0; + new_transmission = true; + return; + } + + /* + * Shift byte and set high bit according to the pulse length. + * Long pulse = 1, Short pulse = 0 + */ + modem_byte = (modem_byte >> 1) | (modem_pulselen < modem_bitlen ? 0x00 : 0x80); + + /* Check if we received complete byte and store it in ring buffer */ + if (!(++modem_bit % 0x08)) { + buffer_put(modem_byte); + } +} + +/* + * Pin Change Interrupt Vector. This is The Modem. + */ +ISR(PCINT3_vect) { + modem.receive(); +} diff --git a/src/modem.h b/src/modem.h new file mode 100644 index 0000000..7409214 --- /dev/null +++ b/src/modem.h @@ -0,0 +1,82 @@ +/* Name: modem.h + * Author: Jari Tulilahti + * Copyright: 2014 Rakettitiede Oy and 2016 Daniel Friesel + * License: LGPLv3, see COPYING, and COPYING.LESSER -files for more info + */ + +#ifndef MODEM_H_ +#define MODEM_H_ + +#include +#include + +/* Modem ring buffer size must be power of 2 */ +#define MODEM_BUFFER_SIZE 64 + +/* Modem defines */ +#define MODEM_SYNC_LEN 42 +#define MODEM_TIMER TCNT1L +#define MODEM_PCINT PCINT24 +#define MODEM_PCMSK PCMSK3 +#define MODEM_PCIE PCIE3 +#define MODEM_PIN PA0 +#define MODEM_DDR DDRA + +/** + * Receive-only modem. Sets up a pin change interrupt on the modem pin + * and receives bytes using a simple protocol. Does not detect or correct + * transmission errors. + */ +class Modem { + private: + uint8_t buffer_head; + uint8_t buffer_tail; + uint8_t buffer[MODEM_BUFFER_SIZE]; + bool new_transmission; + void buffer_put(const uint8_t c); + public: + Modem() {new_transmission = false;}; + + /** + * Checks if a new transmission was started since the last call + * to this function. Returns true if that is the case and false + * otherwise. + * @return true if a new transmission was started + */ + bool newTransmission(); + + /** + * Checks if there are unprocessed bytes in the modem receive buffer. + * @return number of unprocessed bytes + */ + uint8_t buffer_available(void); + + /** + * Get next byte from modem receive buffer. + * @return next unprocessed byte (0 if the buffer is empty) + */ + uint8_t buffer_get(void); + + /** + * Enable the modem. Turns on the input voltage divider on MODEM_PIN + * and enables the receive interrupt (MODEM_PCINT). + */ + void enable(void); + + /** + * Disable the modem. Disables the receive interrupt and turns off + * the input voltage divider on MODEM_PIN. + */ + void disable(void); + + /** + * Called by the pin change interrupt service routine whenever the + * modem pin is toggled. Detects sync pulses, receives bits and + * stores complete bytes in the buffer. + * + * Do not call this function yourself. + */ + void receive(void); +}; + +#endif /* MODEM_H_ */ diff --git a/src/static_patterns.h b/src/static_patterns.h new file mode 100644 index 0000000..94cf51e --- /dev/null +++ b/src/static_patterns.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#ifndef STATIC_PATTERNS_H_ +#define STATIC_PATTERNS_H_ + +#include + +/* + * Note: Static patterns must not be longer than 128 bytes (headers excluded). + * See MessageSpecification.md for the meaning of their headers. + */ + +const uint8_t PROGMEM shutdownPattern[] = { + 0x20, 0x40, + 0x0e, 0x0f, + 0xff, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xff, + 0x7e, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x7e, + 0x3c, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x3c, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, + 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +const uint8_t PROGMEM flashingPattern[] = { + 0x20, 0x10, + 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x33, 0x55, 0x98, 0x00, 0x00 +}; + +#ifdef LANG_DE +const uint8_t PROGMEM emptyPattern[] = { + 0x10, 0x29, + 0xc0, 0x00, + ' ', 1, ' ', 'B', 'l', 'i', 'n', 'k', 'e', 'n', 'r', 'o', 'c', 'k', 'e', + 't', ' ', 'v', '1', '.', '0', ' ', '-', ' ', 'S', 'p', 'e', 'i', 'c', 'h', + 'e', 'r', ' ', 'i', 's', 't', ' ', 'l', 'e', 'e', 'r' +}; +#else +const uint8_t PROGMEM emptyPattern[] = { + 0x10, 0x28, + 0xc0, 0x00, + ' ', 1, ' ', 'B', 'l', 'i', 'n', 'k', 'e', 'n', 'r', 'o', 'c', 'k', 'e', + 't', ' ', 'v', '1', '.', '0', ' ', '-', ' ', 'S', 't', 'o', 'r', 'a', 'g', + 'e', ' ', 'i', 's', ' ', 'e', 'm', 'p', 't', 'y' +}; +#endif + +#ifdef LANG_DE +const uint8_t PROGMEM timeoutPattern[] = { + 0x10, 0x16, + 0xc0, 0x00, + ' ', 2, ' ', 'U', 'e', 'b', 'e', 'r', 't', 'r', 'a', 'g', 'u', 'n', 'g', + 's', 'f', 'e', 'h', 'l', 'e', 'r' +}; +#else +const uint8_t PROGMEM timeoutPattern[] = { + 0x10, 0x15, + 0xc0, 0x00, + ' ', 2, ' ', 'T', 'r', 'a', 'n', 's', 'm', 'i', 's', 's', 'i', 'o', 'n', + ' ', 'e', 'r', 'r', 'o', 'r' +}; +#endif + +#endif /* STATIC_PATTERNS_H_ */ diff --git a/src/storage.cc b/src/storage.cc new file mode 100644 index 0000000..05a1d44 --- /dev/null +++ b/src/storage.cc @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#include +#include +#include + +#include "storage.h" + +Storage storage; + +/* + * EEPROM data structure ("file system"): + * + * Organized as 32B-pages, all animations/texts are page-aligned. Byte 0 .. + * 255 : storage metadata. Byte 0 contains the number of animations, byte 1 the + * page offset of the first animation, byte 2 of the second, and so on. + * Byte 256+: texts/animations without additional storage metadata, aligned + * to 32B. So, a maximum of 256-(256/32) = 248 texts/animations can be stored, + * and a maximum of 255 * 32 = 8160 Bytes (almost 8 kB / 64 kbit) can be + * addressed. To support larger EEPROMS, change the metadate area to Byte 2 .. + * 511 and use 16bit page pointers. + * + * The text/animation size is not limited by this approach. + * + * Example: + * Byte 0 = 3 -> we've got a total of three animations + * Byte 1 = 0 -> first text/animation starts at byte 256 + 32*0 = 256 + * Byte 2 = 4 -> second starts at byte 256 + 32*4 = 384 + * Byte 3 = 5 -> third starts at 256 + 32*5 * 416 + * Byte 4 = whatever + * . + * . + * . + * Byte 256ff = first text/animation. Has a header encoding its length in bytes. + * Byte 384ff = second + * Byte 416ff = third + * . + * . + * . + */ + +void Storage::enable() +{ + /* + * Set I2C clock frequency to 100kHz. + * freq = F_CPU / (16 + (2 * TWBR * TWPS) ) + * let TWPS = "00" = 1 + * -> TWBR = (F_CPU / 100000) - 16 / 2 + */ + TWSR = 0; // the lower two bits control TWPS + TWBR = ((F_CPU / 100000UL) - 16) / 2; + + i2c_read(0, 0, 1, &num_anims); +} + + +/* + * Send an I2C (re)start condition and the EEPROM address in read mode. Returns + * after it has been transmitted successfully. + */ +uint8_t Storage::i2c_start_read() +{ + TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN); + while (!(TWCR & _BV(TWINT))); + if (!(TWSR & 0x18)) // 0x08 == START ok, 0x10 == RESTART ok + return I2C_START_ERR; + + // Note: The R byte ("... | 1") causes the TWI momodule to switch to + // Master Receive mode + TWDR = (I2C_EEPROM_ADDR << 1) | 1; + TWCR = _BV(TWINT) | _BV(TWEN); + while (!(TWCR & _BV(TWINT))); + if (TWSR != 0x40) // 0x40 == SLA+R transmitted, ACK receveid + return I2C_ADDR_ERR; + + return I2C_OK; +} + +/* + * Send an I2C (re)start condition and the EEPROM address in write mode. + * Returns after it has been transmitted successfully. + */ +uint8_t Storage::i2c_start_write() +{ + TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN); + while (!(TWCR & _BV(TWINT))); + if (!(TWSR & 0x18)) // 0x08 == START ok, 0x10 == RESTART ok + return I2C_START_ERR; + + TWDR = (I2C_EEPROM_ADDR << 1) | 0; + TWCR = _BV(TWINT) | _BV(TWEN); + while (!(TWCR & _BV(TWINT))); + if (TWSR != 0x18) // 0x18 == SLA+W transmitted, ACK received + return I2C_ADDR_ERR; + + return I2C_OK; +} + +/* + * Send an I2C stop condition. + */ +void Storage::i2c_stop() +{ + TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN); +} + +/* + * Sends len bytes to the EEPROM. Note that this method does NOT + * send I2C start or stop conditions. + */ +uint8_t Storage::i2c_send(uint8_t len, uint8_t *data) +{ + uint8_t pos = 0; + + for (pos = 0; pos < len; pos++) { + TWDR = data[pos]; + TWCR = _BV(TWINT) | _BV(TWEN); + while (!(TWCR & _BV(TWINT))); + if (TWSR != 0x28) // 0x28 == byte transmitted, ACK received + return pos; + } + + return pos; +} + +/* + * Receives len bytes from the EEPROM into data. Note that this method does + * NOT send I2C start or stop conditions. + */ +uint8_t Storage::i2c_receive(uint8_t len, uint8_t *data) +{ + uint8_t pos = 0; + + for (pos = 0; pos < len; pos++) { + if (pos == len-1) { + // Don't ACK the last byte + TWCR = _BV(TWINT) | _BV(TWEN); + } else { + // Automatically send ACK + TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA); + } + while (!(TWCR & _BV(TWINT))); + data[pos] = TWDR; + /* + * No error handling here -- We send the acks, the EEPROM only + * supplies raw data, so there's no way of knowing whether it's still + * talking to us or we're just reading garbage. + */ + } + + return pos; +} + +/* + * Writes len bytes of data into the EEPROM, starting at byte number pos. + * Does not check for page boundaries. + * Includes a complete I2C transaction. + */ +uint8_t Storage::i2c_write(uint8_t addrhi, uint8_t addrlo, uint8_t len, uint8_t *data) +{ + uint8_t addr_buf[2]; + uint8_t num_tries; + + addr_buf[0] = addrhi; + addr_buf[1] = addrlo; + + /* + * The EEPROM might be busy processing a write command, which can + * take up to 10ms. Wait up to 16ms to respond before giving up. + * All other error conditions (even though they should never happen[tm]) + * are handled the same way. + */ + for (num_tries = 0; num_tries < 32; num_tries++) { + if (num_tries > 0) + _delay_us(500); + + if (i2c_start_write() != I2C_OK) + continue; // EEPROM is busy writing + + if (i2c_send(2, addr_buf) != 2) + continue; // should not happen + + if (i2c_send(len, data) != len) + continue; // should not happen + + i2c_stop(); + return I2C_OK; + } + + i2c_stop(); + return I2C_ERR; +} + +/* + * Reads len bytes of data from the EEPROM, starting at byte number pos. + * Does not check for page boundaries. + * Includes a complete I2C transaction. + */ +uint8_t Storage::i2c_read(uint8_t addrhi, uint8_t addrlo, uint8_t len, uint8_t *data) +{ + uint8_t addr_buf[2]; + uint8_t num_tries; + + addr_buf[0] = addrhi; + addr_buf[1] = addrlo; + + /* + * See comments in i2c_write. + */ + for (num_tries = 0; num_tries < 32; num_tries++) { + if (num_tries > 0) + _delay_us(500); + + if (i2c_start_write() != I2C_OK) + continue; // EEPROM is busy writing + + if (i2c_send(2, addr_buf) != 2) + continue; // should not happen + + if (i2c_start_read() != I2C_OK) + continue; // should not happen + + if (i2c_receive(len, data) != len) + continue; // should not happen + + i2c_stop(); + return I2C_OK; + } + + i2c_stop(); + return I2C_ERR; +} + +void Storage::reset() +{ + first_free_page = 0; + num_anims = 0; +} + +void Storage::sync() +{ + i2c_write(0, 0, 1, &num_anims); +} + +bool Storage::hasData() +{ + // Unprogrammed EEPROM pages always read 0xff + if (num_anims == 0xff) + return false; + return num_anims; +} + +void Storage::load(uint8_t idx, uint8_t *data) +{ + i2c_read(0, 1 + idx, 1, &page_offset); + + /* + * Unconditionally read 132 bytes. The data buffer must hold at least + * 132 bytes anyways, and this way we can save one I2C transaction. If + * there is any speed penalty cause by this I wasn't able to notice it. + * Also note that the EEPROM automatically wraps around when the end of + * memory is reached, so this edge case doesn't need to be accounted for. + */ + i2c_read(1 + (page_offset / 8), (page_offset % 8) * 32, 132, data); +} + +void Storage::loadChunk(uint8_t chunk, uint8_t *data) +{ + uint8_t this_page_offset = page_offset + (4 * chunk); + + // Note that we do not load headers here -> 128 instead of 132 bytes + i2c_read(1 + (this_page_offset / 8), (this_page_offset % 8) * 32 + 4, 128, data); +} + +void Storage::save(uint8_t *data) +{ + /* + * Technically, we can store up to 255 patterns. However, Allowing + * 255 patterns (-> num_anims = 0xff) means we can't easily + * distinguish between an EEPROM with 255 patterns and a factory-new + * EEPROM (which just reads 0xff everywhere). So only 254 patterns + * are allowed. + */ + if (num_anims < 254) { + /* + * Bytes 0 .. 255 (pages 0 .. 7) are reserved for storage metadata, + * first_free_page counts pages starting at byte 256 (page 8). + * So, first_free_page == 247 addresses EEPROM bytes 8160 .. 8191. + * first_free_page == 248 would address bytes 8192 and up, which don't + * exist -> don't save anything afterwards. + * + * Note that at the moment (stored patterns are aligned to page + * boundaries) this means we can actually only store up to 248 + * patterns. + */ + if (first_free_page < 248) { + num_anims++; + i2c_write(0, num_anims, 1, &first_free_page); + append(data); + } + } +} + +void Storage::append(uint8_t *data) +{ + // see comment in Storage::save() + if (first_free_page < 248) { + // the header indicates the length of the data, but we really don't care + // - it's easier to just write the whole page and skip the trailing + // garbage when reading. + i2c_write(1 + (first_free_page / 8), (first_free_page % 8) * 32, 32, data); + first_free_page++; + } +} diff --git a/src/storage.h b/src/storage.h new file mode 100644 index 0000000..6b76190 --- /dev/null +++ b/src/storage.h @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#include + +#define I2C_EEPROM_ADDR 0x50 + +class Storage { + private: + /** + * Number of animations on the storage, AKA contents of byte 0x0000. + * A value of 0xff indicates that the EEPROM was never written to + * and therefore contains no animations. + */ + uint8_t num_anims; + + /** + * Page offset of the pattern read by the last load() call. Used to + * calculate the read address in loadChunk(). The animation this + * offset refers to starts at byte 256 + (32 * page_offset). + */ + uint8_t page_offset; + + /** + * First free page (excluding the 256 byte metadata area) on the + * EEPROM. Used by save() and append(). This value refers to the + * EEPROM bytes 256 + (32 * first_free_page) and up. + */ + uint8_t first_free_page; + + enum I2CStatus : uint8_t { + I2C_OK, + I2C_START_ERR, + I2C_ADDR_ERR, + I2C_ERR + }; + + /** + * Sends an I2C start condition and the I2C_EEPROM_ADDR with the + * write flag set. + * + * @return An I2CStatus value indicating success/failure + */ + uint8_t i2c_start_write(void); + + /** + * Sends an I2C start condition and the I2C_EEPROM_ADDR with the + * read flag set. + * + * @return An I2CStatus value indicating success/failure + */ + uint8_t i2c_start_read(void); + + /** + * Sends an I2C stop condition. Does not check for errors. + */ + void i2c_stop(void); + + /** + * Sends up to len bytes of data via I2C. i2c_start_write() must + * have been called successfully for this to work. + * + * @param len number of bytes to send + * @param data pointer to data buffer, must be at least len bytes + * @return number of bytes which were successfully sent (0 .. len) + */ + uint8_t i2c_send(uint8_t len, uint8_t *data); + + /** + * Receives up to len bytes of data via I2C. i2c_start_read() must + * have been called successfully for this to work. + * + * @param len number of bytes to receive + * @param data pointer to data buffer, must be at least len bytes + * @return number of bytes which were successfully received (0 .. len) + */ + uint8_t i2c_receive(uint8_t len, uint8_t *data); + + /** + * Reads len bytes of data stored on addrhi, addrlo from the EEPROM + * into the data buffer. Does a complete I2C transaction including + * start and stop conditions. addrlo may start at an arbitrary + * position, page boundaries are irrelevant for this function. If a + * read reaches the end of the EEPROM memory, it will wrap around to + * byte 0x0000. + * + * @param addrhi upper address byte. Must be less than 32 + * @param addrlo lower address byte + * @param len number of bytes to read + * @param data pointer to data buffer, must be at least len bytes + * @return An I2CStatus value indicating success/failure + */ + uint8_t i2c_read(uint8_t addrhi, uint8_t addrlo, uint8_t len, uint8_t *data); + + /** + * Writes len bytes of data from the data buffer into addrhi, addrlo + * on the EEPROM. Does a complete I2C transaction including start and + * stop conditions. Note that the EEPROM consists of 32 byte pages, + * so it is not possible to write more than 32 bytes in one + * operation. Also note that this function does not check for page + * boundaries. Starting a 32-byte write in the middle of a page will + * put the first 16 bytes where they belong, while the second 16 + * bytes will wrap around to the first 16 bytes of the page. + * + * @param addrhi upper address byte. Must be less than 32 + * @param addrlo lower address byte + * @param len number of bytes to write + * @param data pointer to data buffer, must be at least len bytes + * @return An I2CStatus value indicating success/failure + */ + uint8_t i2c_write(uint8_t addrhi, uint8_t addrlo, uint8_t len, uint8_t *data); + + public: + Storage() { num_anims = 0; first_free_page = 0;}; + + /** + * Enables the storage hardware: Configures the internal I2C + * module and reads num_anims from the EEPROM. + */ + void enable(); + + /** + * Prepares the storage for a complete overwrite by setting the + * number of stored animations to zero. The next save operation + * will get pattern id 0 and overwrite the first stored pattern. + * + * Note that this function does not write anything to the + * EEPROM. Use Storage::sync() for that. + */ + void reset(); + + /** + * Writes the current number of animations (as set by reset() or + * save() to the EEPROM. Required to get a consistent storage state + * after a power cycle. + */ + void sync(); + + /** + * Checks whether the EEPROM contains animathion data. + * + * @return true if the EEPROM contains valid-looking data + */ + bool hasData(); + + /** + * Accessor for the number of saved patterns on the EEPROM. + * A return value of 255 (0xff) means that there are no patterns + * on the EEPROM (and hasData() returns false in that case). + * + * @return number of patterns + */ + uint8_t numPatterns() { return num_anims; }; + + /** + * Loads pattern number idx from the EEPROM. The + * + * @param idx pattern index (starting with 0) + * @param pointer to the data structure for the pattern. Must be + * at least 132 bytes + */ + void load(uint8_t idx, uint8_t *data); + + /** + * Load partial pattern chunk (without header) from EEPROM. + * + * @param chunk 128 byte-offset inside pattern (starting with 0) + * @param data pointer to data structure for the pattern. Must be + * at least 128 bytes + */ + void loadChunk(uint8_t chunk, uint8_t *data); + + /** + * Save (possibly partial) pattern on the EEPROM. 32 bytes of + * dattern data will be read and stored, regardless of the + * pattern header. + * + * @param data pattern data. Must be at least 32 bytes + */ + void save(uint8_t *data); + + /** + * Continue saving a pattern on the EEPROM. Appends 32 bytes of + * pattern data after the most recently written block of data + * (i.e., to the pattern which is currently being saved). + * + * @param data pattern data. Must be at least 32 bytes + */ + void append(uint8_t *data); +}; + +extern Storage storage; diff --git a/src/system.cc b/src/system.cc new file mode 100644 index 0000000..535702b --- /dev/null +++ b/src/system.cc @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#include +#include +#include +#include +#include +#include + +#include "display.h" +#include "fecmodem.h" +#include "storage.h" +#include "system.h" +#include "static_patterns.h" + +#define SHUTDOWN_THRESHOLD 2048 + +System rocket; + +animation_t active_anim; + +uint8_t disp_buf[132]; // 4 byte header + 128 byte data +uint8_t *rx_buf = disp_buf + sizeof(disp_buf) - 33; + +void System::initialize() +{ + // disable ADC to save power + PRR |= _BV(PRADC); + + // dito + wdt_disable(); + + // Enable pull-ups on PC3 and PC7 (button pins) + PORTC |= _BV(PC3) | _BV(PC7); + + display.enable(); + modem.enable(); + storage.enable(); + + //storage.reset(); + //storage.save((uint8_t *)"\x10\x0a\x11\x00nootnoot"); + //storage.save((uint8_t *)"\x10\x09\x20\x00" "fnordor"); + //storage.save((uint8_t *)"\x10\x05\x20\x00 \x01 "); + //storage.save((uint8_t *)"\x20\x22\x08\x02" + // "\x00\x04\x22\x02\x22\x04\x00\x00" + // "\x00\x00\x00\x00\x00\x00\x00\x00" + // "\x00\x04\x22\x02\x22\x04\x00\x00" + // "\x00\x00\x00\x00"); + //storage.append((uint8_t *)"\x00\x00\x00\x00"); + + sei(); + + current_anim_no = 0; + loadPattern(0); +} + +void System::loadPattern_P(const uint8_t *pattern_ptr) +{ + uint8_t i; + + for (i = 0; i < 4; i++) + disp_buf[i] = pgm_read_byte(pattern_ptr + i); + + for (i = 0; i < disp_buf[1]; i++) + disp_buf[i+4] = pgm_read_byte(pattern_ptr + i + 4); + + loadPattern_buf(disp_buf); +} + +void System::loadPattern_buf(uint8_t *pattern) +{ + active_anim.type = (AnimationType)(pattern[0] >> 4); + active_anim.length = (pattern[0] & 0x0f) << 8; + active_anim.length += pattern[1]; + + if (active_anim.type == AnimationType::TEXT) { + active_anim.speed = 250 - (pattern[2] & 0xf0); + active_anim.delay = (pattern[2] & 0x0f ); + active_anim.direction = pattern[3] >> 4; + } else if (active_anim.type == AnimationType::FRAMES) { + active_anim.speed = 250 - ((pattern[2] & 0x0f) << 4); + active_anim.delay = (pattern[3] & 0x0f); + active_anim.direction = 0; + } + + active_anim.data = pattern + 4; + display.show(&active_anim); +} + +void System::loadPattern(uint8_t anim_no) +{ + if (storage.hasData()) { + storage.load(anim_no, disp_buf); + loadPattern_buf(disp_buf); + } else { + loadPattern_P(emptyPattern); + } +} + +void System::receive(void) +{ + static uint8_t rx_pos = 0; + static uint16_t remaining_bytes = 0; + uint8_t rx_byte = modem.buffer_get(); + + /* + * START* and PATTERN* are sync signals, everything else needs to be + * stored on the EEPROM. + * (Note that the C++ standard guarantees "rxExpect > PATTERN2" to match + * for HEADER*, META* and DATA since they are located after PATTERN2 + * in the RxExpect enum declaration) + */ + if (rxExpect > PATTERN2) { + rx_buf[rx_pos++] = rx_byte; + /* + * HEADER and META are not included in the length + * -> only count bytes for DATA. + */ + if (rxExpect > META2) { + remaining_bytes--; + } + } + + switch(rxExpect) { + case START1: + if (rx_byte == BYTE_START) + rxExpect = START2; + else + rxExpect = NEXT_BLOCK; + break; + case START2: + if (rx_byte == BYTE_START) { + rxExpect = PATTERN1; + storage.reset(); + loadPattern_P(flashingPattern); + MCUSR &= ~_BV(WDRF); + cli(); + // watchdog interrupt after 4 seconds + WDTCSR = _BV(WDCE) | _BV(WDE); + WDTCSR = _BV(WDIE) | _BV(WDP3); + sei(); + } else { + rxExpect = NEXT_BLOCK; + } + break; + case NEXT_BLOCK: + if (rx_byte == BYTE_START) + rxExpect = START2; + else if (rx_byte == BYTE_PATTERN) + rxExpect = PATTERN2; + else if (rx_byte == BYTE_END) { + storage.sync(); + current_anim_no = 0; + loadPattern(0); + rxExpect = START1; + wdt_disable(); + } + break; + case PATTERN1: + if (rx_byte == BYTE_PATTERN) + rxExpect = PATTERN2; + else + rxExpect = NEXT_BLOCK; + break; + case PATTERN2: + rx_pos = 0; + if (rx_byte == BYTE_PATTERN) + rxExpect = HEADER1; + else + rxExpect = NEXT_BLOCK; + break; + case HEADER1: + rxExpect = HEADER2; + remaining_bytes = (rx_byte & 0x0f) << 8; + break; + case HEADER2: + rxExpect = META1; + remaining_bytes += rx_byte; + wdt_reset(); + break; + case META1: + rxExpect = META2; + break; + case META2: + rxExpect = DATA_FIRSTBLOCK; + /* + * skip empty patterns (would bork because of remaining_bytes-- + * otherwise + */ + if (remaining_bytes == 0) + rxExpect = NEXT_BLOCK; + break; + case DATA_FIRSTBLOCK: + if (remaining_bytes == 0) { + rxExpect = NEXT_BLOCK; + storage.save(rx_buf); + } else if (rx_pos == 32) { + rxExpect = DATA; + rx_pos = 0; + storage.save(rx_buf); + } + break; + case DATA: + if (remaining_bytes == 0) { + rxExpect = NEXT_BLOCK; + storage.append(rx_buf); + } else if (rx_pos == 32) { + rx_pos = 0; + storage.append(rx_buf); + wdt_reset(); + } + break; + } +} + +void System::loop() +{ + // First, check for a shutdown request (long press on both buttons) + if ((PINC & (_BV(PC3) | _BV(PC7))) == 0) { + /* + * Naptime! + * (But not before both buttons have been pressed for at least + * SHUTDOWN_THRESHOLD * 0.256 ms) + */ + if (want_shutdown < SHUTDOWN_THRESHOLD) { + want_shutdown++; + } + else { + shutdown(); + want_shutdown = 0; + } + } + else { + want_shutdown = 0; + } + + if (btn_debounce == 0) { + if ((PINC & _BV(PC3)) == 0) { + btnMask = (ButtonMask)(btnMask | BUTTON_RIGHT); + } + if ((PINC & _BV(PC7)) == 0) { + btnMask = (ButtonMask)(btnMask | BUTTON_LEFT); + } + /* + * Only handle button presses when they are released to avoid + * double actions, such as switching to the next/previous pattern + * when the user actually wants to press the shutdown combo. + */ + if ((PINC & (_BV(PC3) | _BV(PC7))) == (_BV(PC3) | _BV(PC7))) { + if (btnMask == BUTTON_RIGHT) { + current_anim_no = (current_anim_no + 1) % storage.numPatterns(); + loadPattern(current_anim_no); + } else if (btnMask == BUTTON_LEFT) { + if (current_anim_no == 0) + current_anim_no = storage.numPatterns() - 1; + else + current_anim_no--; + loadPattern(current_anim_no); + } + btnMask = BUTTON_NONE; + /* + * Ignore keypresses for 25ms to work around bouncing buttons + */ + btn_debounce = 100; + } + } else { + btn_debounce--; + } + + while (modem.buffer_available()) { + receive(); + } + + display.update(); +} + +void System::shutdown() +{ + uint8_t i; + + modem.disable(); + + // show power down image + loadPattern_P(shutdownPattern); + + // wait until both buttons are released + while (!((PINC & _BV(PC3)) && (PINC & _BV(PC7)))) + display.update(); + + // and some more to debounce the buttons (and finish powerdown animation) + for (i = 0; i < 200; i++) { + display.update(); + _delay_ms(1); + } + + // turn off display to indicate we're about to shut down + display.disable(); + + // actual naptime + + // enable PCINT on PC3 (PCINT11) and PC7 (PCINT15) for wakeup + PCMSK1 |= _BV(PCINT15) | _BV(PCINT11); + PCICR |= _BV(PCIE1); + + // go to power-down mode + SMCR = _BV(SM1) | _BV(SE); + asm("sleep"); + + // execution will resume here - disable PCINT again. + // Don't disable PCICR, something else might need it. + PCMSK1 &= ~(_BV(PCINT15) | _BV(PCINT11)); + + // turn on display + loadPattern(current_anim_no); + display.enable(); + + /* + * Wait for wakeup button(s) to be released to avoid accidentally + * going back to sleep again or switching the active pattern. + */ + while (!((PINC & _BV(PC3)) && (PINC & _BV(PC7)))) + display.update(); + + // debounce + for (i = 0; i < 100; i++) { + display.update(); + _delay_ms(1); + } + + // finally, turn on the modem... + modem.enable(); + + // ... and reset the receive state machine + rxExpect = START1; +} + +void System::handleTimeout() +{ + modem.disable(); + modem.enable(); + rxExpect = START1; + current_anim_no = 0; + loadPattern_P(timeoutPattern); +} + +ISR(PCINT1_vect) +{ + // we use PCINT1 for wakeup, so we need an (empty) ISR for it +} + +ISR(WDT_vect) +{ + /* + * Modem transmission was interrupted without END byte. Reset state + * machine and show timeout message. + */ + wdt_disable(); + rocket.handleTimeout(); +} diff --git a/src/system.h b/src/system.h new file mode 100644 index 0000000..eb5d9dd --- /dev/null +++ b/src/system.h @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2016 by Daniel Friesel + * + * License: You may use, redistribute and/or modify this file under the terms + * of either: + * * The GNU LGPL v3 (see COPYING and COPYING.LESSER), or + * * The 3-clause BSD License (see COPYING.BSD) + * + */ + +#include + +#define SHUTDOWN_THRESHOLD 2048 + +/** + * Contains the system idle loop. Checks for button presses, handles + * standby/resume, reads data from the Modem and updates the Display. + */ +class System { + private: + /** + * Shutdown threshold counter. Contains the time since both + * buttons were first pressed at the same time in 256µs steps. + */ + uint16_t want_shutdown; + + /** + * Debounce counter for button presses. Buttons are ignored while + * this value is greater than zero + */ + uint8_t btn_debounce; + + /** + * Index of the currently active animation + */ + uint8_t current_anim_no; + + /** + * Shuts down the entire system. Shows a shutdown animation, waits + * untel both buttons are released, turns off all hardware and puts + * the system to sleep. Re-enables the hardware and shows the + * last active animation after a wakeup + */ + void shutdown(void); + + /** + * Modem receive function. Maintains the internal state machine + * (see RxExpect) + */ + void receive(void); + + /** + * Loads a pattern from the EEPROM and shows it on the display. + * Loads the first 132 bytes (4 bytes header + 128 bytes data) of + * the pattern into the global disp_buf variable, updates the + * global active_anim to reflect the read metadata and calls + * Display::show() to display the pattern. + * + * @param pattern_no index of pattern to show + */ + void loadPattern(uint8_t pattern_no); + + /** + * Show the pattern stored in pattern on the display. + * Updates the global active_anim object with the metadata + * stored in the first four bytes of pattern and cals + * Display::show() to display it + * + * @param pattern array containing the pattern + */ + void loadPattern_buf(uint8_t *pattern); + + /** + * Load pattern from PROGMEM. Loads the entire pattern stored + * at pattern_ptr into the global disp_buf variable, updates + * the global active_anim object with the metadata stored in the + * first four pattern bytes and calls Display::show() to display + * the pattern. The pattern data must not be longer than 128 bytes. + * + * @param pattern_ptr pointer to pattern data in PROGMEM + */ + void loadPattern_P(const uint8_t *pattern_ptr); + + enum TransmissionControl : uint8_t { + BYTE_END = 0x84, + BYTE_START = 0x99, + BYTE_PATTERN = 0xa9, + }; + + enum ButtonMask : uint8_t { + BUTTON_NONE = 0, + BUTTON_LEFT = 1, + BUTTON_RIGHT = 2, + BUTTON_BOTH = 3 + }; + + enum RxExpect : uint8_t { + START1, + START2, + NEXT_BLOCK, + PATTERN1, + PATTERN2, + HEADER1, + HEADER2, + META1, + META2, + DATA_FIRSTBLOCK, + DATA, + }; + + RxExpect rxExpect; + ButtonMask btnMask; + + public: + System() { want_shutdown = 0; rxExpect = START1; current_anim_no = 0; btnMask = BUTTON_NONE; btn_debounce = 0;}; + + /** + * Initial MCU setup. Turns off unused peripherals to save power + * and configures the button pins. Also configures all other pins + * and peripherals using the enable function of their respective + * classes. Turns on interrupts once that's done. + */ + void initialize(void); + + /** + * System idle loop. Checks for button presses, handles + * standby/resume, reads data from the Modem and updates the Display. + * + * It is recommended to run this function before going back to sleep + * whenever the system is woken up by an interrupt. + */ + void loop(void); + + /** + * Resets the modem receive state machine and loads the + * "Transmission error" message. Called by the Watchdog Timeout + * ISR when a transmission was started (2x START received) but not + * properly finished (that is, four seconds passed since the last + * received byte and END byte was receveid). + */ + void handleTimeout(void); +}; + +extern System rocket; -- cgit v1.2.3