summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Hamming/Hamming.c174
-rw-r--r--src/Hamming/Hamming.h68
-rw-r--r--src/Hamming/HammingCalculateParityFast.c67
-rw-r--r--src/Hamming/HammingCalculateParitySmall.c99
-rw-r--r--src/Hamming/HammingCalculateParitySmallAndFast.c64
-rw-r--r--src/Hamming/HammingCalculateParityTextbook.c100
-rw-r--r--src/Hamming/LICENSE201
-rw-r--r--src/Hamming/README.md4
-rw-r--r--src/display.cc236
-rw-r--r--src/display.h223
-rw-r--r--src/fecmodem.cc96
-rw-r--r--src/fecmodem.h67
-rw-r--r--src/font.h244
-rw-r--r--src/gpio.cc102
-rw-r--r--src/gpio.h28
-rw-r--r--src/hamming.h47
-rw-r--r--src/main.cc34
-rw-r--r--src/modem.cc117
-rw-r--r--src/modem.h82
-rw-r--r--src/static_patterns.h75
-rw-r--r--src/storage.cc322
-rw-r--r--src/storage.h199
-rw-r--r--src/system.cc367
-rw-r--r--src/system.h144
24 files changed, 3160 insertions, 0 deletions
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 <avr/pgmspace.h>
+
+#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 <avr/pgmspace.h>
+
+#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 <avr/pgmspace.h>
+
+#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 <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/pgmspace.h>
+#include <stdlib.h>
+
+#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 <avr/io.h>
+#include <stdlib.h>
+
+/**
+ * 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 <avr/io.h>
+#include <stdlib.h>
+#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 <avr/io.h>
+#include <stdlib.h>
+
+#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 <avr/pgmspace.h>
+
+/*
+ * 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}; // <space>
+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 <avr/io.h>
+#include <stdlib.h>
+
+#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 <avr/io.h>
+#include <stdlib.h>
+
+/**
+ * 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 <avr/pgmspace.h>
+
+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 <avr/io.h>
+#include <stdlib.h>
+
+#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 <avr/io.h>
+#include <stdlib.h>
+#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 <avr/interrupt.h>
+#include <stdlib.h>
+
+/* 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 <avr/pgmspace.h>
+
+/*
+ * 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 <util/delay.h>
+#include <avr/io.h>
+#include <stdlib.h>
+
+#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 <stdlib.h>
+
+#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 <avr/io.h>
+#include <avr/interrupt.h>
+#include <avr/wdt.h>
+#include <avr/pgmspace.h>
+#include <util/delay.h>
+#include <stdlib.h>
+
+#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 <stdlib.h>
+
+#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;